Code

75f7944bb06b6f42b534356f1397dc9e16fe2c84
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int head:1;    /* Is it the current HEAD? */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int ltag:1;    /* If so, is the tag local? */
127         unsigned int remote:1;  /* Is it a remote ref? */
128         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129         char name[1];           /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         size_t size;            /* Number of refs. */
135         struct ref **refs;      /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152         INPUT_OK,
153         INPUT_SKIP,
154         INPUT_STOP,
155         INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164         int hotkey;
165         const char *text;
166         void *data;
167 };
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 /*
172  * Allocation helpers ... Entering macro hell to never be seen again.
173  */
175 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
176 static type *                                                                   \
177 name(type **mem, size_t size, size_t increase)                                  \
178 {                                                                               \
179         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
180         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181         type *tmp = *mem;                                                       \
182                                                                                 \
183         if (mem == NULL || num_chunks != num_chunks_new) {                      \
184                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185                 if (tmp)                                                        \
186                         *mem = tmp;                                             \
187         }                                                                       \
188                                                                                 \
189         return tmp;                                                             \
192 /*
193  * String helpers
194  */
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199         if (srclen > dstlen - 1)
200                 srclen = dstlen - 1;
202         strncpy(dst, src, srclen);
203         dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212         string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223         size_t size, pos;
225         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226                 if (src[pos] == '\t') {
227                         size_t expanded = tabsize - (size % tabsize);
229                         if (expanded + size >= dstlen - 1)
230                                 expanded = dstlen - size - 1;
231                         memcpy(dst + size, "        ", expanded);
232                         size += expanded;
233                 } else {
234                         dst[size++] = src[pos];
235                 }
236         }
238         dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244         int namelen;
246         while (isspace(*name))
247                 name++;
249         namelen = strlen(name) - 1;
250         while (namelen > 0 && isspace(name[namelen]))
251                 name[namelen--] = 0;
253         return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259         va_list args;
260         size_t pos = bufpos ? *bufpos : 0;
262         va_start(args, fmt);
263         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264         va_end(args);
266         if (bufpos)
267                 *bufpos = pos;
269         return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273         string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276         string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281         size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285         /* Diff-Header == DIFF_HEADER */
286         for (i = 0; i < len; i++) {
287                 if (toupper(str1[i]) == toupper(str2[i]))
288                         continue;
290                 if (string_enum_sep(str1[i]) &&
291                     string_enum_sep(str2[i]))
292                         continue;
294                 return str1[i] - str2[i];
295         }
297         return 0;
300 #define enum_equals(entry, str, len) \
301         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
303 struct enum_map {
304         const char *name;
305         int namelen;
306         int value;
307 };
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static char *
312 enum_map_name(const char *name, size_t namelen)
314         static char buf[SIZEOF_STR];
315         int bufpos;
317         for (bufpos = 0; bufpos <= namelen; bufpos++) {
318                 buf[bufpos] = tolower(name[bufpos]);
319                 if (buf[bufpos] == '_')
320                         buf[bufpos] = '-';
321         }
323         buf[bufpos] = 0;
324         return buf;
327 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
329 static bool
330 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
332         size_t namelen = strlen(name);
333         int i;
335         for (i = 0; i < map_size; i++)
336                 if (enum_equals(map[i], name, namelen)) {
337                         *value = map[i].value;
338                         return TRUE;
339                 }
341         return FALSE;
344 #define map_enum(attr, map, name) \
345         map_enum_do(map, ARRAY_SIZE(map), attr, name)
347 #define prefixcmp(str1, str2) \
348         strncmp(str1, str2, STRING_SIZE(str2))
350 static inline int
351 suffixcmp(const char *str, int slen, const char *suffix)
353         size_t len = slen >= 0 ? slen : strlen(str);
354         size_t suffixlen = strlen(suffix);
356         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
360 /*
361  * Unicode / UTF-8 handling
362  *
363  * NOTE: Much of the following code for dealing with Unicode is derived from
364  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
365  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
366  */
368 static inline int
369 unicode_width(unsigned long c, int tab_size)
371         if (c >= 0x1100 &&
372            (c <= 0x115f                         /* Hangul Jamo */
373             || c == 0x2329
374             || c == 0x232a
375             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
376                                                 /* CJK ... Yi */
377             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
378             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
379             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
380             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
381             || (c >= 0xffe0  && c <= 0xffe6)
382             || (c >= 0x20000 && c <= 0x2fffd)
383             || (c >= 0x30000 && c <= 0x3fffd)))
384                 return 2;
386         if (c == '\t')
387                 return tab_size;
389         return 1;
392 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
393  * Illegal bytes are set one. */
394 static const unsigned char utf8_bytes[256] = {
395         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,
396         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,
397         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,
398         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,
399         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,
400         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,
401         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,
402         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,
403 };
405 static inline unsigned char
406 utf8_char_length(const char *string, const char *end)
408         int c = *(unsigned char *) string;
410         return utf8_bytes[c];
413 /* Decode UTF-8 multi-byte representation into a Unicode character. */
414 static inline unsigned long
415 utf8_to_unicode(const char *string, size_t length)
417         unsigned long unicode;
419         switch (length) {
420         case 1:
421                 unicode  =   string[0];
422                 break;
423         case 2:
424                 unicode  =  (string[0] & 0x1f) << 6;
425                 unicode +=  (string[1] & 0x3f);
426                 break;
427         case 3:
428                 unicode  =  (string[0] & 0x0f) << 12;
429                 unicode += ((string[1] & 0x3f) << 6);
430                 unicode +=  (string[2] & 0x3f);
431                 break;
432         case 4:
433                 unicode  =  (string[0] & 0x0f) << 18;
434                 unicode += ((string[1] & 0x3f) << 12);
435                 unicode += ((string[2] & 0x3f) << 6);
436                 unicode +=  (string[3] & 0x3f);
437                 break;
438         case 5:
439                 unicode  =  (string[0] & 0x0f) << 24;
440                 unicode += ((string[1] & 0x3f) << 18);
441                 unicode += ((string[2] & 0x3f) << 12);
442                 unicode += ((string[3] & 0x3f) << 6);
443                 unicode +=  (string[4] & 0x3f);
444                 break;
445         case 6:
446                 unicode  =  (string[0] & 0x01) << 30;
447                 unicode += ((string[1] & 0x3f) << 24);
448                 unicode += ((string[2] & 0x3f) << 18);
449                 unicode += ((string[3] & 0x3f) << 12);
450                 unicode += ((string[4] & 0x3f) << 6);
451                 unicode +=  (string[5] & 0x3f);
452                 break;
453         default:
454                 return 0;
455         }
457         /* Invalid characters could return the special 0xfffd value but NUL
458          * should be just as good. */
459         return unicode > 0xffff ? 0 : unicode;
462 /* Calculates how much of string can be shown within the given maximum width
463  * and sets trimmed parameter to non-zero value if all of string could not be
464  * shown. If the reserve flag is TRUE, it will reserve at least one
465  * trailing character, which can be useful when drawing a delimiter.
466  *
467  * Returns the number of bytes to output from string to satisfy max_width. */
468 static size_t
469 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
471         const char *string = *start;
472         const char *end = strchr(string, '\0');
473         unsigned char last_bytes = 0;
474         size_t last_ucwidth = 0;
476         *width = 0;
477         *trimmed = 0;
479         while (string < end) {
480                 unsigned char bytes = utf8_char_length(string, end);
481                 size_t ucwidth;
482                 unsigned long unicode;
484                 if (string + bytes > end)
485                         break;
487                 /* Change representation to figure out whether
488                  * it is a single- or double-width character. */
490                 unicode = utf8_to_unicode(string, bytes);
491                 /* FIXME: Graceful handling of invalid Unicode character. */
492                 if (!unicode)
493                         break;
495                 ucwidth = unicode_width(unicode, tab_size);
496                 if (skip > 0) {
497                         skip -= ucwidth <= skip ? ucwidth : skip;
498                         *start += bytes;
499                 }
500                 *width  += ucwidth;
501                 if (*width > max_width) {
502                         *trimmed = 1;
503                         *width -= ucwidth;
504                         if (reserve && *width == max_width) {
505                                 string -= last_bytes;
506                                 *width -= last_ucwidth;
507                         }
508                         break;
509                 }
511                 string  += bytes;
512                 last_bytes = ucwidth ? bytes : 0;
513                 last_ucwidth = ucwidth;
514         }
516         return string - *start;
520 #define DATE_INFO \
521         DATE_(NO), \
522         DATE_(DEFAULT), \
523         DATE_(RELATIVE), \
524         DATE_(SHORT)
526 enum date {
527 #define DATE_(name) DATE_##name
528         DATE_INFO
529 #undef  DATE_
530 };
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534         DATE_INFO
535 #undef  DATE_
536 };
538 struct time {
539         time_t sec;
540         int tz;
541 };
543 static inline int timecmp(const struct time *t1, const struct time *t2)
545         return t1->sec - t2->sec;
548 static const char *
549 mkdate(const struct time *time, enum date date)
551         static char buf[DATE_COLS + 1];
552         static const struct enum_map reldate[] = {
553                 { "second", 1,                  60 * 2 },
554                 { "minute", 60,                 60 * 60 * 2 },
555                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
556                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
557                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
558                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
559         };
560         struct tm tm;
562         if (!date || !time || !time->sec)
563                 return "";
565         if (date == DATE_RELATIVE) {
566                 struct timeval now;
567                 time_t date = time->sec + time->tz;
568                 time_t seconds;
569                 int i;
571                 gettimeofday(&now, NULL);
572                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
573                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
574                         if (seconds >= reldate[i].value)
575                                 continue;
577                         seconds /= reldate[i].namelen;
578                         if (!string_format(buf, "%ld %s%s %s",
579                                            seconds, reldate[i].name,
580                                            seconds > 1 ? "s" : "",
581                                            now.tv_sec >= date ? "ago" : "ahead"))
582                                 break;
583                         return buf;
584                 }
585         }
587         gmtime_r(&time->sec, &tm);
588         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
592 #define AUTHOR_VALUES \
593         AUTHOR_(NO), \
594         AUTHOR_(FULL), \
595         AUTHOR_(ABBREVIATED)
597 enum author {
598 #define AUTHOR_(name) AUTHOR_##name
599         AUTHOR_VALUES,
600 #undef  AUTHOR_
601         AUTHOR_DEFAULT = AUTHOR_FULL
602 };
604 static const struct enum_map author_map[] = {
605 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
606         AUTHOR_VALUES
607 #undef  AUTHOR_
608 };
610 static const char *
611 get_author_initials(const char *author)
613         static char initials[AUTHOR_COLS * 6 + 1];
614         size_t pos = 0;
615         const char *end = strchr(author, '\0');
617 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
619         memset(initials, 0, sizeof(initials));
620         while (author < end) {
621                 unsigned char bytes;
622                 size_t i;
624                 while (is_initial_sep(*author))
625                         author++;
627                 bytes = utf8_char_length(author, end);
628                 if (bytes < sizeof(initials) - 1 - pos) {
629                         while (bytes--) {
630                                 initials[pos++] = *author++;
631                         }
632                 }
634                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
635                         if (i < sizeof(initials) - 1)
636                                 initials[i++] = *author;
637                 }
639                 initials[i++] = 0;
640         }
642         return initials;
646 static bool
647 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
649         int valuelen;
651         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
652                 bool advance = cmd[valuelen] != 0;
654                 cmd[valuelen] = 0;
655                 argv[(*argc)++] = chomp_string(cmd);
656                 cmd = chomp_string(cmd + valuelen + advance);
657         }
659         if (*argc < SIZEOF_ARG)
660                 argv[*argc] = NULL;
661         return *argc < SIZEOF_ARG;
664 static void
665 argv_from_env(const char **argv, const char *name)
667         char *env = argv ? getenv(name) : NULL;
668         int argc = 0;
670         if (env && *env)
671                 env = strdup(env);
672         if (env && !argv_from_string(argv, &argc, env))
673                 die("Too many arguments in the `%s` environment variable", name);
677 /*
678  * Executing external commands.
679  */
681 enum io_type {
682         IO_FD,                  /* File descriptor based IO. */
683         IO_BG,                  /* Execute command in the background. */
684         IO_FG,                  /* Execute command with same std{in,out,err}. */
685         IO_RD,                  /* Read only fork+exec IO. */
686         IO_WR,                  /* Write only fork+exec IO. */
687         IO_AP,                  /* Append fork+exec output to file. */
688 };
690 struct io {
691         enum io_type type;      /* The requested type of pipe. */
692         const char *dir;        /* Directory from which to execute. */
693         pid_t pid;              /* PID of spawned process. */
694         int pipe;               /* Pipe end for reading or writing. */
695         int error;              /* Error status. */
696         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
697         char *buf;              /* Read buffer. */
698         size_t bufalloc;        /* Allocated buffer size. */
699         size_t bufsize;         /* Buffer content size. */
700         char *bufpos;           /* Current buffer position. */
701         unsigned int eof:1;     /* Has end of file been reached. */
702 };
704 static void
705 io_reset(struct io *io)
707         io->pipe = -1;
708         io->pid = 0;
709         io->buf = io->bufpos = NULL;
710         io->bufalloc = io->bufsize = 0;
711         io->error = 0;
712         io->eof = 0;
715 static void
716 io_init(struct io *io, const char *dir, enum io_type type)
718         io_reset(io);
719         io->type = type;
720         io->dir = dir;
723 static bool
724 io_format(struct io *io, const char *dir, enum io_type type,
725           const char *argv[], enum format_flags flags)
727         io_init(io, dir, type);
728         return format_argv(io->argv, argv, flags);
731 static bool
732 io_open(struct io *io, const char *fmt, ...)
734         char name[SIZEOF_STR] = "";
735         bool fits;
736         va_list args;
738         io_init(io, NULL, IO_FD);
740         va_start(args, fmt);
741         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
742         va_end(args);
744         if (!fits) {
745                 io->error = ENAMETOOLONG;
746                 return FALSE;
747         }
748         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
749         if (io->pipe == -1)
750                 io->error = errno;
751         return io->pipe != -1;
754 static bool
755 io_kill(struct io *io)
757         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
760 static bool
761 io_done(struct io *io)
763         pid_t pid = io->pid;
765         if (io->pipe != -1)
766                 close(io->pipe);
767         free(io->buf);
768         io_reset(io);
770         while (pid > 0) {
771                 int status;
772                 pid_t waiting = waitpid(pid, &status, 0);
774                 if (waiting < 0) {
775                         if (errno == EINTR)
776                                 continue;
777                         io->error = errno;
778                         return FALSE;
779                 }
781                 return waiting == pid &&
782                        !WIFSIGNALED(status) &&
783                        WIFEXITED(status) &&
784                        !WEXITSTATUS(status);
785         }
787         return TRUE;
790 static bool
791 io_start(struct io *io)
793         int pipefds[2] = { -1, -1 };
795         if (io->type == IO_FD)
796                 return TRUE;
798         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
799                 io->error = errno;
800                 return FALSE;
801         } else if (io->type == IO_AP) {
802                 pipefds[1] = io->pipe;
803         }
805         if ((io->pid = fork())) {
806                 if (io->pid == -1)
807                         io->error = errno;
808                 if (pipefds[!(io->type == IO_WR)] != -1)
809                         close(pipefds[!(io->type == IO_WR)]);
810                 if (io->pid != -1) {
811                         io->pipe = pipefds[!!(io->type == IO_WR)];
812                         return TRUE;
813                 }
815         } else {
816                 if (io->type != IO_FG) {
817                         int devnull = open("/dev/null", O_RDWR);
818                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
819                         int writefd = (io->type == IO_RD || io->type == IO_AP)
820                                                         ? pipefds[1] : devnull;
822                         dup2(readfd,  STDIN_FILENO);
823                         dup2(writefd, STDOUT_FILENO);
824                         dup2(devnull, STDERR_FILENO);
826                         close(devnull);
827                         if (pipefds[0] != -1)
828                                 close(pipefds[0]);
829                         if (pipefds[1] != -1)
830                                 close(pipefds[1]);
831                 }
833                 if (io->dir && *io->dir && chdir(io->dir) == -1)
834                         exit(errno);
836                 execvp(io->argv[0], (char *const*) io->argv);
837                 exit(errno);
838         }
840         if (pipefds[!!(io->type == IO_WR)] != -1)
841                 close(pipefds[!!(io->type == IO_WR)]);
842         return FALSE;
845 static bool
846 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
848         io_init(io, dir, type);
849         if (!format_argv(io->argv, argv, FORMAT_NONE))
850                 return FALSE;
851         return io_start(io);
854 static int
855 io_complete(struct io *io)
857         return io_start(io) && io_done(io);
860 static int
861 io_run_bg(const char **argv)
863         struct io io = {};
865         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
866                 return FALSE;
867         return io_complete(&io);
870 static bool
871 io_run_fg(const char **argv, const char *dir)
873         struct io io = {};
875         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
876                 return FALSE;
877         return io_complete(&io);
880 static bool
881 io_run_append(const char **argv, enum format_flags flags, int fd)
883         struct io io = {};
885         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
886                 close(fd);
887                 return FALSE;
888         }
890         io.pipe = fd;
891         return io_complete(&io);
894 static bool
895 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
897         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
900 static bool
901 io_eof(struct io *io)
903         return io->eof;
906 static int
907 io_error(struct io *io)
909         return io->error;
912 static char *
913 io_strerror(struct io *io)
915         return strerror(io->error);
918 static bool
919 io_can_read(struct io *io)
921         struct timeval tv = { 0, 500 };
922         fd_set fds;
924         FD_ZERO(&fds);
925         FD_SET(io->pipe, &fds);
927         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
930 static ssize_t
931 io_read(struct io *io, void *buf, size_t bufsize)
933         do {
934                 ssize_t readsize = read(io->pipe, buf, bufsize);
936                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
937                         continue;
938                 else if (readsize == -1)
939                         io->error = errno;
940                 else if (readsize == 0)
941                         io->eof = 1;
942                 return readsize;
943         } while (1);
946 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
948 static char *
949 io_get(struct io *io, int c, bool can_read)
951         char *eol;
952         ssize_t readsize;
954         while (TRUE) {
955                 if (io->bufsize > 0) {
956                         eol = memchr(io->bufpos, c, io->bufsize);
957                         if (eol) {
958                                 char *line = io->bufpos;
960                                 *eol = 0;
961                                 io->bufpos = eol + 1;
962                                 io->bufsize -= io->bufpos - line;
963                                 return line;
964                         }
965                 }
967                 if (io_eof(io)) {
968                         if (io->bufsize) {
969                                 io->bufpos[io->bufsize] = 0;
970                                 io->bufsize = 0;
971                                 return io->bufpos;
972                         }
973                         return NULL;
974                 }
976                 if (!can_read)
977                         return NULL;
979                 if (io->bufsize > 0 && io->bufpos > io->buf)
980                         memmove(io->buf, io->bufpos, io->bufsize);
982                 if (io->bufalloc == io->bufsize) {
983                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
984                                 return NULL;
985                         io->bufalloc += BUFSIZ;
986                 }
988                 io->bufpos = io->buf;
989                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
990                 if (io_error(io))
991                         return NULL;
992                 io->bufsize += readsize;
993         }
996 static bool
997 io_write(struct io *io, const void *buf, size_t bufsize)
999         size_t written = 0;
1001         while (!io_error(io) && written < bufsize) {
1002                 ssize_t size;
1004                 size = write(io->pipe, buf + written, bufsize - written);
1005                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1006                         continue;
1007                 else if (size == -1)
1008                         io->error = errno;
1009                 else
1010                         written += size;
1011         }
1013         return written == bufsize;
1016 static bool
1017 io_read_buf(struct io *io, char buf[], size_t bufsize)
1019         char *result = io_get(io, '\n', TRUE);
1021         if (result) {
1022                 result = chomp_string(result);
1023                 string_ncopy_do(buf, bufsize, result, strlen(result));
1024         }
1026         return io_done(io) && result;
1029 static bool
1030 io_run_buf(const char **argv, char buf[], size_t bufsize)
1032         struct io io = {};
1034         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1035             && io_read_buf(&io, buf, bufsize);
1038 static int
1039 io_load(struct io *io, const char *separators,
1040         int (*read_property)(char *, size_t, char *, size_t))
1042         char *name;
1043         int state = OK;
1045         if (!io_start(io))
1046                 return ERR;
1048         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1049                 char *value;
1050                 size_t namelen;
1051                 size_t valuelen;
1053                 name = chomp_string(name);
1054                 namelen = strcspn(name, separators);
1056                 if (name[namelen]) {
1057                         name[namelen] = 0;
1058                         value = chomp_string(name + namelen + 1);
1059                         valuelen = strlen(value);
1061                 } else {
1062                         value = "";
1063                         valuelen = 0;
1064                 }
1066                 state = read_property(name, namelen, value, valuelen);
1067         }
1069         if (state != ERR && io_error(io))
1070                 state = ERR;
1071         io_done(io);
1073         return state;
1076 static int
1077 io_run_load(const char **argv, const char *separators,
1078             int (*read_property)(char *, size_t, char *, size_t))
1080         struct io io = {};
1082         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1083                 ? io_load(&io, separators, read_property) : ERR;
1087 /*
1088  * User requests
1089  */
1091 #define REQ_INFO \
1092         /* XXX: Keep the view request first and in sync with views[]. */ \
1093         REQ_GROUP("View switching") \
1094         REQ_(VIEW_MAIN,         "Show main view"), \
1095         REQ_(VIEW_DIFF,         "Show diff view"), \
1096         REQ_(VIEW_LOG,          "Show log view"), \
1097         REQ_(VIEW_TREE,         "Show tree view"), \
1098         REQ_(VIEW_BLOB,         "Show blob view"), \
1099         REQ_(VIEW_BLAME,        "Show blame view"), \
1100         REQ_(VIEW_BRANCH,       "Show branch view"), \
1101         REQ_(VIEW_HELP,         "Show help page"), \
1102         REQ_(VIEW_PAGER,        "Show pager view"), \
1103         REQ_(VIEW_STATUS,       "Show status view"), \
1104         REQ_(VIEW_STAGE,        "Show stage view"), \
1105         \
1106         REQ_GROUP("View manipulation") \
1107         REQ_(ENTER,             "Enter current line and scroll"), \
1108         REQ_(NEXT,              "Move to next"), \
1109         REQ_(PREVIOUS,          "Move to previous"), \
1110         REQ_(PARENT,            "Move to parent"), \
1111         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1112         REQ_(REFRESH,           "Reload and refresh"), \
1113         REQ_(MAXIMIZE,          "Maximize the current view"), \
1114         REQ_(VIEW_CLOSE,        "Close the current view"), \
1115         REQ_(QUIT,              "Close all views and quit"), \
1116         \
1117         REQ_GROUP("View specific requests") \
1118         REQ_(STATUS_UPDATE,     "Update file status"), \
1119         REQ_(STATUS_REVERT,     "Revert file changes"), \
1120         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1121         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1122         \
1123         REQ_GROUP("Cursor navigation") \
1124         REQ_(MOVE_UP,           "Move cursor one line up"), \
1125         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1126         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1127         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1128         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1129         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1130         \
1131         REQ_GROUP("Scrolling") \
1132         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1133         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1134         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1135         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1136         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1137         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1138         \
1139         REQ_GROUP("Searching") \
1140         REQ_(SEARCH,            "Search the view"), \
1141         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1142         REQ_(FIND_NEXT,         "Find next search match"), \
1143         REQ_(FIND_PREV,         "Find previous search match"), \
1144         \
1145         REQ_GROUP("Option manipulation") \
1146         REQ_(OPTIONS,           "Open option menu"), \
1147         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1148         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1149         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1150         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1151         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1152         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1153         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1154         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1155         \
1156         REQ_GROUP("Misc") \
1157         REQ_(PROMPT,            "Bring up the prompt"), \
1158         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1159         REQ_(SHOW_VERSION,      "Show version information"), \
1160         REQ_(STOP_LOADING,      "Stop all loading views"), \
1161         REQ_(EDIT,              "Open in editor"), \
1162         REQ_(NONE,              "Do nothing")
1165 /* User action requests. */
1166 enum request {
1167 #define REQ_GROUP(help)
1168 #define REQ_(req, help) REQ_##req
1170         /* Offset all requests to avoid conflicts with ncurses getch values. */
1171         REQ_OFFSET = KEY_MAX + 1,
1172         REQ_INFO
1174 #undef  REQ_GROUP
1175 #undef  REQ_
1176 };
1178 struct request_info {
1179         enum request request;
1180         const char *name;
1181         int namelen;
1182         const char *help;
1183 };
1185 static const struct request_info req_info[] = {
1186 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1187 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1188         REQ_INFO
1189 #undef  REQ_GROUP
1190 #undef  REQ_
1191 };
1193 static enum request
1194 get_request(const char *name)
1196         int namelen = strlen(name);
1197         int i;
1199         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1200                 if (enum_equals(req_info[i], name, namelen))
1201                         return req_info[i].request;
1203         return REQ_NONE;
1207 /*
1208  * Options
1209  */
1211 /* Option and state variables. */
1212 static enum date opt_date               = DATE_DEFAULT;
1213 static enum author opt_author           = AUTHOR_DEFAULT;
1214 static bool opt_line_number             = FALSE;
1215 static bool opt_line_graphics           = TRUE;
1216 static bool opt_rev_graph               = FALSE;
1217 static bool opt_show_refs               = TRUE;
1218 static int opt_num_interval             = 5;
1219 static double opt_hscroll               = 0.50;
1220 static double opt_scale_split_view      = 2.0 / 3.0;
1221 static int opt_tab_size                 = 8;
1222 static int opt_author_cols              = AUTHOR_COLS;
1223 static char opt_path[SIZEOF_STR]        = "";
1224 static char opt_file[SIZEOF_STR]        = "";
1225 static char opt_ref[SIZEOF_REF]         = "";
1226 static char opt_head[SIZEOF_REF]        = "";
1227 static char opt_remote[SIZEOF_REF]      = "";
1228 static char opt_encoding[20]            = "UTF-8";
1229 static iconv_t opt_iconv_in             = ICONV_NONE;
1230 static iconv_t opt_iconv_out            = ICONV_NONE;
1231 static char opt_search[SIZEOF_STR]      = "";
1232 static char opt_cdup[SIZEOF_STR]        = "";
1233 static char opt_prefix[SIZEOF_STR]      = "";
1234 static char opt_git_dir[SIZEOF_STR]     = "";
1235 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1236 static char opt_editor[SIZEOF_STR]      = "";
1237 static FILE *opt_tty                    = NULL;
1239 #define is_initial_commit()     (!get_ref_head())
1240 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1243 /*
1244  * Line-oriented content detection.
1245  */
1247 #define LINE_INFO \
1248 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1249 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1250 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1251 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1252 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1253 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1254 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1255 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1261 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1262 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1263 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1264 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1265 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1266 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1267 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1268 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1269 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1270 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1271 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1272 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1273 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1274 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1275 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1276 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1277 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1278 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1279 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1280 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1281 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1282 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1283 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1284 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1285 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1286 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1287 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1288 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1289 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1290 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1291 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1292 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1293 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1294 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1295 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1296 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1297 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1298 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1299 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1301 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1302 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1303 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1304 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1306 enum line_type {
1307 #define LINE(type, line, fg, bg, attr) \
1308         LINE_##type
1309         LINE_INFO,
1310         LINE_NONE
1311 #undef  LINE
1312 };
1314 struct line_info {
1315         const char *name;       /* Option name. */
1316         int namelen;            /* Size of option name. */
1317         const char *line;       /* The start of line to match. */
1318         int linelen;            /* Size of string to match. */
1319         int fg, bg, attr;       /* Color and text attributes for the lines. */
1320 };
1322 static struct line_info line_info[] = {
1323 #define LINE(type, line, fg, bg, attr) \
1324         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1325         LINE_INFO
1326 #undef  LINE
1327 };
1329 static enum line_type
1330 get_line_type(const char *line)
1332         int linelen = strlen(line);
1333         enum line_type type;
1335         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1336                 /* Case insensitive search matches Signed-off-by lines better. */
1337                 if (linelen >= line_info[type].linelen &&
1338                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1339                         return type;
1341         return LINE_DEFAULT;
1344 static inline int
1345 get_line_attr(enum line_type type)
1347         assert(type < ARRAY_SIZE(line_info));
1348         return COLOR_PAIR(type) | line_info[type].attr;
1351 static struct line_info *
1352 get_line_info(const char *name)
1354         size_t namelen = strlen(name);
1355         enum line_type type;
1357         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1358                 if (enum_equals(line_info[type], name, namelen))
1359                         return &line_info[type];
1361         return NULL;
1364 static void
1365 init_colors(void)
1367         int default_bg = line_info[LINE_DEFAULT].bg;
1368         int default_fg = line_info[LINE_DEFAULT].fg;
1369         enum line_type type;
1371         start_color();
1373         if (assume_default_colors(default_fg, default_bg) == ERR) {
1374                 default_bg = COLOR_BLACK;
1375                 default_fg = COLOR_WHITE;
1376         }
1378         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1379                 struct line_info *info = &line_info[type];
1380                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1381                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1383                 init_pair(type, fg, bg);
1384         }
1387 struct line {
1388         enum line_type type;
1390         /* State flags */
1391         unsigned int selected:1;
1392         unsigned int dirty:1;
1393         unsigned int cleareol:1;
1394         unsigned int other:16;
1396         void *data;             /* User data */
1397 };
1400 /*
1401  * Keys
1402  */
1404 struct keybinding {
1405         int alias;
1406         enum request request;
1407 };
1409 static const struct keybinding default_keybindings[] = {
1410         /* View switching */
1411         { 'm',          REQ_VIEW_MAIN },
1412         { 'd',          REQ_VIEW_DIFF },
1413         { 'l',          REQ_VIEW_LOG },
1414         { 't',          REQ_VIEW_TREE },
1415         { 'f',          REQ_VIEW_BLOB },
1416         { 'B',          REQ_VIEW_BLAME },
1417         { 'H',          REQ_VIEW_BRANCH },
1418         { 'p',          REQ_VIEW_PAGER },
1419         { 'h',          REQ_VIEW_HELP },
1420         { 'S',          REQ_VIEW_STATUS },
1421         { 'c',          REQ_VIEW_STAGE },
1423         /* View manipulation */
1424         { 'q',          REQ_VIEW_CLOSE },
1425         { KEY_TAB,      REQ_VIEW_NEXT },
1426         { KEY_RETURN,   REQ_ENTER },
1427         { KEY_UP,       REQ_PREVIOUS },
1428         { KEY_DOWN,     REQ_NEXT },
1429         { 'R',          REQ_REFRESH },
1430         { KEY_F(5),     REQ_REFRESH },
1431         { 'O',          REQ_MAXIMIZE },
1433         /* Cursor navigation */
1434         { 'k',          REQ_MOVE_UP },
1435         { 'j',          REQ_MOVE_DOWN },
1436         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1437         { KEY_END,      REQ_MOVE_LAST_LINE },
1438         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1439         { ' ',          REQ_MOVE_PAGE_DOWN },
1440         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1441         { 'b',          REQ_MOVE_PAGE_UP },
1442         { '-',          REQ_MOVE_PAGE_UP },
1444         /* Scrolling */
1445         { KEY_LEFT,     REQ_SCROLL_LEFT },
1446         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1447         { KEY_IC,       REQ_SCROLL_LINE_UP },
1448         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1449         { 'w',          REQ_SCROLL_PAGE_UP },
1450         { 's',          REQ_SCROLL_PAGE_DOWN },
1452         /* Searching */
1453         { '/',          REQ_SEARCH },
1454         { '?',          REQ_SEARCH_BACK },
1455         { 'n',          REQ_FIND_NEXT },
1456         { 'N',          REQ_FIND_PREV },
1458         /* Misc */
1459         { 'Q',          REQ_QUIT },
1460         { 'z',          REQ_STOP_LOADING },
1461         { 'v',          REQ_SHOW_VERSION },
1462         { 'r',          REQ_SCREEN_REDRAW },
1463         { 'o',          REQ_OPTIONS },
1464         { '.',          REQ_TOGGLE_LINENO },
1465         { 'D',          REQ_TOGGLE_DATE },
1466         { 'A',          REQ_TOGGLE_AUTHOR },
1467         { 'g',          REQ_TOGGLE_REV_GRAPH },
1468         { 'F',          REQ_TOGGLE_REFS },
1469         { 'I',          REQ_TOGGLE_SORT_ORDER },
1470         { 'i',          REQ_TOGGLE_SORT_FIELD },
1471         { ':',          REQ_PROMPT },
1472         { 'u',          REQ_STATUS_UPDATE },
1473         { '!',          REQ_STATUS_REVERT },
1474         { 'M',          REQ_STATUS_MERGE },
1475         { '@',          REQ_STAGE_NEXT },
1476         { ',',          REQ_PARENT },
1477         { 'e',          REQ_EDIT },
1478 };
1480 #define KEYMAP_INFO \
1481         KEYMAP_(GENERIC), \
1482         KEYMAP_(MAIN), \
1483         KEYMAP_(DIFF), \
1484         KEYMAP_(LOG), \
1485         KEYMAP_(TREE), \
1486         KEYMAP_(BLOB), \
1487         KEYMAP_(BLAME), \
1488         KEYMAP_(BRANCH), \
1489         KEYMAP_(PAGER), \
1490         KEYMAP_(HELP), \
1491         KEYMAP_(STATUS), \
1492         KEYMAP_(STAGE)
1494 enum keymap {
1495 #define KEYMAP_(name) KEYMAP_##name
1496         KEYMAP_INFO
1497 #undef  KEYMAP_
1498 };
1500 static const struct enum_map keymap_table[] = {
1501 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1502         KEYMAP_INFO
1503 #undef  KEYMAP_
1504 };
1506 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1508 struct keybinding_table {
1509         struct keybinding *data;
1510         size_t size;
1511 };
1513 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1515 static void
1516 add_keybinding(enum keymap keymap, enum request request, int key)
1518         struct keybinding_table *table = &keybindings[keymap];
1520         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1521         if (!table->data)
1522                 die("Failed to allocate keybinding");
1523         table->data[table->size].alias = key;
1524         table->data[table->size++].request = request;
1527 /* Looks for a key binding first in the given map, then in the generic map, and
1528  * lastly in the default keybindings. */
1529 static enum request
1530 get_keybinding(enum keymap keymap, int key)
1532         size_t i;
1534         for (i = 0; i < keybindings[keymap].size; i++)
1535                 if (keybindings[keymap].data[i].alias == key)
1536                         return keybindings[keymap].data[i].request;
1538         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1539                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1540                         return keybindings[KEYMAP_GENERIC].data[i].request;
1542         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1543                 if (default_keybindings[i].alias == key)
1544                         return default_keybindings[i].request;
1546         return (enum request) key;
1550 struct key {
1551         const char *name;
1552         int value;
1553 };
1555 static const struct key key_table[] = {
1556         { "Enter",      KEY_RETURN },
1557         { "Space",      ' ' },
1558         { "Backspace",  KEY_BACKSPACE },
1559         { "Tab",        KEY_TAB },
1560         { "Escape",     KEY_ESC },
1561         { "Left",       KEY_LEFT },
1562         { "Right",      KEY_RIGHT },
1563         { "Up",         KEY_UP },
1564         { "Down",       KEY_DOWN },
1565         { "Insert",     KEY_IC },
1566         { "Delete",     KEY_DC },
1567         { "Hash",       '#' },
1568         { "Home",       KEY_HOME },
1569         { "End",        KEY_END },
1570         { "PageUp",     KEY_PPAGE },
1571         { "PageDown",   KEY_NPAGE },
1572         { "F1",         KEY_F(1) },
1573         { "F2",         KEY_F(2) },
1574         { "F3",         KEY_F(3) },
1575         { "F4",         KEY_F(4) },
1576         { "F5",         KEY_F(5) },
1577         { "F6",         KEY_F(6) },
1578         { "F7",         KEY_F(7) },
1579         { "F8",         KEY_F(8) },
1580         { "F9",         KEY_F(9) },
1581         { "F10",        KEY_F(10) },
1582         { "F11",        KEY_F(11) },
1583         { "F12",        KEY_F(12) },
1584 };
1586 static int
1587 get_key_value(const char *name)
1589         int i;
1591         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1592                 if (!strcasecmp(key_table[i].name, name))
1593                         return key_table[i].value;
1595         if (strlen(name) == 1 && isprint(*name))
1596                 return (int) *name;
1598         return ERR;
1601 static const char *
1602 get_key_name(int key_value)
1604         static char key_char[] = "'X'";
1605         const char *seq = NULL;
1606         int key;
1608         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1609                 if (key_table[key].value == key_value)
1610                         seq = key_table[key].name;
1612         if (seq == NULL &&
1613             key_value < 127 &&
1614             isprint(key_value)) {
1615                 key_char[1] = (char) key_value;
1616                 seq = key_char;
1617         }
1619         return seq ? seq : "(no key)";
1622 static bool
1623 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1625         const char *sep = *pos > 0 ? ", " : "";
1626         const char *keyname = get_key_name(keybinding->alias);
1628         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1631 static bool
1632 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1633                            enum keymap keymap, bool all)
1635         int i;
1637         for (i = 0; i < keybindings[keymap].size; i++) {
1638                 if (keybindings[keymap].data[i].request == request) {
1639                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1640                                 return FALSE;
1641                         if (!all)
1642                                 break;
1643                 }
1644         }
1646         return TRUE;
1649 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1651 static const char *
1652 get_keys(enum keymap keymap, enum request request, bool all)
1654         static char buf[BUFSIZ];
1655         size_t pos = 0;
1656         int i;
1658         buf[pos] = 0;
1660         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1661                 return "Too many keybindings!";
1662         if (pos > 0 && !all)
1663                 return buf;
1665         if (keymap != KEYMAP_GENERIC) {
1666                 /* Only the generic keymap includes the default keybindings when
1667                  * listing all keys. */
1668                 if (all)
1669                         return buf;
1671                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1672                         return "Too many keybindings!";
1673                 if (pos)
1674                         return buf;
1675         }
1677         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1678                 if (default_keybindings[i].request == request) {
1679                         if (!append_key(buf, &pos, &default_keybindings[i]))
1680                                 return "Too many keybindings!";
1681                         if (!all)
1682                                 return buf;
1683                 }
1684         }
1686         return buf;
1689 struct run_request {
1690         enum keymap keymap;
1691         int key;
1692         const char *argv[SIZEOF_ARG];
1693 };
1695 static struct run_request *run_request;
1696 static size_t run_requests;
1698 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1700 static enum request
1701 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1703         struct run_request *req;
1705         if (argc >= ARRAY_SIZE(req->argv) - 1)
1706                 return REQ_NONE;
1708         if (!realloc_run_requests(&run_request, run_requests, 1))
1709                 return REQ_NONE;
1711         req = &run_request[run_requests];
1712         req->keymap = keymap;
1713         req->key = key;
1714         req->argv[0] = NULL;
1716         if (!format_argv(req->argv, argv, FORMAT_NONE))
1717                 return REQ_NONE;
1719         return REQ_NONE + ++run_requests;
1722 static struct run_request *
1723 get_run_request(enum request request)
1725         if (request <= REQ_NONE)
1726                 return NULL;
1727         return &run_request[request - REQ_NONE - 1];
1730 static void
1731 add_builtin_run_requests(void)
1733         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1734         const char *commit[] = { "git", "commit", NULL };
1735         const char *gc[] = { "git", "gc", NULL };
1736         struct {
1737                 enum keymap keymap;
1738                 int key;
1739                 int argc;
1740                 const char **argv;
1741         } reqs[] = {
1742                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1743                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1744                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1745         };
1746         int i;
1748         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1749                 enum request req;
1751                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1752                 if (req != REQ_NONE)
1753                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1754         }
1757 /*
1758  * User config file handling.
1759  */
1761 static int   config_lineno;
1762 static bool  config_errors;
1763 static const char *config_msg;
1765 static const struct enum_map color_map[] = {
1766 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1767         COLOR_MAP(DEFAULT),
1768         COLOR_MAP(BLACK),
1769         COLOR_MAP(BLUE),
1770         COLOR_MAP(CYAN),
1771         COLOR_MAP(GREEN),
1772         COLOR_MAP(MAGENTA),
1773         COLOR_MAP(RED),
1774         COLOR_MAP(WHITE),
1775         COLOR_MAP(YELLOW),
1776 };
1778 static const struct enum_map attr_map[] = {
1779 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1780         ATTR_MAP(NORMAL),
1781         ATTR_MAP(BLINK),
1782         ATTR_MAP(BOLD),
1783         ATTR_MAP(DIM),
1784         ATTR_MAP(REVERSE),
1785         ATTR_MAP(STANDOUT),
1786         ATTR_MAP(UNDERLINE),
1787 };
1789 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1791 static int parse_step(double *opt, const char *arg)
1793         *opt = atoi(arg);
1794         if (!strchr(arg, '%'))
1795                 return OK;
1797         /* "Shift down" so 100% and 1 does not conflict. */
1798         *opt = (*opt - 1) / 100;
1799         if (*opt >= 1.0) {
1800                 *opt = 0.99;
1801                 config_msg = "Step value larger than 100%";
1802                 return ERR;
1803         }
1804         if (*opt < 0.0) {
1805                 *opt = 1;
1806                 config_msg = "Invalid step value";
1807                 return ERR;
1808         }
1809         return OK;
1812 static int
1813 parse_int(int *opt, const char *arg, int min, int max)
1815         int value = atoi(arg);
1817         if (min <= value && value <= max) {
1818                 *opt = value;
1819                 return OK;
1820         }
1822         config_msg = "Integer value out of bound";
1823         return ERR;
1826 static bool
1827 set_color(int *color, const char *name)
1829         if (map_enum(color, color_map, name))
1830                 return TRUE;
1831         if (!prefixcmp(name, "color"))
1832                 return parse_int(color, name + 5, 0, 255) == OK;
1833         return FALSE;
1836 /* Wants: object fgcolor bgcolor [attribute] */
1837 static int
1838 option_color_command(int argc, const char *argv[])
1840         struct line_info *info;
1842         if (argc < 3) {
1843                 config_msg = "Wrong number of arguments given to color command";
1844                 return ERR;
1845         }
1847         info = get_line_info(argv[0]);
1848         if (!info) {
1849                 static const struct enum_map obsolete[] = {
1850                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1851                         ENUM_MAP("main-date",   LINE_DATE),
1852                         ENUM_MAP("main-author", LINE_AUTHOR),
1853                 };
1854                 int index;
1856                 if (!map_enum(&index, obsolete, argv[0])) {
1857                         config_msg = "Unknown color name";
1858                         return ERR;
1859                 }
1860                 info = &line_info[index];
1861         }
1863         if (!set_color(&info->fg, argv[1]) ||
1864             !set_color(&info->bg, argv[2])) {
1865                 config_msg = "Unknown color";
1866                 return ERR;
1867         }
1869         info->attr = 0;
1870         while (argc-- > 3) {
1871                 int attr;
1873                 if (!set_attribute(&attr, argv[argc])) {
1874                         config_msg = "Unknown attribute";
1875                         return ERR;
1876                 }
1877                 info->attr |= attr;
1878         }
1880         return OK;
1883 static int parse_bool(bool *opt, const char *arg)
1885         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1886                 ? TRUE : FALSE;
1887         return OK;
1890 static int parse_enum_do(unsigned int *opt, const char *arg,
1891                          const struct enum_map *map, size_t map_size)
1893         bool is_true;
1895         assert(map_size > 1);
1897         if (map_enum_do(map, map_size, (int *) opt, arg))
1898                 return OK;
1900         if (parse_bool(&is_true, arg) != OK)
1901                 return ERR;
1903         *opt = is_true ? map[1].value : map[0].value;
1904         return OK;
1907 #define parse_enum(opt, arg, map) \
1908         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1910 static int
1911 parse_string(char *opt, const char *arg, size_t optsize)
1913         int arglen = strlen(arg);
1915         switch (arg[0]) {
1916         case '\"':
1917         case '\'':
1918                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1919                         config_msg = "Unmatched quotation";
1920                         return ERR;
1921                 }
1922                 arg += 1; arglen -= 2;
1923         default:
1924                 string_ncopy_do(opt, optsize, arg, arglen);
1925                 return OK;
1926         }
1929 /* Wants: name = value */
1930 static int
1931 option_set_command(int argc, const char *argv[])
1933         if (argc != 3) {
1934                 config_msg = "Wrong number of arguments given to set command";
1935                 return ERR;
1936         }
1938         if (strcmp(argv[1], "=")) {
1939                 config_msg = "No value assigned";
1940                 return ERR;
1941         }
1943         if (!strcmp(argv[0], "show-author"))
1944                 return parse_enum(&opt_author, argv[2], author_map);
1946         if (!strcmp(argv[0], "show-date"))
1947                 return parse_enum(&opt_date, argv[2], date_map);
1949         if (!strcmp(argv[0], "show-rev-graph"))
1950                 return parse_bool(&opt_rev_graph, argv[2]);
1952         if (!strcmp(argv[0], "show-refs"))
1953                 return parse_bool(&opt_show_refs, argv[2]);
1955         if (!strcmp(argv[0], "show-line-numbers"))
1956                 return parse_bool(&opt_line_number, argv[2]);
1958         if (!strcmp(argv[0], "line-graphics"))
1959                 return parse_bool(&opt_line_graphics, argv[2]);
1961         if (!strcmp(argv[0], "line-number-interval"))
1962                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1964         if (!strcmp(argv[0], "author-width"))
1965                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1967         if (!strcmp(argv[0], "horizontal-scroll"))
1968                 return parse_step(&opt_hscroll, argv[2]);
1970         if (!strcmp(argv[0], "split-view-height"))
1971                 return parse_step(&opt_scale_split_view, argv[2]);
1973         if (!strcmp(argv[0], "tab-size"))
1974                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1976         if (!strcmp(argv[0], "commit-encoding"))
1977                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1979         config_msg = "Unknown variable name";
1980         return ERR;
1983 /* Wants: mode request key */
1984 static int
1985 option_bind_command(int argc, const char *argv[])
1987         enum request request;
1988         int keymap = -1;
1989         int key;
1991         if (argc < 3) {
1992                 config_msg = "Wrong number of arguments given to bind command";
1993                 return ERR;
1994         }
1996         if (set_keymap(&keymap, argv[0]) == ERR) {
1997                 config_msg = "Unknown key map";
1998                 return ERR;
1999         }
2001         key = get_key_value(argv[1]);
2002         if (key == ERR) {
2003                 config_msg = "Unknown key";
2004                 return ERR;
2005         }
2007         request = get_request(argv[2]);
2008         if (request == REQ_NONE) {
2009                 static const struct enum_map obsolete[] = {
2010                         ENUM_MAP("cherry-pick",         REQ_NONE),
2011                         ENUM_MAP("screen-resize",       REQ_NONE),
2012                         ENUM_MAP("tree-parent",         REQ_PARENT),
2013                 };
2014                 int alias;
2016                 if (map_enum(&alias, obsolete, argv[2])) {
2017                         if (alias != REQ_NONE)
2018                                 add_keybinding(keymap, alias, key);
2019                         config_msg = "Obsolete request name";
2020                         return ERR;
2021                 }
2022         }
2023         if (request == REQ_NONE && *argv[2]++ == '!')
2024                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2025         if (request == REQ_NONE) {
2026                 config_msg = "Unknown request name";
2027                 return ERR;
2028         }
2030         add_keybinding(keymap, request, key);
2032         return OK;
2035 static int
2036 set_option(const char *opt, char *value)
2038         const char *argv[SIZEOF_ARG];
2039         int argc = 0;
2041         if (!argv_from_string(argv, &argc, value)) {
2042                 config_msg = "Too many option arguments";
2043                 return ERR;
2044         }
2046         if (!strcmp(opt, "color"))
2047                 return option_color_command(argc, argv);
2049         if (!strcmp(opt, "set"))
2050                 return option_set_command(argc, argv);
2052         if (!strcmp(opt, "bind"))
2053                 return option_bind_command(argc, argv);
2055         config_msg = "Unknown option command";
2056         return ERR;
2059 static int
2060 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2062         int status = OK;
2064         config_lineno++;
2065         config_msg = "Internal error";
2067         /* Check for comment markers, since read_properties() will
2068          * only ensure opt and value are split at first " \t". */
2069         optlen = strcspn(opt, "#");
2070         if (optlen == 0)
2071                 return OK;
2073         if (opt[optlen] != 0) {
2074                 config_msg = "No option value";
2075                 status = ERR;
2077         }  else {
2078                 /* Look for comment endings in the value. */
2079                 size_t len = strcspn(value, "#");
2081                 if (len < valuelen) {
2082                         valuelen = len;
2083                         value[valuelen] = 0;
2084                 }
2086                 status = set_option(opt, value);
2087         }
2089         if (status == ERR) {
2090                 warn("Error on line %d, near '%.*s': %s",
2091                      config_lineno, (int) optlen, opt, config_msg);
2092                 config_errors = TRUE;
2093         }
2095         /* Always keep going if errors are encountered. */
2096         return OK;
2099 static void
2100 load_option_file(const char *path)
2102         struct io io = {};
2104         /* It's OK that the file doesn't exist. */
2105         if (!io_open(&io, "%s", path))
2106                 return;
2108         config_lineno = 0;
2109         config_errors = FALSE;
2111         if (io_load(&io, " \t", read_option) == ERR ||
2112             config_errors == TRUE)
2113                 warn("Errors while loading %s.", path);
2116 static int
2117 load_options(void)
2119         const char *home = getenv("HOME");
2120         const char *tigrc_user = getenv("TIGRC_USER");
2121         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2122         char buf[SIZEOF_STR];
2124         add_builtin_run_requests();
2126         if (!tigrc_system)
2127                 tigrc_system = SYSCONFDIR "/tigrc";
2128         load_option_file(tigrc_system);
2130         if (!tigrc_user) {
2131                 if (!home || !string_format(buf, "%s/.tigrc", home))
2132                         return ERR;
2133                 tigrc_user = buf;
2134         }
2135         load_option_file(tigrc_user);
2137         return OK;
2141 /*
2142  * The viewer
2143  */
2145 struct view;
2146 struct view_ops;
2148 /* The display array of active views and the index of the current view. */
2149 static struct view *display[2];
2150 static unsigned int current_view;
2152 #define foreach_displayed_view(view, i) \
2153         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2155 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2157 /* Current head and commit ID */
2158 static char ref_blob[SIZEOF_REF]        = "";
2159 static char ref_commit[SIZEOF_REF]      = "HEAD";
2160 static char ref_head[SIZEOF_REF]        = "HEAD";
2162 struct view {
2163         const char *name;       /* View name */
2164         const char *cmd_env;    /* Command line set via environment */
2165         const char *id;         /* Points to either of ref_{head,commit,blob} */
2167         struct view_ops *ops;   /* View operations */
2169         enum keymap keymap;     /* What keymap does this view have */
2170         bool git_dir;           /* Whether the view requires a git directory. */
2172         char ref[SIZEOF_REF];   /* Hovered commit reference */
2173         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2175         int height, width;      /* The width and height of the main window */
2176         WINDOW *win;            /* The main window */
2177         WINDOW *title;          /* The title window living below the main window */
2179         /* Navigation */
2180         unsigned long offset;   /* Offset of the window top */
2181         unsigned long yoffset;  /* Offset from the window side. */
2182         unsigned long lineno;   /* Current line number */
2183         unsigned long p_offset; /* Previous offset of the window top */
2184         unsigned long p_yoffset;/* Previous offset from the window side */
2185         unsigned long p_lineno; /* Previous current line number */
2186         bool p_restore;         /* Should the previous position be restored. */
2188         /* Searching */
2189         char grep[SIZEOF_STR];  /* Search string */
2190         regex_t *regex;         /* Pre-compiled regexp */
2192         /* If non-NULL, points to the view that opened this view. If this view
2193          * is closed tig will switch back to the parent view. */
2194         struct view *parent;
2196         /* Buffering */
2197         size_t lines;           /* Total number of lines */
2198         struct line *line;      /* Line index */
2199         unsigned int digits;    /* Number of digits in the lines member. */
2201         /* Drawing */
2202         struct line *curline;   /* Line currently being drawn. */
2203         enum line_type curtype; /* Attribute currently used for drawing. */
2204         unsigned long col;      /* Column when drawing. */
2205         bool has_scrolled;      /* View was scrolled. */
2207         /* Loading */
2208         struct io io;
2209         struct io *pipe;
2210         time_t start_time;
2211         time_t update_secs;
2212 };
2214 struct view_ops {
2215         /* What type of content being displayed. Used in the title bar. */
2216         const char *type;
2217         /* Default command arguments. */
2218         const char **argv;
2219         /* Open and reads in all view content. */
2220         bool (*open)(struct view *view);
2221         /* Read one line; updates view->line. */
2222         bool (*read)(struct view *view, char *data);
2223         /* Draw one line; @lineno must be < view->height. */
2224         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2225         /* Depending on view handle a special requests. */
2226         enum request (*request)(struct view *view, enum request request, struct line *line);
2227         /* Search for regexp in a line. */
2228         bool (*grep)(struct view *view, struct line *line);
2229         /* Select line */
2230         void (*select)(struct view *view, struct line *line);
2231         /* Prepare view for loading */
2232         bool (*prepare)(struct view *view);
2233 };
2235 static struct view_ops blame_ops;
2236 static struct view_ops blob_ops;
2237 static struct view_ops diff_ops;
2238 static struct view_ops help_ops;
2239 static struct view_ops log_ops;
2240 static struct view_ops main_ops;
2241 static struct view_ops pager_ops;
2242 static struct view_ops stage_ops;
2243 static struct view_ops status_ops;
2244 static struct view_ops tree_ops;
2245 static struct view_ops branch_ops;
2247 #define VIEW_STR(name, env, ref, ops, map, git) \
2248         { name, #env, ref, ops, map, git }
2250 #define VIEW_(id, name, ops, git, ref) \
2251         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2254 static struct view views[] = {
2255         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2256         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2257         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2258         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2259         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2260         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2261         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2262         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2263         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2264         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2265         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2266 };
2268 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2269 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2271 #define foreach_view(view, i) \
2272         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2274 #define view_is_displayed(view) \
2275         (view == display[0] || view == display[1])
2278 static inline void
2279 set_view_attr(struct view *view, enum line_type type)
2281         if (!view->curline->selected && view->curtype != type) {
2282                 (void) wattrset(view->win, get_line_attr(type));
2283                 wchgat(view->win, -1, 0, type, NULL);
2284                 view->curtype = type;
2285         }
2288 static int
2289 draw_chars(struct view *view, enum line_type type, const char *string,
2290            int max_len, bool use_tilde)
2292         static char out_buffer[BUFSIZ * 2];
2293         int len = 0;
2294         int col = 0;
2295         int trimmed = FALSE;
2296         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2298         if (max_len <= 0)
2299                 return 0;
2301         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2303         set_view_attr(view, type);
2304         if (len > 0) {
2305                 if (opt_iconv_out != ICONV_NONE) {
2306                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2307                         size_t inlen = len + 1;
2309                         char *outbuf = out_buffer;
2310                         size_t outlen = sizeof(out_buffer);
2312                         size_t ret;
2314                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2315                         if (ret != (size_t) -1) {
2316                                 string = out_buffer;
2317                                 len = sizeof(out_buffer) - outlen;
2318                         }
2319                 }
2321                 waddnstr(view->win, string, len);
2322         }
2323         if (trimmed && use_tilde) {
2324                 set_view_attr(view, LINE_DELIMITER);
2325                 waddch(view->win, '~');
2326                 col++;
2327         }
2329         return col;
2332 static int
2333 draw_space(struct view *view, enum line_type type, int max, int spaces)
2335         static char space[] = "                    ";
2336         int col = 0;
2338         spaces = MIN(max, spaces);
2340         while (spaces > 0) {
2341                 int len = MIN(spaces, sizeof(space) - 1);
2343                 col += draw_chars(view, type, space, len, FALSE);
2344                 spaces -= len;
2345         }
2347         return col;
2350 static bool
2351 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2353         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2354         return view->width + view->yoffset <= view->col;
2357 static bool
2358 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2360         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2361         int max = view->width + view->yoffset - view->col;
2362         int i;
2364         if (max < size)
2365                 size = max;
2367         set_view_attr(view, type);
2368         /* Using waddch() instead of waddnstr() ensures that
2369          * they'll be rendered correctly for the cursor line. */
2370         for (i = skip; i < size; i++)
2371                 waddch(view->win, graphic[i]);
2373         view->col += size;
2374         if (size < max && skip <= size)
2375                 waddch(view->win, ' ');
2376         view->col++;
2378         return view->width + view->yoffset <= view->col;
2381 static bool
2382 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2384         int max = MIN(view->width + view->yoffset - view->col, len);
2385         int col;
2387         if (text)
2388                 col = draw_chars(view, type, text, max - 1, trim);
2389         else
2390                 col = draw_space(view, type, max - 1, max - 1);
2392         view->col += col;
2393         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2394         return view->width + view->yoffset <= view->col;
2397 static bool
2398 draw_date(struct view *view, struct time *time)
2400         const char *date = mkdate(time, opt_date);
2401         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2403         return draw_field(view, LINE_DATE, date, cols, FALSE);
2406 static bool
2407 draw_author(struct view *view, const char *author)
2409         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2410         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2412         if (abbreviate && author)
2413                 author = get_author_initials(author);
2415         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2418 static bool
2419 draw_mode(struct view *view, mode_t mode)
2421         const char *str;
2423         if (S_ISDIR(mode))
2424                 str = "drwxr-xr-x";
2425         else if (S_ISLNK(mode))
2426                 str = "lrwxrwxrwx";
2427         else if (S_ISGITLINK(mode))
2428                 str = "m---------";
2429         else if (S_ISREG(mode) && mode & S_IXUSR)
2430                 str = "-rwxr-xr-x";
2431         else if (S_ISREG(mode))
2432                 str = "-rw-r--r--";
2433         else
2434                 str = "----------";
2436         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2439 static bool
2440 draw_lineno(struct view *view, unsigned int lineno)
2442         char number[10];
2443         int digits3 = view->digits < 3 ? 3 : view->digits;
2444         int max = MIN(view->width + view->yoffset - view->col, digits3);
2445         char *text = NULL;
2446         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2448         lineno += view->offset + 1;
2449         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2450                 static char fmt[] = "%1ld";
2452                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2453                 if (string_format(number, fmt, lineno))
2454                         text = number;
2455         }
2456         if (text)
2457                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2458         else
2459                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2460         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2463 static bool
2464 draw_view_line(struct view *view, unsigned int lineno)
2466         struct line *line;
2467         bool selected = (view->offset + lineno == view->lineno);
2469         assert(view_is_displayed(view));
2471         if (view->offset + lineno >= view->lines)
2472                 return FALSE;
2474         line = &view->line[view->offset + lineno];
2476         wmove(view->win, lineno, 0);
2477         if (line->cleareol)
2478                 wclrtoeol(view->win);
2479         view->col = 0;
2480         view->curline = line;
2481         view->curtype = LINE_NONE;
2482         line->selected = FALSE;
2483         line->dirty = line->cleareol = 0;
2485         if (selected) {
2486                 set_view_attr(view, LINE_CURSOR);
2487                 line->selected = TRUE;
2488                 view->ops->select(view, line);
2489         }
2491         return view->ops->draw(view, line, lineno);
2494 static void
2495 redraw_view_dirty(struct view *view)
2497         bool dirty = FALSE;
2498         int lineno;
2500         for (lineno = 0; lineno < view->height; lineno++) {
2501                 if (view->offset + lineno >= view->lines)
2502                         break;
2503                 if (!view->line[view->offset + lineno].dirty)
2504                         continue;
2505                 dirty = TRUE;
2506                 if (!draw_view_line(view, lineno))
2507                         break;
2508         }
2510         if (!dirty)
2511                 return;
2512         wnoutrefresh(view->win);
2515 static void
2516 redraw_view_from(struct view *view, int lineno)
2518         assert(0 <= lineno && lineno < view->height);
2520         for (; lineno < view->height; lineno++) {
2521                 if (!draw_view_line(view, lineno))
2522                         break;
2523         }
2525         wnoutrefresh(view->win);
2528 static void
2529 redraw_view(struct view *view)
2531         werase(view->win);
2532         redraw_view_from(view, 0);
2536 static void
2537 update_view_title(struct view *view)
2539         char buf[SIZEOF_STR];
2540         char state[SIZEOF_STR];
2541         size_t bufpos = 0, statelen = 0;
2543         assert(view_is_displayed(view));
2545         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2546                 unsigned int view_lines = view->offset + view->height;
2547                 unsigned int lines = view->lines
2548                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2549                                    : 0;
2551                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2552                                    view->ops->type,
2553                                    view->lineno + 1,
2554                                    view->lines,
2555                                    lines);
2557         }
2559         if (view->pipe) {
2560                 time_t secs = time(NULL) - view->start_time;
2562                 /* Three git seconds are a long time ... */
2563                 if (secs > 2)
2564                         string_format_from(state, &statelen, " loading %lds", secs);
2565         }
2567         string_format_from(buf, &bufpos, "[%s]", view->name);
2568         if (*view->ref && bufpos < view->width) {
2569                 size_t refsize = strlen(view->ref);
2570                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2572                 if (minsize < view->width)
2573                         refsize = view->width - minsize + 7;
2574                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2575         }
2577         if (statelen && bufpos < view->width) {
2578                 string_format_from(buf, &bufpos, "%s", state);
2579         }
2581         if (view == display[current_view])
2582                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2583         else
2584                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2586         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2587         wclrtoeol(view->title);
2588         wnoutrefresh(view->title);
2591 static int
2592 apply_step(double step, int value)
2594         if (step >= 1)
2595                 return (int) step;
2596         value *= step + 0.01;
2597         return value ? value : 1;
2600 static void
2601 resize_display(void)
2603         int offset, i;
2604         struct view *base = display[0];
2605         struct view *view = display[1] ? display[1] : display[0];
2607         /* Setup window dimensions */
2609         getmaxyx(stdscr, base->height, base->width);
2611         /* Make room for the status window. */
2612         base->height -= 1;
2614         if (view != base) {
2615                 /* Horizontal split. */
2616                 view->width   = base->width;
2617                 view->height  = apply_step(opt_scale_split_view, base->height);
2618                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2619                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2620                 base->height -= view->height;
2622                 /* Make room for the title bar. */
2623                 view->height -= 1;
2624         }
2626         /* Make room for the title bar. */
2627         base->height -= 1;
2629         offset = 0;
2631         foreach_displayed_view (view, i) {
2632                 if (!view->win) {
2633                         view->win = newwin(view->height, 0, offset, 0);
2634                         if (!view->win)
2635                                 die("Failed to create %s view", view->name);
2637                         scrollok(view->win, FALSE);
2639                         view->title = newwin(1, 0, offset + view->height, 0);
2640                         if (!view->title)
2641                                 die("Failed to create title window");
2643                 } else {
2644                         wresize(view->win, view->height, view->width);
2645                         mvwin(view->win,   offset, 0);
2646                         mvwin(view->title, offset + view->height, 0);
2647                 }
2649                 offset += view->height + 1;
2650         }
2653 static void
2654 redraw_display(bool clear)
2656         struct view *view;
2657         int i;
2659         foreach_displayed_view (view, i) {
2660                 if (clear)
2661                         wclear(view->win);
2662                 redraw_view(view);
2663                 update_view_title(view);
2664         }
2667 static void
2668 toggle_enum_option_do(unsigned int *opt, const char *help,
2669                       const struct enum_map *map, size_t size)
2671         *opt = (*opt + 1) % size;
2672         redraw_display(FALSE);
2673         report("Displaying %s %s", enum_name(map[*opt]), help);
2676 #define toggle_enum_option(opt, help, map) \
2677         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2679 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2680 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2682 static void
2683 toggle_view_option(bool *option, const char *help)
2685         *option = !*option;
2686         redraw_display(FALSE);
2687         report("%sabling %s", *option ? "En" : "Dis", help);
2690 static void
2691 open_option_menu(void)
2693         const struct menu_item menu[] = {
2694                 { '.', "line numbers", &opt_line_number },
2695                 { 'D', "date display", &opt_date },
2696                 { 'A', "author display", &opt_author },
2697                 { 'g', "revision graph display", &opt_rev_graph },
2698                 { 'F', "reference display", &opt_show_refs },
2699                 { 0 }
2700         };
2701         int selected = 0;
2703         if (prompt_menu("Toggle option", menu, &selected)) {
2704                 if (menu[selected].data == &opt_date)
2705                         toggle_date();
2706                 else if (menu[selected].data == &opt_author)
2707                         toggle_author();
2708                 else
2709                         toggle_view_option(menu[selected].data, menu[selected].text);
2710         }
2713 static void
2714 maximize_view(struct view *view)
2716         memset(display, 0, sizeof(display));
2717         current_view = 0;
2718         display[current_view] = view;
2719         resize_display();
2720         redraw_display(FALSE);
2721         report("");
2725 /*
2726  * Navigation
2727  */
2729 static bool
2730 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2732         if (lineno >= view->lines)
2733                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2735         if (offset > lineno || offset + view->height <= lineno) {
2736                 unsigned long half = view->height / 2;
2738                 if (lineno > half)
2739                         offset = lineno - half;
2740                 else
2741                         offset = 0;
2742         }
2744         if (offset != view->offset || lineno != view->lineno) {
2745                 view->offset = offset;
2746                 view->lineno = lineno;
2747                 return TRUE;
2748         }
2750         return FALSE;
2753 /* Scrolling backend */
2754 static void
2755 do_scroll_view(struct view *view, int lines)
2757         bool redraw_current_line = FALSE;
2759         /* The rendering expects the new offset. */
2760         view->offset += lines;
2762         assert(0 <= view->offset && view->offset < view->lines);
2763         assert(lines);
2765         /* Move current line into the view. */
2766         if (view->lineno < view->offset) {
2767                 view->lineno = view->offset;
2768                 redraw_current_line = TRUE;
2769         } else if (view->lineno >= view->offset + view->height) {
2770                 view->lineno = view->offset + view->height - 1;
2771                 redraw_current_line = TRUE;
2772         }
2774         assert(view->offset <= view->lineno && view->lineno < view->lines);
2776         /* Redraw the whole screen if scrolling is pointless. */
2777         if (view->height < ABS(lines)) {
2778                 redraw_view(view);
2780         } else {
2781                 int line = lines > 0 ? view->height - lines : 0;
2782                 int end = line + ABS(lines);
2784                 scrollok(view->win, TRUE);
2785                 wscrl(view->win, lines);
2786                 scrollok(view->win, FALSE);
2788                 while (line < end && draw_view_line(view, line))
2789                         line++;
2791                 if (redraw_current_line)
2792                         draw_view_line(view, view->lineno - view->offset);
2793                 wnoutrefresh(view->win);
2794         }
2796         view->has_scrolled = TRUE;
2797         report("");
2800 /* Scroll frontend */
2801 static void
2802 scroll_view(struct view *view, enum request request)
2804         int lines = 1;
2806         assert(view_is_displayed(view));
2808         switch (request) {
2809         case REQ_SCROLL_LEFT:
2810                 if (view->yoffset == 0) {
2811                         report("Cannot scroll beyond the first column");
2812                         return;
2813                 }
2814                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2815                         view->yoffset = 0;
2816                 else
2817                         view->yoffset -= apply_step(opt_hscroll, view->width);
2818                 redraw_view_from(view, 0);
2819                 report("");
2820                 return;
2821         case REQ_SCROLL_RIGHT:
2822                 view->yoffset += apply_step(opt_hscroll, view->width);
2823                 redraw_view(view);
2824                 report("");
2825                 return;
2826         case REQ_SCROLL_PAGE_DOWN:
2827                 lines = view->height;
2828         case REQ_SCROLL_LINE_DOWN:
2829                 if (view->offset + lines > view->lines)
2830                         lines = view->lines - view->offset;
2832                 if (lines == 0 || view->offset + view->height >= view->lines) {
2833                         report("Cannot scroll beyond the last line");
2834                         return;
2835                 }
2836                 break;
2838         case REQ_SCROLL_PAGE_UP:
2839                 lines = view->height;
2840         case REQ_SCROLL_LINE_UP:
2841                 if (lines > view->offset)
2842                         lines = view->offset;
2844                 if (lines == 0) {
2845                         report("Cannot scroll beyond the first line");
2846                         return;
2847                 }
2849                 lines = -lines;
2850                 break;
2852         default:
2853                 die("request %d not handled in switch", request);
2854         }
2856         do_scroll_view(view, lines);
2859 /* Cursor moving */
2860 static void
2861 move_view(struct view *view, enum request request)
2863         int scroll_steps = 0;
2864         int steps;
2866         switch (request) {
2867         case REQ_MOVE_FIRST_LINE:
2868                 steps = -view->lineno;
2869                 break;
2871         case REQ_MOVE_LAST_LINE:
2872                 steps = view->lines - view->lineno - 1;
2873                 break;
2875         case REQ_MOVE_PAGE_UP:
2876                 steps = view->height > view->lineno
2877                       ? -view->lineno : -view->height;
2878                 break;
2880         case REQ_MOVE_PAGE_DOWN:
2881                 steps = view->lineno + view->height >= view->lines
2882                       ? view->lines - view->lineno - 1 : view->height;
2883                 break;
2885         case REQ_MOVE_UP:
2886                 steps = -1;
2887                 break;
2889         case REQ_MOVE_DOWN:
2890                 steps = 1;
2891                 break;
2893         default:
2894                 die("request %d not handled in switch", request);
2895         }
2897         if (steps <= 0 && view->lineno == 0) {
2898                 report("Cannot move beyond the first line");
2899                 return;
2901         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2902                 report("Cannot move beyond the last line");
2903                 return;
2904         }
2906         /* Move the current line */
2907         view->lineno += steps;
2908         assert(0 <= view->lineno && view->lineno < view->lines);
2910         /* Check whether the view needs to be scrolled */
2911         if (view->lineno < view->offset ||
2912             view->lineno >= view->offset + view->height) {
2913                 scroll_steps = steps;
2914                 if (steps < 0 && -steps > view->offset) {
2915                         scroll_steps = -view->offset;
2917                 } else if (steps > 0) {
2918                         if (view->lineno == view->lines - 1 &&
2919                             view->lines > view->height) {
2920                                 scroll_steps = view->lines - view->offset - 1;
2921                                 if (scroll_steps >= view->height)
2922                                         scroll_steps -= view->height - 1;
2923                         }
2924                 }
2925         }
2927         if (!view_is_displayed(view)) {
2928                 view->offset += scroll_steps;
2929                 assert(0 <= view->offset && view->offset < view->lines);
2930                 view->ops->select(view, &view->line[view->lineno]);
2931                 return;
2932         }
2934         /* Repaint the old "current" line if we be scrolling */
2935         if (ABS(steps) < view->height)
2936                 draw_view_line(view, view->lineno - steps - view->offset);
2938         if (scroll_steps) {
2939                 do_scroll_view(view, scroll_steps);
2940                 return;
2941         }
2943         /* Draw the current line */
2944         draw_view_line(view, view->lineno - view->offset);
2946         wnoutrefresh(view->win);
2947         report("");
2951 /*
2952  * Searching
2953  */
2955 static void search_view(struct view *view, enum request request);
2957 static bool
2958 grep_text(struct view *view, const char *text[])
2960         regmatch_t pmatch;
2961         size_t i;
2963         for (i = 0; text[i]; i++)
2964                 if (*text[i] &&
2965                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2966                         return TRUE;
2967         return FALSE;
2970 static void
2971 select_view_line(struct view *view, unsigned long lineno)
2973         unsigned long old_lineno = view->lineno;
2974         unsigned long old_offset = view->offset;
2976         if (goto_view_line(view, view->offset, lineno)) {
2977                 if (view_is_displayed(view)) {
2978                         if (old_offset != view->offset) {
2979                                 redraw_view(view);
2980                         } else {
2981                                 draw_view_line(view, old_lineno - view->offset);
2982                                 draw_view_line(view, view->lineno - view->offset);
2983                                 wnoutrefresh(view->win);
2984                         }
2985                 } else {
2986                         view->ops->select(view, &view->line[view->lineno]);
2987                 }
2988         }
2991 static void
2992 find_next(struct view *view, enum request request)
2994         unsigned long lineno = view->lineno;
2995         int direction;
2997         if (!*view->grep) {
2998                 if (!*opt_search)
2999                         report("No previous search");
3000                 else
3001                         search_view(view, request);
3002                 return;
3003         }
3005         switch (request) {
3006         case REQ_SEARCH:
3007         case REQ_FIND_NEXT:
3008                 direction = 1;
3009                 break;
3011         case REQ_SEARCH_BACK:
3012         case REQ_FIND_PREV:
3013                 direction = -1;
3014                 break;
3016         default:
3017                 return;
3018         }
3020         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3021                 lineno += direction;
3023         /* Note, lineno is unsigned long so will wrap around in which case it
3024          * will become bigger than view->lines. */
3025         for (; lineno < view->lines; lineno += direction) {
3026                 if (view->ops->grep(view, &view->line[lineno])) {
3027                         select_view_line(view, lineno);
3028                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3029                         return;
3030                 }
3031         }
3033         report("No match found for '%s'", view->grep);
3036 static void
3037 search_view(struct view *view, enum request request)
3039         int regex_err;
3041         if (view->regex) {
3042                 regfree(view->regex);
3043                 *view->grep = 0;
3044         } else {
3045                 view->regex = calloc(1, sizeof(*view->regex));
3046                 if (!view->regex)
3047                         return;
3048         }
3050         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3051         if (regex_err != 0) {
3052                 char buf[SIZEOF_STR] = "unknown error";
3054                 regerror(regex_err, view->regex, buf, sizeof(buf));
3055                 report("Search failed: %s", buf);
3056                 return;
3057         }
3059         string_copy(view->grep, opt_search);
3061         find_next(view, request);
3064 /*
3065  * Incremental updating
3066  */
3068 static void
3069 reset_view(struct view *view)
3071         int i;
3073         for (i = 0; i < view->lines; i++)
3074                 free(view->line[i].data);
3075         free(view->line);
3077         view->p_offset = view->offset;
3078         view->p_yoffset = view->yoffset;
3079         view->p_lineno = view->lineno;
3081         view->line = NULL;
3082         view->offset = 0;
3083         view->yoffset = 0;
3084         view->lines  = 0;
3085         view->lineno = 0;
3086         view->vid[0] = 0;
3087         view->update_secs = 0;
3090 static void
3091 free_argv(const char *argv[])
3093         int argc;
3095         for (argc = 0; argv[argc]; argc++)
3096                 free((void *) argv[argc]);
3099 static const char *
3100 format_arg(const char *name)
3102         static struct {
3103                 const char *name;
3104                 size_t namelen;
3105                 const char *value;
3106                 const char *value_if_empty;
3107         } vars[] = {
3108 #define FORMAT_VAR(name, value, value_if_empty) \
3109         { name, STRING_SIZE(name), value, value_if_empty }
3110                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3111                 FORMAT_VAR("%(file)",           opt_file,       ""),
3112                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3113                 FORMAT_VAR("%(head)",           ref_head,       ""),
3114                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3115                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3116         };
3117         int i;
3119         for (i = 0; i < ARRAY_SIZE(vars); i++)
3120                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3121                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3123         return NULL;
3126 static bool
3127 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3129         char buf[SIZEOF_STR];
3130         int argc;
3131         bool noreplace = flags == FORMAT_NONE;
3133         free_argv(dst_argv);
3135         for (argc = 0; src_argv[argc]; argc++) {
3136                 const char *arg = src_argv[argc];
3137                 size_t bufpos = 0;
3139                 while (arg) {
3140                         char *next = strstr(arg, "%(");
3141                         int len = next - arg;
3142                         const char *value;
3144                         if (!next || noreplace) {
3145                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
3146                                         noreplace = TRUE;
3147                                 len = strlen(arg);
3148                                 value = "";
3150                         } else {
3151                                 value = format_arg(next);
3153                                 if (!value) {
3154                                         report("Unknown replacement: `%s`", next);
3155                                         return FALSE;
3156                                 }
3157                         }
3159                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3160                                 return FALSE;
3162                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3163                 }
3165                 dst_argv[argc] = strdup(buf);
3166                 if (!dst_argv[argc])
3167                         break;
3168         }
3170         dst_argv[argc] = NULL;
3172         return src_argv[argc] == NULL;
3175 static bool
3176 restore_view_position(struct view *view)
3178         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3179                 return FALSE;
3181         /* Changing the view position cancels the restoring. */
3182         /* FIXME: Changing back to the first line is not detected. */
3183         if (view->offset != 0 || view->lineno != 0) {
3184                 view->p_restore = FALSE;
3185                 return FALSE;
3186         }
3188         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3189             view_is_displayed(view))
3190                 werase(view->win);
3192         view->yoffset = view->p_yoffset;
3193         view->p_restore = FALSE;
3195         return TRUE;
3198 static void
3199 end_update(struct view *view, bool force)
3201         if (!view->pipe)
3202                 return;
3203         while (!view->ops->read(view, NULL))
3204                 if (!force)
3205                         return;
3206         if (force)
3207                 io_kill(view->pipe);
3208         io_done(view->pipe);
3209         view->pipe = NULL;
3212 static void
3213 setup_update(struct view *view, const char *vid)
3215         reset_view(view);
3216         string_copy_rev(view->vid, vid);
3217         view->pipe = &view->io;
3218         view->start_time = time(NULL);
3221 static bool
3222 prepare_update(struct view *view, const char *argv[], const char *dir,
3223                enum format_flags flags)
3225         if (view->pipe)
3226                 end_update(view, TRUE);
3227         return io_format(&view->io, dir, IO_RD, argv, flags);
3230 static bool
3231 prepare_update_file(struct view *view, const char *name)
3233         if (view->pipe)
3234                 end_update(view, TRUE);
3235         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3238 static bool
3239 begin_update(struct view *view, bool refresh)
3241         if (view->pipe)
3242                 end_update(view, TRUE);
3244         if (!refresh) {
3245                 if (view->ops->prepare) {
3246                         if (!view->ops->prepare(view))
3247                                 return FALSE;
3248                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3249                         return FALSE;
3250                 }
3252                 /* Put the current ref_* value to the view title ref
3253                  * member. This is needed by the blob view. Most other
3254                  * views sets it automatically after loading because the
3255                  * first line is a commit line. */
3256                 string_copy_rev(view->ref, view->id);
3257         }
3259         if (!io_start(&view->io))
3260                 return FALSE;
3262         setup_update(view, view->id);
3264         return TRUE;
3267 static bool
3268 update_view(struct view *view)
3270         char out_buffer[BUFSIZ * 2];
3271         char *line;
3272         /* Clear the view and redraw everything since the tree sorting
3273          * might have rearranged things. */
3274         bool redraw = view->lines == 0;
3275         bool can_read = TRUE;
3277         if (!view->pipe)
3278                 return TRUE;
3280         if (!io_can_read(view->pipe)) {
3281                 if (view->lines == 0 && view_is_displayed(view)) {
3282                         time_t secs = time(NULL) - view->start_time;
3284                         if (secs > 1 && secs > view->update_secs) {
3285                                 if (view->update_secs == 0)
3286                                         redraw_view(view);
3287                                 update_view_title(view);
3288                                 view->update_secs = secs;
3289                         }
3290                 }
3291                 return TRUE;
3292         }
3294         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3295                 if (opt_iconv_in != ICONV_NONE) {
3296                         ICONV_CONST char *inbuf = line;
3297                         size_t inlen = strlen(line) + 1;
3299                         char *outbuf = out_buffer;
3300                         size_t outlen = sizeof(out_buffer);
3302                         size_t ret;
3304                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3305                         if (ret != (size_t) -1)
3306                                 line = out_buffer;
3307                 }
3309                 if (!view->ops->read(view, line)) {
3310                         report("Allocation failure");
3311                         end_update(view, TRUE);
3312                         return FALSE;
3313                 }
3314         }
3316         {
3317                 unsigned long lines = view->lines;
3318                 int digits;
3320                 for (digits = 0; lines; digits++)
3321                         lines /= 10;
3323                 /* Keep the displayed view in sync with line number scaling. */
3324                 if (digits != view->digits) {
3325                         view->digits = digits;
3326                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3327                                 redraw = TRUE;
3328                 }
3329         }
3331         if (io_error(view->pipe)) {
3332                 report("Failed to read: %s", io_strerror(view->pipe));
3333                 end_update(view, TRUE);
3335         } else if (io_eof(view->pipe)) {
3336                 report("");
3337                 end_update(view, FALSE);
3338         }
3340         if (restore_view_position(view))
3341                 redraw = TRUE;
3343         if (!view_is_displayed(view))
3344                 return TRUE;
3346         if (redraw)
3347                 redraw_view_from(view, 0);
3348         else
3349                 redraw_view_dirty(view);
3351         /* Update the title _after_ the redraw so that if the redraw picks up a
3352          * commit reference in view->ref it'll be available here. */
3353         update_view_title(view);
3354         return TRUE;
3357 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3359 static struct line *
3360 add_line_data(struct view *view, void *data, enum line_type type)
3362         struct line *line;
3364         if (!realloc_lines(&view->line, view->lines, 1))
3365                 return NULL;
3367         line = &view->line[view->lines++];
3368         memset(line, 0, sizeof(*line));
3369         line->type = type;
3370         line->data = data;
3371         line->dirty = 1;
3373         return line;
3376 static struct line *
3377 add_line_text(struct view *view, const char *text, enum line_type type)
3379         char *data = text ? strdup(text) : NULL;
3381         return data ? add_line_data(view, data, type) : NULL;
3384 static struct line *
3385 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3387         char buf[SIZEOF_STR];
3388         va_list args;
3390         va_start(args, fmt);
3391         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3392                 buf[0] = 0;
3393         va_end(args);
3395         return buf[0] ? add_line_text(view, buf, type) : NULL;
3398 /*
3399  * View opening
3400  */
3402 enum open_flags {
3403         OPEN_DEFAULT = 0,       /* Use default view switching. */
3404         OPEN_SPLIT = 1,         /* Split current view. */
3405         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3406         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3407         OPEN_PREPARED = 32,     /* Open already prepared command. */
3408 };
3410 static void
3411 open_view(struct view *prev, enum request request, enum open_flags flags)
3413         bool split = !!(flags & OPEN_SPLIT);
3414         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3415         bool nomaximize = !!(flags & OPEN_REFRESH);
3416         struct view *view = VIEW(request);
3417         int nviews = displayed_views();
3418         struct view *base_view = display[0];
3420         if (view == prev && nviews == 1 && !reload) {
3421                 report("Already in %s view", view->name);
3422                 return;
3423         }
3425         if (view->git_dir && !opt_git_dir[0]) {
3426                 report("The %s view is disabled in pager view", view->name);
3427                 return;
3428         }
3430         if (split) {
3431                 display[1] = view;
3432                 current_view = 1;
3433         } else if (!nomaximize) {
3434                 /* Maximize the current view. */
3435                 memset(display, 0, sizeof(display));
3436                 current_view = 0;
3437                 display[current_view] = view;
3438         }
3440         /* No parent signals that this is the first loaded view. */
3441         if (prev && view != prev) {
3442                 view->parent = prev;
3443         }
3445         /* Resize the view when switching between split- and full-screen,
3446          * or when switching between two different full-screen views. */
3447         if (nviews != displayed_views() ||
3448             (nviews == 1 && base_view != display[0]))
3449                 resize_display();
3451         if (view->ops->open) {
3452                 if (view->pipe)
3453                         end_update(view, TRUE);
3454                 if (!view->ops->open(view)) {
3455                         report("Failed to load %s view", view->name);
3456                         return;
3457                 }
3458                 restore_view_position(view);
3460         } else if ((reload || strcmp(view->vid, view->id)) &&
3461                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3462                 report("Failed to load %s view", view->name);
3463                 return;
3464         }
3466         if (split && prev->lineno - prev->offset >= prev->height) {
3467                 /* Take the title line into account. */
3468                 int lines = prev->lineno - prev->offset - prev->height + 1;
3470                 /* Scroll the view that was split if the current line is
3471                  * outside the new limited view. */
3472                 do_scroll_view(prev, lines);
3473         }
3475         if (prev && view != prev && split && view_is_displayed(prev)) {
3476                 /* "Blur" the previous view. */
3477                 update_view_title(prev);
3478         }
3480         if (view->pipe && view->lines == 0) {
3481                 /* Clear the old view and let the incremental updating refill
3482                  * the screen. */
3483                 werase(view->win);
3484                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3485                 report("");
3486         } else if (view_is_displayed(view)) {
3487                 redraw_view(view);
3488                 report("");
3489         }
3492 static void
3493 open_external_viewer(const char *argv[], const char *dir)
3495         def_prog_mode();           /* save current tty modes */
3496         endwin();                  /* restore original tty modes */
3497         io_run_fg(argv, dir);
3498         fprintf(stderr, "Press Enter to continue");
3499         getc(opt_tty);
3500         reset_prog_mode();
3501         redraw_display(TRUE);
3504 static void
3505 open_mergetool(const char *file)
3507         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3509         open_external_viewer(mergetool_argv, opt_cdup);
3512 static void
3513 open_editor(const char *file)
3515         const char *editor_argv[] = { "vi", file, NULL };
3516         const char *editor;
3518         editor = getenv("GIT_EDITOR");
3519         if (!editor && *opt_editor)
3520                 editor = opt_editor;
3521         if (!editor)
3522                 editor = getenv("VISUAL");
3523         if (!editor)
3524                 editor = getenv("EDITOR");
3525         if (!editor)
3526                 editor = "vi";
3528         editor_argv[0] = editor;
3529         open_external_viewer(editor_argv, opt_cdup);
3532 static void
3533 open_run_request(enum request request)
3535         struct run_request *req = get_run_request(request);
3536         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3538         if (!req) {
3539                 report("Unknown run request");
3540                 return;
3541         }
3543         if (format_argv(argv, req->argv, FORMAT_ALL))
3544                 open_external_viewer(argv, NULL);
3545         free_argv(argv);
3548 /*
3549  * User request switch noodle
3550  */
3552 static int
3553 view_driver(struct view *view, enum request request)
3555         int i;
3557         if (request == REQ_NONE)
3558                 return TRUE;
3560         if (request > REQ_NONE) {
3561                 open_run_request(request);
3562                 /* FIXME: When all views can refresh always do this. */
3563                 if (view == VIEW(REQ_VIEW_STATUS) ||
3564                     view == VIEW(REQ_VIEW_MAIN) ||
3565                     view == VIEW(REQ_VIEW_LOG) ||
3566                     view == VIEW(REQ_VIEW_BRANCH) ||
3567                     view == VIEW(REQ_VIEW_STAGE))
3568                         request = REQ_REFRESH;
3569                 else
3570                         return TRUE;
3571         }
3573         if (view && view->lines) {
3574                 request = view->ops->request(view, request, &view->line[view->lineno]);
3575                 if (request == REQ_NONE)
3576                         return TRUE;
3577         }
3579         switch (request) {
3580         case REQ_MOVE_UP:
3581         case REQ_MOVE_DOWN:
3582         case REQ_MOVE_PAGE_UP:
3583         case REQ_MOVE_PAGE_DOWN:
3584         case REQ_MOVE_FIRST_LINE:
3585         case REQ_MOVE_LAST_LINE:
3586                 move_view(view, request);
3587                 break;
3589         case REQ_SCROLL_LEFT:
3590         case REQ_SCROLL_RIGHT:
3591         case REQ_SCROLL_LINE_DOWN:
3592         case REQ_SCROLL_LINE_UP:
3593         case REQ_SCROLL_PAGE_DOWN:
3594         case REQ_SCROLL_PAGE_UP:
3595                 scroll_view(view, request);
3596                 break;
3598         case REQ_VIEW_BLAME:
3599                 if (!opt_file[0]) {
3600                         report("No file chosen, press %s to open tree view",
3601                                get_key(view->keymap, REQ_VIEW_TREE));
3602                         break;
3603                 }
3604                 open_view(view, request, OPEN_DEFAULT);
3605                 break;
3607         case REQ_VIEW_BLOB:
3608                 if (!ref_blob[0]) {
3609                         report("No file chosen, press %s to open tree view",
3610                                get_key(view->keymap, REQ_VIEW_TREE));
3611                         break;
3612                 }
3613                 open_view(view, request, OPEN_DEFAULT);
3614                 break;
3616         case REQ_VIEW_PAGER:
3617                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3618                         report("No pager content, press %s to run command from prompt",
3619                                get_key(view->keymap, REQ_PROMPT));
3620                         break;
3621                 }
3622                 open_view(view, request, OPEN_DEFAULT);
3623                 break;
3625         case REQ_VIEW_STAGE:
3626                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3627                         report("No stage content, press %s to open the status view and choose file",
3628                                get_key(view->keymap, REQ_VIEW_STATUS));
3629                         break;
3630                 }
3631                 open_view(view, request, OPEN_DEFAULT);
3632                 break;
3634         case REQ_VIEW_STATUS:
3635                 if (opt_is_inside_work_tree == FALSE) {
3636                         report("The status view requires a working tree");
3637                         break;
3638                 }
3639                 open_view(view, request, OPEN_DEFAULT);
3640                 break;
3642         case REQ_VIEW_MAIN:
3643         case REQ_VIEW_DIFF:
3644         case REQ_VIEW_LOG:
3645         case REQ_VIEW_TREE:
3646         case REQ_VIEW_HELP:
3647         case REQ_VIEW_BRANCH:
3648                 open_view(view, request, OPEN_DEFAULT);
3649                 break;
3651         case REQ_NEXT:
3652         case REQ_PREVIOUS:
3653                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3655                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3656                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3657                    (view == VIEW(REQ_VIEW_DIFF) &&
3658                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3659                    (view == VIEW(REQ_VIEW_STAGE) &&
3660                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3661                    (view == VIEW(REQ_VIEW_BLOB) &&
3662                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3663                    (view == VIEW(REQ_VIEW_MAIN) &&
3664                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3665                         int line;
3667                         view = view->parent;
3668                         line = view->lineno;
3669                         move_view(view, request);
3670                         if (view_is_displayed(view))
3671                                 update_view_title(view);
3672                         if (line != view->lineno)
3673                                 view->ops->request(view, REQ_ENTER,
3674                                                    &view->line[view->lineno]);
3676                 } else {
3677                         move_view(view, request);
3678                 }
3679                 break;
3681         case REQ_VIEW_NEXT:
3682         {
3683                 int nviews = displayed_views();
3684                 int next_view = (current_view + 1) % nviews;
3686                 if (next_view == current_view) {
3687                         report("Only one view is displayed");
3688                         break;
3689                 }
3691                 current_view = next_view;
3692                 /* Blur out the title of the previous view. */
3693                 update_view_title(view);
3694                 report("");
3695                 break;
3696         }
3697         case REQ_REFRESH:
3698                 report("Refreshing is not yet supported for the %s view", view->name);
3699                 break;
3701         case REQ_MAXIMIZE:
3702                 if (displayed_views() == 2)
3703                         maximize_view(view);
3704                 break;
3706         case REQ_OPTIONS:
3707                 open_option_menu();
3708                 break;
3710         case REQ_TOGGLE_LINENO:
3711                 toggle_view_option(&opt_line_number, "line numbers");
3712                 break;
3714         case REQ_TOGGLE_DATE:
3715                 toggle_date();
3716                 break;
3718         case REQ_TOGGLE_AUTHOR:
3719                 toggle_author();
3720                 break;
3722         case REQ_TOGGLE_REV_GRAPH:
3723                 toggle_view_option(&opt_rev_graph, "revision graph display");
3724                 break;
3726         case REQ_TOGGLE_REFS:
3727                 toggle_view_option(&opt_show_refs, "reference display");
3728                 break;
3730         case REQ_TOGGLE_SORT_FIELD:
3731         case REQ_TOGGLE_SORT_ORDER:
3732                 report("Sorting is not yet supported for the %s view", view->name);
3733                 break;
3735         case REQ_SEARCH:
3736         case REQ_SEARCH_BACK:
3737                 search_view(view, request);
3738                 break;
3740         case REQ_FIND_NEXT:
3741         case REQ_FIND_PREV:
3742                 find_next(view, request);
3743                 break;
3745         case REQ_STOP_LOADING:
3746                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3747                         view = &views[i];
3748                         if (view->pipe)
3749                                 report("Stopped loading the %s view", view->name),
3750                         end_update(view, TRUE);
3751                 }
3752                 break;
3754         case REQ_SHOW_VERSION:
3755                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3756                 return TRUE;
3758         case REQ_SCREEN_REDRAW:
3759                 redraw_display(TRUE);
3760                 break;
3762         case REQ_EDIT:
3763                 report("Nothing to edit");
3764                 break;
3766         case REQ_ENTER:
3767                 report("Nothing to enter");
3768                 break;
3770         case REQ_VIEW_CLOSE:
3771                 /* XXX: Mark closed views by letting view->parent point to the
3772                  * view itself. Parents to closed view should never be
3773                  * followed. */
3774                 if (view->parent &&
3775                     view->parent->parent != view->parent) {
3776                         maximize_view(view->parent);
3777                         view->parent = view;
3778                         break;
3779                 }
3780                 /* Fall-through */
3781         case REQ_QUIT:
3782                 return FALSE;
3784         default:
3785                 report("Unknown key, press %s for help",
3786                        get_key(view->keymap, REQ_VIEW_HELP));
3787                 return TRUE;
3788         }
3790         return TRUE;
3794 /*
3795  * View backend utilities
3796  */
3798 enum sort_field {
3799         ORDERBY_NAME,
3800         ORDERBY_DATE,
3801         ORDERBY_AUTHOR,
3802 };
3804 struct sort_state {
3805         const enum sort_field *fields;
3806         size_t size, current;
3807         bool reverse;
3808 };
3810 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3811 #define get_sort_field(state) ((state).fields[(state).current])
3812 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3814 static void
3815 sort_view(struct view *view, enum request request, struct sort_state *state,
3816           int (*compare)(const void *, const void *))
3818         switch (request) {
3819         case REQ_TOGGLE_SORT_FIELD:
3820                 state->current = (state->current + 1) % state->size;
3821                 break;
3823         case REQ_TOGGLE_SORT_ORDER:
3824                 state->reverse = !state->reverse;
3825                 break;
3826         default:
3827                 die("Not a sort request");
3828         }
3830         qsort(view->line, view->lines, sizeof(*view->line), compare);
3831         redraw_view(view);
3834 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3836 /* Small author cache to reduce memory consumption. It uses binary
3837  * search to lookup or find place to position new entries. No entries
3838  * are ever freed. */
3839 static const char *
3840 get_author(const char *name)
3842         static const char **authors;
3843         static size_t authors_size;
3844         int from = 0, to = authors_size - 1;
3846         while (from <= to) {
3847                 size_t pos = (to + from) / 2;
3848                 int cmp = strcmp(name, authors[pos]);
3850                 if (!cmp)
3851                         return authors[pos];
3853                 if (cmp < 0)
3854                         to = pos - 1;
3855                 else
3856                         from = pos + 1;
3857         }
3859         if (!realloc_authors(&authors, authors_size, 1))
3860                 return NULL;
3861         name = strdup(name);
3862         if (!name)
3863                 return NULL;
3865         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3866         authors[from] = name;
3867         authors_size++;
3869         return name;
3872 static void
3873 parse_timesec(struct time *time, const char *sec)
3875         time->sec = (time_t) atol(sec);
3878 static void
3879 parse_timezone(struct time *time, const char *zone)
3881         long tz;
3883         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3884         tz += ('0' - zone[2]) * 60 * 60;
3885         tz += ('0' - zone[3]) * 60;
3886         tz += ('0' - zone[4]);
3888         if (zone[0] == '-')
3889                 tz = -tz;
3891         time->tz = tz;
3892         time->sec -= tz;
3895 /* Parse author lines where the name may be empty:
3896  *      author  <email@address.tld> 1138474660 +0100
3897  */
3898 static void
3899 parse_author_line(char *ident, const char **author, struct time *time)
3901         char *nameend = strchr(ident, '<');
3902         char *emailend = strchr(ident, '>');
3904         if (nameend && emailend)
3905                 *nameend = *emailend = 0;
3906         ident = chomp_string(ident);
3907         if (!*ident) {
3908                 if (nameend)
3909                         ident = chomp_string(nameend + 1);
3910                 if (!*ident)
3911                         ident = "Unknown";
3912         }
3914         *author = get_author(ident);
3916         /* Parse epoch and timezone */
3917         if (emailend && emailend[1] == ' ') {
3918                 char *secs = emailend + 2;
3919                 char *zone = strchr(secs, ' ');
3921                 parse_timesec(time, secs);
3923                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3924                         parse_timezone(time, zone + 1);
3925         }
3928 static bool
3929 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3931         char rev[SIZEOF_REV];
3932         const char *revlist_argv[] = {
3933                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3934         };
3935         struct menu_item *items;
3936         char text[SIZEOF_STR];
3937         bool ok = TRUE;
3938         int i;
3940         items = calloc(*parents + 1, sizeof(*items));
3941         if (!items)
3942                 return FALSE;
3944         for (i = 0; i < *parents; i++) {
3945                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3946                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3947                     !(items[i].text = strdup(text))) {
3948                         ok = FALSE;
3949                         break;
3950                 }
3951         }
3953         if (ok) {
3954                 *parents = 0;
3955                 ok = prompt_menu("Select parent", items, parents);
3956         }
3957         for (i = 0; items[i].text; i++)
3958                 free((char *) items[i].text);
3959         free(items);
3960         return ok;
3963 static bool
3964 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3966         char buf[SIZEOF_STR * 4];
3967         const char *revlist_argv[] = {
3968                 "git", "log", "--no-color", "-1",
3969                         "--pretty=format:%P", id, "--", path, NULL
3970         };
3971         int parents;
3973         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3974             (parents = strlen(buf) / 40) < 0) {
3975                 report("Failed to get parent information");
3976                 return FALSE;
3978         } else if (parents == 0) {
3979                 if (path)
3980                         report("Path '%s' does not exist in the parent", path);
3981                 else
3982                         report("The selected commit has no parents");
3983                 return FALSE;
3984         }
3986         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3987                 return FALSE;
3989         string_copy_rev(rev, &buf[41 * parents]);
3990         return TRUE;
3993 /*
3994  * Pager backend
3995  */
3997 static bool
3998 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4000         char text[SIZEOF_STR];
4002         if (opt_line_number && draw_lineno(view, lineno))
4003                 return TRUE;
4005         string_expand(text, sizeof(text), line->data, opt_tab_size);
4006         draw_text(view, line->type, text, TRUE);
4007         return TRUE;
4010 static bool
4011 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4013         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4014         char ref[SIZEOF_STR];
4016         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4017                 return TRUE;
4019         /* This is the only fatal call, since it can "corrupt" the buffer. */
4020         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4021                 return FALSE;
4023         return TRUE;
4026 static void
4027 add_pager_refs(struct view *view, struct line *line)
4029         char buf[SIZEOF_STR];
4030         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4031         struct ref_list *list;
4032         size_t bufpos = 0, i;
4033         const char *sep = "Refs: ";
4034         bool is_tag = FALSE;
4036         assert(line->type == LINE_COMMIT);
4038         list = get_ref_list(commit_id);
4039         if (!list) {
4040                 if (view == VIEW(REQ_VIEW_DIFF))
4041                         goto try_add_describe_ref;
4042                 return;
4043         }
4045         for (i = 0; i < list->size; i++) {
4046                 struct ref *ref = list->refs[i];
4047                 const char *fmt = ref->tag    ? "%s[%s]" :
4048                                   ref->remote ? "%s<%s>" : "%s%s";
4050                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4051                         return;
4052                 sep = ", ";
4053                 if (ref->tag)
4054                         is_tag = TRUE;
4055         }
4057         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4058 try_add_describe_ref:
4059                 /* Add <tag>-g<commit_id> "fake" reference. */
4060                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4061                         return;
4062         }
4064         if (bufpos == 0)
4065                 return;
4067         add_line_text(view, buf, LINE_PP_REFS);
4070 static bool
4071 pager_read(struct view *view, char *data)
4073         struct line *line;
4075         if (!data)
4076                 return TRUE;
4078         line = add_line_text(view, data, get_line_type(data));
4079         if (!line)
4080                 return FALSE;
4082         if (line->type == LINE_COMMIT &&
4083             (view == VIEW(REQ_VIEW_DIFF) ||
4084              view == VIEW(REQ_VIEW_LOG)))
4085                 add_pager_refs(view, line);
4087         return TRUE;
4090 static enum request
4091 pager_request(struct view *view, enum request request, struct line *line)
4093         int split = 0;
4095         if (request != REQ_ENTER)
4096                 return request;
4098         if (line->type == LINE_COMMIT &&
4099            (view == VIEW(REQ_VIEW_LOG) ||
4100             view == VIEW(REQ_VIEW_PAGER))) {
4101                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4102                 split = 1;
4103         }
4105         /* Always scroll the view even if it was split. That way
4106          * you can use Enter to scroll through the log view and
4107          * split open each commit diff. */
4108         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4110         /* FIXME: A minor workaround. Scrolling the view will call report("")
4111          * but if we are scrolling a non-current view this won't properly
4112          * update the view title. */
4113         if (split)
4114                 update_view_title(view);
4116         return REQ_NONE;
4119 static bool
4120 pager_grep(struct view *view, struct line *line)
4122         const char *text[] = { line->data, NULL };
4124         return grep_text(view, text);
4127 static void
4128 pager_select(struct view *view, struct line *line)
4130         if (line->type == LINE_COMMIT) {
4131                 char *text = (char *)line->data + STRING_SIZE("commit ");
4133                 if (view != VIEW(REQ_VIEW_PAGER))
4134                         string_copy_rev(view->ref, text);
4135                 string_copy_rev(ref_commit, text);
4136         }
4139 static struct view_ops pager_ops = {
4140         "line",
4141         NULL,
4142         NULL,
4143         pager_read,
4144         pager_draw,
4145         pager_request,
4146         pager_grep,
4147         pager_select,
4148 };
4150 static const char *log_argv[SIZEOF_ARG] = {
4151         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4152 };
4154 static enum request
4155 log_request(struct view *view, enum request request, struct line *line)
4157         switch (request) {
4158         case REQ_REFRESH:
4159                 load_refs();
4160                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4161                 return REQ_NONE;
4162         default:
4163                 return pager_request(view, request, line);
4164         }
4167 static struct view_ops log_ops = {
4168         "line",
4169         log_argv,
4170         NULL,
4171         pager_read,
4172         pager_draw,
4173         log_request,
4174         pager_grep,
4175         pager_select,
4176 };
4178 static const char *diff_argv[SIZEOF_ARG] = {
4179         "git", "show", "--pretty=fuller", "--no-color", "--root",
4180                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4181 };
4183 static struct view_ops diff_ops = {
4184         "line",
4185         diff_argv,
4186         NULL,
4187         pager_read,
4188         pager_draw,
4189         pager_request,
4190         pager_grep,
4191         pager_select,
4192 };
4194 /*
4195  * Help backend
4196  */
4198 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4200 static bool
4201 help_open_keymap_title(struct view *view, enum keymap keymap)
4203         struct line *line;
4205         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4206                                help_keymap_hidden[keymap] ? '+' : '-',
4207                                enum_name(keymap_table[keymap]));
4208         if (line)
4209                 line->other = keymap;
4211         return help_keymap_hidden[keymap];
4214 static void
4215 help_open_keymap(struct view *view, enum keymap keymap)
4217         const char *group = NULL;
4218         char buf[SIZEOF_STR];
4219         size_t bufpos;
4220         bool add_title = TRUE;
4221         int i;
4223         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4224                 const char *key = NULL;
4226                 if (req_info[i].request == REQ_NONE)
4227                         continue;
4229                 if (!req_info[i].request) {
4230                         group = req_info[i].help;
4231                         continue;
4232                 }
4234                 key = get_keys(keymap, req_info[i].request, TRUE);
4235                 if (!key || !*key)
4236                         continue;
4238                 if (add_title && help_open_keymap_title(view, keymap))
4239                         return;
4240                 add_title = FALSE;
4242                 if (group) {
4243                         add_line_text(view, group, LINE_HELP_GROUP);
4244                         group = NULL;
4245                 }
4247                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4248                                 enum_name(req_info[i]), req_info[i].help);
4249         }
4251         group = "External commands:";
4253         for (i = 0; i < run_requests; i++) {
4254                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4255                 const char *key;
4256                 int argc;
4258                 if (!req || req->keymap != keymap)
4259                         continue;
4261                 key = get_key_name(req->key);
4262                 if (!*key)
4263                         key = "(no key defined)";
4265                 if (add_title && help_open_keymap_title(view, keymap))
4266                         return;
4267                 if (group) {
4268                         add_line_text(view, group, LINE_HELP_GROUP);
4269                         group = NULL;
4270                 }
4272                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4273                         if (!string_format_from(buf, &bufpos, "%s%s",
4274                                                 argc ? " " : "", req->argv[argc]))
4275                                 return;
4277                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4278         }
4281 static bool
4282 help_open(struct view *view)
4284         enum keymap keymap;
4286         reset_view(view);
4287         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4288         add_line_text(view, "", LINE_DEFAULT);
4290         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4291                 help_open_keymap(view, keymap);
4293         return TRUE;
4296 static enum request
4297 help_request(struct view *view, enum request request, struct line *line)
4299         switch (request) {
4300         case REQ_ENTER:
4301                 if (line->type == LINE_HELP_KEYMAP) {
4302                         help_keymap_hidden[line->other] =
4303                                 !help_keymap_hidden[line->other];
4304                         view->p_restore = TRUE;
4305                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4306                 }
4308                 return REQ_NONE;
4309         default:
4310                 return pager_request(view, request, line);
4311         }
4314 static struct view_ops help_ops = {
4315         "line",
4316         NULL,
4317         help_open,
4318         NULL,
4319         pager_draw,
4320         help_request,
4321         pager_grep,
4322         pager_select,
4323 };
4326 /*
4327  * Tree backend
4328  */
4330 struct tree_stack_entry {
4331         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4332         unsigned long lineno;           /* Line number to restore */
4333         char *name;                     /* Position of name in opt_path */
4334 };
4336 /* The top of the path stack. */
4337 static struct tree_stack_entry *tree_stack = NULL;
4338 unsigned long tree_lineno = 0;
4340 static void
4341 pop_tree_stack_entry(void)
4343         struct tree_stack_entry *entry = tree_stack;
4345         tree_lineno = entry->lineno;
4346         entry->name[0] = 0;
4347         tree_stack = entry->prev;
4348         free(entry);
4351 static void
4352 push_tree_stack_entry(const char *name, unsigned long lineno)
4354         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4355         size_t pathlen = strlen(opt_path);
4357         if (!entry)
4358                 return;
4360         entry->prev = tree_stack;
4361         entry->name = opt_path + pathlen;
4362         tree_stack = entry;
4364         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4365                 pop_tree_stack_entry();
4366                 return;
4367         }
4369         /* Move the current line to the first tree entry. */
4370         tree_lineno = 1;
4371         entry->lineno = lineno;
4374 /* Parse output from git-ls-tree(1):
4375  *
4376  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4377  */
4379 #define SIZEOF_TREE_ATTR \
4380         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4382 #define SIZEOF_TREE_MODE \
4383         STRING_SIZE("100644 ")
4385 #define TREE_ID_OFFSET \
4386         STRING_SIZE("100644 blob ")
4388 struct tree_entry {
4389         char id[SIZEOF_REV];
4390         mode_t mode;
4391         struct time time;               /* Date from the author ident. */
4392         const char *author;             /* Author of the commit. */
4393         char name[1];
4394 };
4396 static const char *
4397 tree_path(const struct line *line)
4399         return ((struct tree_entry *) line->data)->name;
4402 static int
4403 tree_compare_entry(const struct line *line1, const struct line *line2)
4405         if (line1->type != line2->type)
4406                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4407         return strcmp(tree_path(line1), tree_path(line2));
4410 static const enum sort_field tree_sort_fields[] = {
4411         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4412 };
4413 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4415 static int
4416 tree_compare(const void *l1, const void *l2)
4418         const struct line *line1 = (const struct line *) l1;
4419         const struct line *line2 = (const struct line *) l2;
4420         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4421         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4423         if (line1->type == LINE_TREE_HEAD)
4424                 return -1;
4425         if (line2->type == LINE_TREE_HEAD)
4426                 return 1;
4428         switch (get_sort_field(tree_sort_state)) {
4429         case ORDERBY_DATE:
4430                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4432         case ORDERBY_AUTHOR:
4433                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4435         case ORDERBY_NAME:
4436         default:
4437                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4438         }
4442 static struct line *
4443 tree_entry(struct view *view, enum line_type type, const char *path,
4444            const char *mode, const char *id)
4446         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4447         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4449         if (!entry || !line) {
4450                 free(entry);
4451                 return NULL;
4452         }
4454         strncpy(entry->name, path, strlen(path));
4455         if (mode)
4456                 entry->mode = strtoul(mode, NULL, 8);
4457         if (id)
4458                 string_copy_rev(entry->id, id);
4460         return line;
4463 static bool
4464 tree_read_date(struct view *view, char *text, bool *read_date)
4466         static const char *author_name;
4467         static struct time author_time;
4469         if (!text && *read_date) {
4470                 *read_date = FALSE;
4471                 return TRUE;
4473         } else if (!text) {
4474                 char *path = *opt_path ? opt_path : ".";
4475                 /* Find next entry to process */
4476                 const char *log_file[] = {
4477                         "git", "log", "--no-color", "--pretty=raw",
4478                                 "--cc", "--raw", view->id, "--", path, NULL
4479                 };
4480                 struct io io = {};
4482                 if (!view->lines) {
4483                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4484                         report("Tree is empty");
4485                         return TRUE;
4486                 }
4488                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4489                         report("Failed to load tree data");
4490                         return TRUE;
4491                 }
4493                 io_done(view->pipe);
4494                 view->io = io;
4495                 *read_date = TRUE;
4496                 return FALSE;
4498         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4499                 parse_author_line(text + STRING_SIZE("author "),
4500                                   &author_name, &author_time);
4502         } else if (*text == ':') {
4503                 char *pos;
4504                 size_t annotated = 1;
4505                 size_t i;
4507                 pos = strchr(text, '\t');
4508                 if (!pos)
4509                         return TRUE;
4510                 text = pos + 1;
4511                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4512                         text += strlen(opt_path);
4513                 pos = strchr(text, '/');
4514                 if (pos)
4515                         *pos = 0;
4517                 for (i = 1; i < view->lines; i++) {
4518                         struct line *line = &view->line[i];
4519                         struct tree_entry *entry = line->data;
4521                         annotated += !!entry->author;
4522                         if (entry->author || strcmp(entry->name, text))
4523                                 continue;
4525                         entry->author = author_name;
4526                         entry->time = author_time;
4527                         line->dirty = 1;
4528                         break;
4529                 }
4531                 if (annotated == view->lines)
4532                         io_kill(view->pipe);
4533         }
4534         return TRUE;
4537 static bool
4538 tree_read(struct view *view, char *text)
4540         static bool read_date = FALSE;
4541         struct tree_entry *data;
4542         struct line *entry, *line;
4543         enum line_type type;
4544         size_t textlen = text ? strlen(text) : 0;
4545         char *path = text + SIZEOF_TREE_ATTR;
4547         if (read_date || !text)
4548                 return tree_read_date(view, text, &read_date);
4550         if (textlen <= SIZEOF_TREE_ATTR)
4551                 return FALSE;
4552         if (view->lines == 0 &&
4553             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4554                 return FALSE;
4556         /* Strip the path part ... */
4557         if (*opt_path) {
4558                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4559                 size_t striplen = strlen(opt_path);
4561                 if (pathlen > striplen)
4562                         memmove(path, path + striplen,
4563                                 pathlen - striplen + 1);
4565                 /* Insert "link" to parent directory. */
4566                 if (view->lines == 1 &&
4567                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4568                         return FALSE;
4569         }
4571         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4572         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4573         if (!entry)
4574                 return FALSE;
4575         data = entry->data;
4577         /* Skip "Directory ..." and ".." line. */
4578         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4579                 if (tree_compare_entry(line, entry) <= 0)
4580                         continue;
4582                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4584                 line->data = data;
4585                 line->type = type;
4586                 for (; line <= entry; line++)
4587                         line->dirty = line->cleareol = 1;
4588                 return TRUE;
4589         }
4591         if (tree_lineno > view->lineno) {
4592                 view->lineno = tree_lineno;
4593                 tree_lineno = 0;
4594         }
4596         return TRUE;
4599 static bool
4600 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4602         struct tree_entry *entry = line->data;
4604         if (line->type == LINE_TREE_HEAD) {
4605                 if (draw_text(view, line->type, "Directory path /", TRUE))
4606                         return TRUE;
4607         } else {
4608                 if (draw_mode(view, entry->mode))
4609                         return TRUE;
4611                 if (opt_author && draw_author(view, entry->author))
4612                         return TRUE;
4614                 if (opt_date && draw_date(view, &entry->time))
4615                         return TRUE;
4616         }
4617         if (draw_text(view, line->type, entry->name, TRUE))
4618                 return TRUE;
4619         return TRUE;
4622 static void
4623 open_blob_editor()
4625         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4626         int fd = mkstemp(file);
4628         if (fd == -1)
4629                 report("Failed to create temporary file");
4630         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4631                 report("Failed to save blob data to file");
4632         else
4633                 open_editor(file);
4634         if (fd != -1)
4635                 unlink(file);
4638 static enum request
4639 tree_request(struct view *view, enum request request, struct line *line)
4641         enum open_flags flags;
4643         switch (request) {
4644         case REQ_VIEW_BLAME:
4645                 if (line->type != LINE_TREE_FILE) {
4646                         report("Blame only supported for files");
4647                         return REQ_NONE;
4648                 }
4650                 string_copy(opt_ref, view->vid);
4651                 return request;
4653         case REQ_EDIT:
4654                 if (line->type != LINE_TREE_FILE) {
4655                         report("Edit only supported for files");
4656                 } else if (!is_head_commit(view->vid)) {
4657                         open_blob_editor();
4658                 } else {
4659                         open_editor(opt_file);
4660                 }
4661                 return REQ_NONE;
4663         case REQ_TOGGLE_SORT_FIELD:
4664         case REQ_TOGGLE_SORT_ORDER:
4665                 sort_view(view, request, &tree_sort_state, tree_compare);
4666                 return REQ_NONE;
4668         case REQ_PARENT:
4669                 if (!*opt_path) {
4670                         /* quit view if at top of tree */
4671                         return REQ_VIEW_CLOSE;
4672                 }
4673                 /* fake 'cd  ..' */
4674                 line = &view->line[1];
4675                 break;
4677         case REQ_ENTER:
4678                 break;
4680         default:
4681                 return request;
4682         }
4684         /* Cleanup the stack if the tree view is at a different tree. */
4685         while (!*opt_path && tree_stack)
4686                 pop_tree_stack_entry();
4688         switch (line->type) {
4689         case LINE_TREE_DIR:
4690                 /* Depending on whether it is a subdirectory or parent link
4691                  * mangle the path buffer. */
4692                 if (line == &view->line[1] && *opt_path) {
4693                         pop_tree_stack_entry();
4695                 } else {
4696                         const char *basename = tree_path(line);
4698                         push_tree_stack_entry(basename, view->lineno);
4699                 }
4701                 /* Trees and subtrees share the same ID, so they are not not
4702                  * unique like blobs. */
4703                 flags = OPEN_RELOAD;
4704                 request = REQ_VIEW_TREE;
4705                 break;
4707         case LINE_TREE_FILE:
4708                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4709                 request = REQ_VIEW_BLOB;
4710                 break;
4712         default:
4713                 return REQ_NONE;
4714         }
4716         open_view(view, request, flags);
4717         if (request == REQ_VIEW_TREE)
4718                 view->lineno = tree_lineno;
4720         return REQ_NONE;
4723 static bool
4724 tree_grep(struct view *view, struct line *line)
4726         struct tree_entry *entry = line->data;
4727         const char *text[] = {
4728                 entry->name,
4729                 opt_author ? entry->author : "",
4730                 mkdate(&entry->time, opt_date),
4731                 NULL
4732         };
4734         return grep_text(view, text);
4737 static void
4738 tree_select(struct view *view, struct line *line)
4740         struct tree_entry *entry = line->data;
4742         if (line->type == LINE_TREE_FILE) {
4743                 string_copy_rev(ref_blob, entry->id);
4744                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4746         } else if (line->type != LINE_TREE_DIR) {
4747                 return;
4748         }
4750         string_copy_rev(view->ref, entry->id);
4753 static bool
4754 tree_prepare(struct view *view)
4756         if (view->lines == 0 && opt_prefix[0]) {
4757                 char *pos = opt_prefix;
4759                 while (pos && *pos) {
4760                         char *end = strchr(pos, '/');
4762                         if (end)
4763                                 *end = 0;
4764                         push_tree_stack_entry(pos, 0);
4765                         pos = end;
4766                         if (end) {
4767                                 *end = '/';
4768                                 pos++;
4769                         }
4770                 }
4772         } else if (strcmp(view->vid, view->id)) {
4773                 opt_path[0] = 0;
4774         }
4776         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4779 static const char *tree_argv[SIZEOF_ARG] = {
4780         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4781 };
4783 static struct view_ops tree_ops = {
4784         "file",
4785         tree_argv,
4786         NULL,
4787         tree_read,
4788         tree_draw,
4789         tree_request,
4790         tree_grep,
4791         tree_select,
4792         tree_prepare,
4793 };
4795 static bool
4796 blob_read(struct view *view, char *line)
4798         if (!line)
4799                 return TRUE;
4800         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4803 static enum request
4804 blob_request(struct view *view, enum request request, struct line *line)
4806         switch (request) {
4807         case REQ_EDIT:
4808                 open_blob_editor();
4809                 return REQ_NONE;
4810         default:
4811                 return pager_request(view, request, line);
4812         }
4815 static const char *blob_argv[SIZEOF_ARG] = {
4816         "git", "cat-file", "blob", "%(blob)", NULL
4817 };
4819 static struct view_ops blob_ops = {
4820         "line",
4821         blob_argv,
4822         NULL,
4823         blob_read,
4824         pager_draw,
4825         blob_request,
4826         pager_grep,
4827         pager_select,
4828 };
4830 /*
4831  * Blame backend
4832  *
4833  * Loading the blame view is a two phase job:
4834  *
4835  *  1. File content is read either using opt_file from the
4836  *     filesystem or using git-cat-file.
4837  *  2. Then blame information is incrementally added by
4838  *     reading output from git-blame.
4839  */
4841 static const char *blame_head_argv[] = {
4842         "git", "blame", "--incremental", "--", "%(file)", NULL
4843 };
4845 static const char *blame_ref_argv[] = {
4846         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4847 };
4849 static const char *blame_cat_file_argv[] = {
4850         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4851 };
4853 struct blame_commit {
4854         char id[SIZEOF_REV];            /* SHA1 ID. */
4855         char title[128];                /* First line of the commit message. */
4856         const char *author;             /* Author of the commit. */
4857         struct time time;               /* Date from the author ident. */
4858         char filename[128];             /* Name of file. */
4859         bool has_previous;              /* Was a "previous" line detected. */
4860 };
4862 struct blame {
4863         struct blame_commit *commit;
4864         unsigned long lineno;
4865         char text[1];
4866 };
4868 static bool
4869 blame_open(struct view *view)
4871         char path[SIZEOF_STR];
4873         if (!view->parent && *opt_prefix) {
4874                 string_copy(path, opt_file);
4875                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4876                         return FALSE;
4877         }
4879         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4880                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4881                         return FALSE;
4882         }
4884         setup_update(view, opt_file);
4885         string_format(view->ref, "%s ...", opt_file);
4887         return TRUE;
4890 static struct blame_commit *
4891 get_blame_commit(struct view *view, const char *id)
4893         size_t i;
4895         for (i = 0; i < view->lines; i++) {
4896                 struct blame *blame = view->line[i].data;
4898                 if (!blame->commit)
4899                         continue;
4901                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4902                         return blame->commit;
4903         }
4905         {
4906                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4908                 if (commit)
4909                         string_ncopy(commit->id, id, SIZEOF_REV);
4910                 return commit;
4911         }
4914 static bool
4915 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4917         const char *pos = *posref;
4919         *posref = NULL;
4920         pos = strchr(pos + 1, ' ');
4921         if (!pos || !isdigit(pos[1]))
4922                 return FALSE;
4923         *number = atoi(pos + 1);
4924         if (*number < min || *number > max)
4925                 return FALSE;
4927         *posref = pos;
4928         return TRUE;
4931 static struct blame_commit *
4932 parse_blame_commit(struct view *view, const char *text, int *blamed)
4934         struct blame_commit *commit;
4935         struct blame *blame;
4936         const char *pos = text + SIZEOF_REV - 2;
4937         size_t orig_lineno = 0;
4938         size_t lineno;
4939         size_t group;
4941         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4942                 return NULL;
4944         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4945             !parse_number(&pos, &lineno, 1, view->lines) ||
4946             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4947                 return NULL;
4949         commit = get_blame_commit(view, text);
4950         if (!commit)
4951                 return NULL;
4953         *blamed += group;
4954         while (group--) {
4955                 struct line *line = &view->line[lineno + group - 1];
4957                 blame = line->data;
4958                 blame->commit = commit;
4959                 blame->lineno = orig_lineno + group - 1;
4960                 line->dirty = 1;
4961         }
4963         return commit;
4966 static bool
4967 blame_read_file(struct view *view, const char *line, bool *read_file)
4969         if (!line) {
4970                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4971                 struct io io = {};
4973                 if (view->lines == 0 && !view->parent)
4974                         die("No blame exist for %s", view->vid);
4976                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4977                         report("Failed to load blame data");
4978                         return TRUE;
4979                 }
4981                 io_done(view->pipe);
4982                 view->io = io;
4983                 *read_file = FALSE;
4984                 return FALSE;
4986         } else {
4987                 size_t linelen = strlen(line);
4988                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4990                 if (!blame)
4991                         return FALSE;
4993                 blame->commit = NULL;
4994                 strncpy(blame->text, line, linelen);
4995                 blame->text[linelen] = 0;
4996                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4997         }
5000 static bool
5001 match_blame_header(const char *name, char **line)
5003         size_t namelen = strlen(name);
5004         bool matched = !strncmp(name, *line, namelen);
5006         if (matched)
5007                 *line += namelen;
5009         return matched;
5012 static bool
5013 blame_read(struct view *view, char *line)
5015         static struct blame_commit *commit = NULL;
5016         static int blamed = 0;
5017         static bool read_file = TRUE;
5019         if (read_file)
5020                 return blame_read_file(view, line, &read_file);
5022         if (!line) {
5023                 /* Reset all! */
5024                 commit = NULL;
5025                 blamed = 0;
5026                 read_file = TRUE;
5027                 string_format(view->ref, "%s", view->vid);
5028                 if (view_is_displayed(view)) {
5029                         update_view_title(view);
5030                         redraw_view_from(view, 0);
5031                 }
5032                 return TRUE;
5033         }
5035         if (!commit) {
5036                 commit = parse_blame_commit(view, line, &blamed);
5037                 string_format(view->ref, "%s %2d%%", view->vid,
5038                               view->lines ? blamed * 100 / view->lines : 0);
5040         } else if (match_blame_header("author ", &line)) {
5041                 commit->author = get_author(line);
5043         } else if (match_blame_header("author-time ", &line)) {
5044                 parse_timesec(&commit->time, line);
5046         } else if (match_blame_header("author-tz ", &line)) {
5047                 parse_timezone(&commit->time, line);
5049         } else if (match_blame_header("summary ", &line)) {
5050                 string_ncopy(commit->title, line, strlen(line));
5052         } else if (match_blame_header("previous ", &line)) {
5053                 commit->has_previous = TRUE;
5055         } else if (match_blame_header("filename ", &line)) {
5056                 string_ncopy(commit->filename, line, strlen(line));
5057                 commit = NULL;
5058         }
5060         return TRUE;
5063 static bool
5064 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5066         struct blame *blame = line->data;
5067         struct time *time = NULL;
5068         const char *id = NULL, *author = NULL;
5069         char text[SIZEOF_STR];
5071         if (blame->commit && *blame->commit->filename) {
5072                 id = blame->commit->id;
5073                 author = blame->commit->author;
5074                 time = &blame->commit->time;
5075         }
5077         if (opt_date && draw_date(view, time))
5078                 return TRUE;
5080         if (opt_author && draw_author(view, author))
5081                 return TRUE;
5083         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5084                 return TRUE;
5086         if (draw_lineno(view, lineno))
5087                 return TRUE;
5089         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5090         draw_text(view, LINE_DEFAULT, text, TRUE);
5091         return TRUE;
5094 static bool
5095 check_blame_commit(struct blame *blame, bool check_null_id)
5097         if (!blame->commit)
5098                 report("Commit data not loaded yet");
5099         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5100                 report("No commit exist for the selected line");
5101         else
5102                 return TRUE;
5103         return FALSE;
5106 static void
5107 setup_blame_parent_line(struct view *view, struct blame *blame)
5109         const char *diff_tree_argv[] = {
5110                 "git", "diff-tree", "-U0", blame->commit->id,
5111                         "--", blame->commit->filename, NULL
5112         };
5113         struct io io = {};
5114         int parent_lineno = -1;
5115         int blamed_lineno = -1;
5116         char *line;
5118         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5119                 return;
5121         while ((line = io_get(&io, '\n', TRUE))) {
5122                 if (*line == '@') {
5123                         char *pos = strchr(line, '+');
5125                         parent_lineno = atoi(line + 4);
5126                         if (pos)
5127                                 blamed_lineno = atoi(pos + 1);
5129                 } else if (*line == '+' && parent_lineno != -1) {
5130                         if (blame->lineno == blamed_lineno - 1 &&
5131                             !strcmp(blame->text, line + 1)) {
5132                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5133                                 break;
5134                         }
5135                         blamed_lineno++;
5136                 }
5137         }
5139         io_done(&io);
5142 static enum request
5143 blame_request(struct view *view, enum request request, struct line *line)
5145         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5146         struct blame *blame = line->data;
5148         switch (request) {
5149         case REQ_VIEW_BLAME:
5150                 if (check_blame_commit(blame, TRUE)) {
5151                         string_copy(opt_ref, blame->commit->id);
5152                         string_copy(opt_file, blame->commit->filename);
5153                         if (blame->lineno)
5154                                 view->lineno = blame->lineno;
5155                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5156                 }
5157                 break;
5159         case REQ_PARENT:
5160                 if (check_blame_commit(blame, TRUE) &&
5161                     select_commit_parent(blame->commit->id, opt_ref,
5162                                          blame->commit->filename)) {
5163                         string_copy(opt_file, blame->commit->filename);
5164                         setup_blame_parent_line(view, blame);
5165                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5166                 }
5167                 break;
5169         case REQ_ENTER:
5170                 if (!check_blame_commit(blame, FALSE))
5171                         break;
5173                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5174                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5175                         break;
5177                 if (!strcmp(blame->commit->id, NULL_ID)) {
5178                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5179                         const char *diff_index_argv[] = {
5180                                 "git", "diff-index", "--root", "--patch-with-stat",
5181                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5182                         };
5184                         if (!blame->commit->has_previous) {
5185                                 diff_index_argv[1] = "diff";
5186                                 diff_index_argv[2] = "--no-color";
5187                                 diff_index_argv[6] = "--";
5188                                 diff_index_argv[7] = "/dev/null";
5189                         }
5191                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5192                                 report("Failed to allocate diff command");
5193                                 break;
5194                         }
5195                         flags |= OPEN_PREPARED;
5196                 }
5198                 open_view(view, REQ_VIEW_DIFF, flags);
5199                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5200                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5201                 break;
5203         default:
5204                 return request;
5205         }
5207         return REQ_NONE;
5210 static bool
5211 blame_grep(struct view *view, struct line *line)
5213         struct blame *blame = line->data;
5214         struct blame_commit *commit = blame->commit;
5215         const char *text[] = {
5216                 blame->text,
5217                 commit ? commit->title : "",
5218                 commit ? commit->id : "",
5219                 commit && opt_author ? commit->author : "",
5220                 commit ? mkdate(&commit->time, opt_date) : "",
5221                 NULL
5222         };
5224         return grep_text(view, text);
5227 static void
5228 blame_select(struct view *view, struct line *line)
5230         struct blame *blame = line->data;
5231         struct blame_commit *commit = blame->commit;
5233         if (!commit)
5234                 return;
5236         if (!strcmp(commit->id, NULL_ID))
5237                 string_ncopy(ref_commit, "HEAD", 4);
5238         else
5239                 string_copy_rev(ref_commit, commit->id);
5242 static struct view_ops blame_ops = {
5243         "line",
5244         NULL,
5245         blame_open,
5246         blame_read,
5247         blame_draw,
5248         blame_request,
5249         blame_grep,
5250         blame_select,
5251 };
5253 /*
5254  * Branch backend
5255  */
5257 struct branch {
5258         const char *author;             /* Author of the last commit. */
5259         struct time time;               /* Date of the last activity. */
5260         const struct ref *ref;          /* Name and commit ID information. */
5261 };
5263 static const struct ref branch_all;
5265 static const enum sort_field branch_sort_fields[] = {
5266         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5267 };
5268 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5270 static int
5271 branch_compare(const void *l1, const void *l2)
5273         const struct branch *branch1 = ((const struct line *) l1)->data;
5274         const struct branch *branch2 = ((const struct line *) l2)->data;
5276         switch (get_sort_field(branch_sort_state)) {
5277         case ORDERBY_DATE:
5278                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5280         case ORDERBY_AUTHOR:
5281                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5283         case ORDERBY_NAME:
5284         default:
5285                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5286         }
5289 static bool
5290 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5292         struct branch *branch = line->data;
5293         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5295         if (opt_date && draw_date(view, &branch->time))
5296                 return TRUE;
5298         if (opt_author && draw_author(view, branch->author))
5299                 return TRUE;
5301         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5302         return TRUE;
5305 static enum request
5306 branch_request(struct view *view, enum request request, struct line *line)
5308         struct branch *branch = line->data;
5310         switch (request) {
5311         case REQ_REFRESH:
5312                 load_refs();
5313                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5314                 return REQ_NONE;
5316         case REQ_TOGGLE_SORT_FIELD:
5317         case REQ_TOGGLE_SORT_ORDER:
5318                 sort_view(view, request, &branch_sort_state, branch_compare);
5319                 return REQ_NONE;
5321         case REQ_ENTER:
5322                 if (branch->ref == &branch_all) {
5323                         const char *all_branches_argv[] = {
5324                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5325                                       "--topo-order", "--all", NULL
5326                         };
5327                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5329                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5330                                 report("Failed to load view of all branches");
5331                                 return REQ_NONE;
5332                         }
5333                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5334                 } else {
5335                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5336                 }
5337                 return REQ_NONE;
5339         default:
5340                 return request;
5341         }
5344 static bool
5345 branch_read(struct view *view, char *line)
5347         static char id[SIZEOF_REV];
5348         struct branch *reference;
5349         size_t i;
5351         if (!line)
5352                 return TRUE;
5354         switch (get_line_type(line)) {
5355         case LINE_COMMIT:
5356                 string_copy_rev(id, line + STRING_SIZE("commit "));
5357                 return TRUE;
5359         case LINE_AUTHOR:
5360                 for (i = 0, reference = NULL; i < view->lines; i++) {
5361                         struct branch *branch = view->line[i].data;
5363                         if (strcmp(branch->ref->id, id))
5364                                 continue;
5366                         view->line[i].dirty = TRUE;
5367                         if (reference) {
5368                                 branch->author = reference->author;
5369                                 branch->time = reference->time;
5370                                 continue;
5371                         }
5373                         parse_author_line(line + STRING_SIZE("author "),
5374                                           &branch->author, &branch->time);
5375                         reference = branch;
5376                 }
5377                 return TRUE;
5379         default:
5380                 return TRUE;
5381         }
5385 static bool
5386 branch_open_visitor(void *data, const struct ref *ref)
5388         struct view *view = data;
5389         struct branch *branch;
5391         if (ref->tag || ref->ltag || ref->remote)
5392                 return TRUE;
5394         branch = calloc(1, sizeof(*branch));
5395         if (!branch)
5396                 return FALSE;
5398         branch->ref = ref;
5399         return !!add_line_data(view, branch, LINE_DEFAULT);
5402 static bool
5403 branch_open(struct view *view)
5405         const char *branch_log[] = {
5406                 "git", "log", "--no-color", "--pretty=raw",
5407                         "--simplify-by-decoration", "--all", NULL
5408         };
5410         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5411                 report("Failed to load branch data");
5412                 return TRUE;
5413         }
5415         setup_update(view, view->id);
5416         branch_open_visitor(view, &branch_all);
5417         foreach_ref(branch_open_visitor, view);
5418         view->p_restore = TRUE;
5420         return TRUE;
5423 static bool
5424 branch_grep(struct view *view, struct line *line)
5426         struct branch *branch = line->data;
5427         const char *text[] = {
5428                 branch->ref->name,
5429                 branch->author,
5430                 NULL
5431         };
5433         return grep_text(view, text);
5436 static void
5437 branch_select(struct view *view, struct line *line)
5439         struct branch *branch = line->data;
5441         string_copy_rev(view->ref, branch->ref->id);
5442         string_copy_rev(ref_commit, branch->ref->id);
5443         string_copy_rev(ref_head, branch->ref->id);
5446 static struct view_ops branch_ops = {
5447         "branch",
5448         NULL,
5449         branch_open,
5450         branch_read,
5451         branch_draw,
5452         branch_request,
5453         branch_grep,
5454         branch_select,
5455 };
5457 /*
5458  * Status backend
5459  */
5461 struct status {
5462         char status;
5463         struct {
5464                 mode_t mode;
5465                 char rev[SIZEOF_REV];
5466                 char name[SIZEOF_STR];
5467         } old;
5468         struct {
5469                 mode_t mode;
5470                 char rev[SIZEOF_REV];
5471                 char name[SIZEOF_STR];
5472         } new;
5473 };
5475 static char status_onbranch[SIZEOF_STR];
5476 static struct status stage_status;
5477 static enum line_type stage_line_type;
5478 static size_t stage_chunks;
5479 static int *stage_chunk;
5481 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5483 /* This should work even for the "On branch" line. */
5484 static inline bool
5485 status_has_none(struct view *view, struct line *line)
5487         return line < view->line + view->lines && !line[1].data;
5490 /* Get fields from the diff line:
5491  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5492  */
5493 static inline bool
5494 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5496         const char *old_mode = buf +  1;
5497         const char *new_mode = buf +  8;
5498         const char *old_rev  = buf + 15;
5499         const char *new_rev  = buf + 56;
5500         const char *status   = buf + 97;
5502         if (bufsize < 98 ||
5503             old_mode[-1] != ':' ||
5504             new_mode[-1] != ' ' ||
5505             old_rev[-1]  != ' ' ||
5506             new_rev[-1]  != ' ' ||
5507             status[-1]   != ' ')
5508                 return FALSE;
5510         file->status = *status;
5512         string_copy_rev(file->old.rev, old_rev);
5513         string_copy_rev(file->new.rev, new_rev);
5515         file->old.mode = strtoul(old_mode, NULL, 8);
5516         file->new.mode = strtoul(new_mode, NULL, 8);
5518         file->old.name[0] = file->new.name[0] = 0;
5520         return TRUE;
5523 static bool
5524 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5526         struct status *unmerged = NULL;
5527         char *buf;
5528         struct io io = {};
5530         if (!io_run(&io, argv, opt_cdup, IO_RD))
5531                 return FALSE;
5533         add_line_data(view, NULL, type);
5535         while ((buf = io_get(&io, 0, TRUE))) {
5536                 struct status *file = unmerged;
5538                 if (!file) {
5539                         file = calloc(1, sizeof(*file));
5540                         if (!file || !add_line_data(view, file, type))
5541                                 goto error_out;
5542                 }
5544                 /* Parse diff info part. */
5545                 if (status) {
5546                         file->status = status;
5547                         if (status == 'A')
5548                                 string_copy(file->old.rev, NULL_ID);
5550                 } else if (!file->status || file == unmerged) {
5551                         if (!status_get_diff(file, buf, strlen(buf)))
5552                                 goto error_out;
5554                         buf = io_get(&io, 0, TRUE);
5555                         if (!buf)
5556                                 break;
5558                         /* Collapse all modified entries that follow an
5559                          * associated unmerged entry. */
5560                         if (unmerged == file) {
5561                                 unmerged->status = 'U';
5562                                 unmerged = NULL;
5563                         } else if (file->status == 'U') {
5564                                 unmerged = file;
5565                         }
5566                 }
5568                 /* Grab the old name for rename/copy. */
5569                 if (!*file->old.name &&
5570                     (file->status == 'R' || file->status == 'C')) {
5571                         string_ncopy(file->old.name, buf, strlen(buf));
5573                         buf = io_get(&io, 0, TRUE);
5574                         if (!buf)
5575                                 break;
5576                 }
5578                 /* git-ls-files just delivers a NUL separated list of
5579                  * file names similar to the second half of the
5580                  * git-diff-* output. */
5581                 string_ncopy(file->new.name, buf, strlen(buf));
5582                 if (!*file->old.name)
5583                         string_copy(file->old.name, file->new.name);
5584                 file = NULL;
5585         }
5587         if (io_error(&io)) {
5588 error_out:
5589                 io_done(&io);
5590                 return FALSE;
5591         }
5593         if (!view->line[view->lines - 1].data)
5594                 add_line_data(view, NULL, LINE_STAT_NONE);
5596         io_done(&io);
5597         return TRUE;
5600 /* Don't show unmerged entries in the staged section. */
5601 static const char *status_diff_index_argv[] = {
5602         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5603                              "--cached", "-M", "HEAD", NULL
5604 };
5606 static const char *status_diff_files_argv[] = {
5607         "git", "diff-files", "-z", NULL
5608 };
5610 static const char *status_list_other_argv[] = {
5611         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5612 };
5614 static const char *status_list_no_head_argv[] = {
5615         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5616 };
5618 static const char *update_index_argv[] = {
5619         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5620 };
5622 /* Restore the previous line number to stay in the context or select a
5623  * line with something that can be updated. */
5624 static void
5625 status_restore(struct view *view)
5627         if (view->p_lineno >= view->lines)
5628                 view->p_lineno = view->lines - 1;
5629         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5630                 view->p_lineno++;
5631         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5632                 view->p_lineno--;
5634         /* If the above fails, always skip the "On branch" line. */
5635         if (view->p_lineno < view->lines)
5636                 view->lineno = view->p_lineno;
5637         else
5638                 view->lineno = 1;
5640         if (view->lineno < view->offset)
5641                 view->offset = view->lineno;
5642         else if (view->offset + view->height <= view->lineno)
5643                 view->offset = view->lineno - view->height + 1;
5645         view->p_restore = FALSE;
5648 static void
5649 status_update_onbranch(void)
5651         static const char *paths[][2] = {
5652                 { "rebase-apply/rebasing",      "Rebasing" },
5653                 { "rebase-apply/applying",      "Applying mailbox" },
5654                 { "rebase-apply/",              "Rebasing mailbox" },
5655                 { "rebase-merge/interactive",   "Interactive rebase" },
5656                 { "rebase-merge/",              "Rebase merge" },
5657                 { "MERGE_HEAD",                 "Merging" },
5658                 { "BISECT_LOG",                 "Bisecting" },
5659                 { "HEAD",                       "On branch" },
5660         };
5661         char buf[SIZEOF_STR];
5662         struct stat stat;
5663         int i;
5665         if (is_initial_commit()) {
5666                 string_copy(status_onbranch, "Initial commit");
5667                 return;
5668         }
5670         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5671                 char *head = opt_head;
5673                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5674                     lstat(buf, &stat) < 0)
5675                         continue;
5677                 if (!*opt_head) {
5678                         struct io io = {};
5680                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5681                             io_read_buf(&io, buf, sizeof(buf))) {
5682                                 head = buf;
5683                                 if (!prefixcmp(head, "refs/heads/"))
5684                                         head += STRING_SIZE("refs/heads/");
5685                         }
5686                 }
5688                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5689                         string_copy(status_onbranch, opt_head);
5690                 return;
5691         }
5693         string_copy(status_onbranch, "Not currently on any branch");
5696 /* First parse staged info using git-diff-index(1), then parse unstaged
5697  * info using git-diff-files(1), and finally untracked files using
5698  * git-ls-files(1). */
5699 static bool
5700 status_open(struct view *view)
5702         reset_view(view);
5704         add_line_data(view, NULL, LINE_STAT_HEAD);
5705         status_update_onbranch();
5707         io_run_bg(update_index_argv);
5709         if (is_initial_commit()) {
5710                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5711                         return FALSE;
5712         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5713                 return FALSE;
5714         }
5716         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5717             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5718                 return FALSE;
5720         /* Restore the exact position or use the specialized restore
5721          * mode? */
5722         if (!view->p_restore)
5723                 status_restore(view);
5724         return TRUE;
5727 static bool
5728 status_draw(struct view *view, struct line *line, unsigned int lineno)
5730         struct status *status = line->data;
5731         enum line_type type;
5732         const char *text;
5734         if (!status) {
5735                 switch (line->type) {
5736                 case LINE_STAT_STAGED:
5737                         type = LINE_STAT_SECTION;
5738                         text = "Changes to be committed:";
5739                         break;
5741                 case LINE_STAT_UNSTAGED:
5742                         type = LINE_STAT_SECTION;
5743                         text = "Changed but not updated:";
5744                         break;
5746                 case LINE_STAT_UNTRACKED:
5747                         type = LINE_STAT_SECTION;
5748                         text = "Untracked files:";
5749                         break;
5751                 case LINE_STAT_NONE:
5752                         type = LINE_DEFAULT;
5753                         text = "  (no files)";
5754                         break;
5756                 case LINE_STAT_HEAD:
5757                         type = LINE_STAT_HEAD;
5758                         text = status_onbranch;
5759                         break;
5761                 default:
5762                         return FALSE;
5763                 }
5764         } else {
5765                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5767                 buf[0] = status->status;
5768                 if (draw_text(view, line->type, buf, TRUE))
5769                         return TRUE;
5770                 type = LINE_DEFAULT;
5771                 text = status->new.name;
5772         }
5774         draw_text(view, type, text, TRUE);
5775         return TRUE;
5778 static enum request
5779 status_load_error(struct view *view, struct view *stage, const char *path)
5781         if (displayed_views() == 2 || display[current_view] != view)
5782                 maximize_view(view);
5783         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5784         return REQ_NONE;
5787 static enum request
5788 status_enter(struct view *view, struct line *line)
5790         struct status *status = line->data;
5791         const char *oldpath = status ? status->old.name : NULL;
5792         /* Diffs for unmerged entries are empty when passing the new
5793          * path, so leave it empty. */
5794         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5795         const char *info;
5796         enum open_flags split;
5797         struct view *stage = VIEW(REQ_VIEW_STAGE);
5799         if (line->type == LINE_STAT_NONE ||
5800             (!status && line[1].type == LINE_STAT_NONE)) {
5801                 report("No file to diff");
5802                 return REQ_NONE;
5803         }
5805         switch (line->type) {
5806         case LINE_STAT_STAGED:
5807                 if (is_initial_commit()) {
5808                         const char *no_head_diff_argv[] = {
5809                                 "git", "diff", "--no-color", "--patch-with-stat",
5810                                         "--", "/dev/null", newpath, NULL
5811                         };
5813                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5814                                 return status_load_error(view, stage, newpath);
5815                 } else {
5816                         const char *index_show_argv[] = {
5817                                 "git", "diff-index", "--root", "--patch-with-stat",
5818                                         "-C", "-M", "--cached", "HEAD", "--",
5819                                         oldpath, newpath, NULL
5820                         };
5822                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5823                                 return status_load_error(view, stage, newpath);
5824                 }
5826                 if (status)
5827                         info = "Staged changes to %s";
5828                 else
5829                         info = "Staged changes";
5830                 break;
5832         case LINE_STAT_UNSTAGED:
5833         {
5834                 const char *files_show_argv[] = {
5835                         "git", "diff-files", "--root", "--patch-with-stat",
5836                                 "-C", "-M", "--", oldpath, newpath, NULL
5837                 };
5839                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5840                         return status_load_error(view, stage, newpath);
5841                 if (status)
5842                         info = "Unstaged changes to %s";
5843                 else
5844                         info = "Unstaged changes";
5845                 break;
5846         }
5847         case LINE_STAT_UNTRACKED:
5848                 if (!newpath) {
5849                         report("No file to show");
5850                         return REQ_NONE;
5851                 }
5853                 if (!suffixcmp(status->new.name, -1, "/")) {
5854                         report("Cannot display a directory");
5855                         return REQ_NONE;
5856                 }
5858                 if (!prepare_update_file(stage, newpath))
5859                         return status_load_error(view, stage, newpath);
5860                 info = "Untracked file %s";
5861                 break;
5863         case LINE_STAT_HEAD:
5864                 return REQ_NONE;
5866         default:
5867                 die("line type %d not handled in switch", line->type);
5868         }
5870         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5871         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5872         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5873                 if (status) {
5874                         stage_status = *status;
5875                 } else {
5876                         memset(&stage_status, 0, sizeof(stage_status));
5877                 }
5879                 stage_line_type = line->type;
5880                 stage_chunks = 0;
5881                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5882         }
5884         return REQ_NONE;
5887 static bool
5888 status_exists(struct status *status, enum line_type type)
5890         struct view *view = VIEW(REQ_VIEW_STATUS);
5891         unsigned long lineno;
5893         for (lineno = 0; lineno < view->lines; lineno++) {
5894                 struct line *line = &view->line[lineno];
5895                 struct status *pos = line->data;
5897                 if (line->type != type)
5898                         continue;
5899                 if (!pos && (!status || !status->status) && line[1].data) {
5900                         select_view_line(view, lineno);
5901                         return TRUE;
5902                 }
5903                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5904                         select_view_line(view, lineno);
5905                         return TRUE;
5906                 }
5907         }
5909         return FALSE;
5913 static bool
5914 status_update_prepare(struct io *io, enum line_type type)
5916         const char *staged_argv[] = {
5917                 "git", "update-index", "-z", "--index-info", NULL
5918         };
5919         const char *others_argv[] = {
5920                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5921         };
5923         switch (type) {
5924         case LINE_STAT_STAGED:
5925                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5927         case LINE_STAT_UNSTAGED:
5928         case LINE_STAT_UNTRACKED:
5929                 return io_run(io, others_argv, opt_cdup, IO_WR);
5931         default:
5932                 die("line type %d not handled in switch", type);
5933                 return FALSE;
5934         }
5937 static bool
5938 status_update_write(struct io *io, struct status *status, enum line_type type)
5940         char buf[SIZEOF_STR];
5941         size_t bufsize = 0;
5943         switch (type) {
5944         case LINE_STAT_STAGED:
5945                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5946                                         status->old.mode,
5947                                         status->old.rev,
5948                                         status->old.name, 0))
5949                         return FALSE;
5950                 break;
5952         case LINE_STAT_UNSTAGED:
5953         case LINE_STAT_UNTRACKED:
5954                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5955                         return FALSE;
5956                 break;
5958         default:
5959                 die("line type %d not handled in switch", type);
5960         }
5962         return io_write(io, buf, bufsize);
5965 static bool
5966 status_update_file(struct status *status, enum line_type type)
5968         struct io io = {};
5969         bool result;
5971         if (!status_update_prepare(&io, type))
5972                 return FALSE;
5974         result = status_update_write(&io, status, type);
5975         return io_done(&io) && result;
5978 static bool
5979 status_update_files(struct view *view, struct line *line)
5981         char buf[sizeof(view->ref)];
5982         struct io io = {};
5983         bool result = TRUE;
5984         struct line *pos = view->line + view->lines;
5985         int files = 0;
5986         int file, done;
5987         int cursor_y = -1, cursor_x = -1;
5989         if (!status_update_prepare(&io, line->type))
5990                 return FALSE;
5992         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5993                 files++;
5995         string_copy(buf, view->ref);
5996         getsyx(cursor_y, cursor_x);
5997         for (file = 0, done = 5; result && file < files; line++, file++) {
5998                 int almost_done = file * 100 / files;
6000                 if (almost_done > done) {
6001                         done = almost_done;
6002                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6003                                       file, files, done);
6004                         update_view_title(view);
6005                         setsyx(cursor_y, cursor_x);
6006                         doupdate();
6007                 }
6008                 result = status_update_write(&io, line->data, line->type);
6009         }
6010         string_copy(view->ref, buf);
6012         return io_done(&io) && result;
6015 static bool
6016 status_update(struct view *view)
6018         struct line *line = &view->line[view->lineno];
6020         assert(view->lines);
6022         if (!line->data) {
6023                 /* This should work even for the "On branch" line. */
6024                 if (line < view->line + view->lines && !line[1].data) {
6025                         report("Nothing to update");
6026                         return FALSE;
6027                 }
6029                 if (!status_update_files(view, line + 1)) {
6030                         report("Failed to update file status");
6031                         return FALSE;
6032                 }
6034         } else if (!status_update_file(line->data, line->type)) {
6035                 report("Failed to update file status");
6036                 return FALSE;
6037         }
6039         return TRUE;
6042 static bool
6043 status_revert(struct status *status, enum line_type type, bool has_none)
6045         if (!status || type != LINE_STAT_UNSTAGED) {
6046                 if (type == LINE_STAT_STAGED) {
6047                         report("Cannot revert changes to staged files");
6048                 } else if (type == LINE_STAT_UNTRACKED) {
6049                         report("Cannot revert changes to untracked files");
6050                 } else if (has_none) {
6051                         report("Nothing to revert");
6052                 } else {
6053                         report("Cannot revert changes to multiple files");
6054                 }
6056         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6057                 char mode[10] = "100644";
6058                 const char *reset_argv[] = {
6059                         "git", "update-index", "--cacheinfo", mode,
6060                                 status->old.rev, status->old.name, NULL
6061                 };
6062                 const char *checkout_argv[] = {
6063                         "git", "checkout", "--", status->old.name, NULL
6064                 };
6066                 if (status->status == 'U') {
6067                         string_format(mode, "%5o", status->old.mode);
6069                         if (status->old.mode == 0 && status->new.mode == 0) {
6070                                 reset_argv[2] = "--force-remove";
6071                                 reset_argv[3] = status->old.name;
6072                                 reset_argv[4] = NULL;
6073                         }
6075                         if (!io_run_fg(reset_argv, opt_cdup))
6076                                 return FALSE;
6077                         if (status->old.mode == 0 && status->new.mode == 0)
6078                                 return TRUE;
6079                 }
6081                 return io_run_fg(checkout_argv, opt_cdup);
6082         }
6084         return FALSE;
6087 static enum request
6088 status_request(struct view *view, enum request request, struct line *line)
6090         struct status *status = line->data;
6092         switch (request) {
6093         case REQ_STATUS_UPDATE:
6094                 if (!status_update(view))
6095                         return REQ_NONE;
6096                 break;
6098         case REQ_STATUS_REVERT:
6099                 if (!status_revert(status, line->type, status_has_none(view, line)))
6100                         return REQ_NONE;
6101                 break;
6103         case REQ_STATUS_MERGE:
6104                 if (!status || status->status != 'U') {
6105                         report("Merging only possible for files with unmerged status ('U').");
6106                         return REQ_NONE;
6107                 }
6108                 open_mergetool(status->new.name);
6109                 break;
6111         case REQ_EDIT:
6112                 if (!status)
6113                         return request;
6114                 if (status->status == 'D') {
6115                         report("File has been deleted.");
6116                         return REQ_NONE;
6117                 }
6119                 open_editor(status->new.name);
6120                 break;
6122         case REQ_VIEW_BLAME:
6123                 if (status)
6124                         opt_ref[0] = 0;
6125                 return request;
6127         case REQ_ENTER:
6128                 /* After returning the status view has been split to
6129                  * show the stage view. No further reloading is
6130                  * necessary. */
6131                 return status_enter(view, line);
6133         case REQ_REFRESH:
6134                 /* Simply reload the view. */
6135                 break;
6137         default:
6138                 return request;
6139         }
6141         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6143         return REQ_NONE;
6146 static void
6147 status_select(struct view *view, struct line *line)
6149         struct status *status = line->data;
6150         char file[SIZEOF_STR] = "all files";
6151         const char *text;
6152         const char *key;
6154         if (status && !string_format(file, "'%s'", status->new.name))
6155                 return;
6157         if (!status && line[1].type == LINE_STAT_NONE)
6158                 line++;
6160         switch (line->type) {
6161         case LINE_STAT_STAGED:
6162                 text = "Press %s to unstage %s for commit";
6163                 break;
6165         case LINE_STAT_UNSTAGED:
6166                 text = "Press %s to stage %s for commit";
6167                 break;
6169         case LINE_STAT_UNTRACKED:
6170                 text = "Press %s to stage %s for addition";
6171                 break;
6173         case LINE_STAT_HEAD:
6174         case LINE_STAT_NONE:
6175                 text = "Nothing to update";
6176                 break;
6178         default:
6179                 die("line type %d not handled in switch", line->type);
6180         }
6182         if (status && status->status == 'U') {
6183                 text = "Press %s to resolve conflict in %s";
6184                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6186         } else {
6187                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6188         }
6190         string_format(view->ref, text, key, file);
6191         if (status)
6192                 string_copy(opt_file, status->new.name);
6195 static bool
6196 status_grep(struct view *view, struct line *line)
6198         struct status *status = line->data;
6200         if (status) {
6201                 const char buf[2] = { status->status, 0 };
6202                 const char *text[] = { status->new.name, buf, NULL };
6204                 return grep_text(view, text);
6205         }
6207         return FALSE;
6210 static struct view_ops status_ops = {
6211         "file",
6212         NULL,
6213         status_open,
6214         NULL,
6215         status_draw,
6216         status_request,
6217         status_grep,
6218         status_select,
6219 };
6222 static bool
6223 stage_diff_write(struct io *io, struct line *line, struct line *end)
6225         while (line < end) {
6226                 if (!io_write(io, line->data, strlen(line->data)) ||
6227                     !io_write(io, "\n", 1))
6228                         return FALSE;
6229                 line++;
6230                 if (line->type == LINE_DIFF_CHUNK ||
6231                     line->type == LINE_DIFF_HEADER)
6232                         break;
6233         }
6235         return TRUE;
6238 static struct line *
6239 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6241         for (; view->line < line; line--)
6242                 if (line->type == type)
6243                         return line;
6245         return NULL;
6248 static bool
6249 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6251         const char *apply_argv[SIZEOF_ARG] = {
6252                 "git", "apply", "--whitespace=nowarn", NULL
6253         };
6254         struct line *diff_hdr;
6255         struct io io = {};
6256         int argc = 3;
6258         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6259         if (!diff_hdr)
6260                 return FALSE;
6262         if (!revert)
6263                 apply_argv[argc++] = "--cached";
6264         if (revert || stage_line_type == LINE_STAT_STAGED)
6265                 apply_argv[argc++] = "-R";
6266         apply_argv[argc++] = "-";
6267         apply_argv[argc++] = NULL;
6268         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6269                 return FALSE;
6271         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6272             !stage_diff_write(&io, chunk, view->line + view->lines))
6273                 chunk = NULL;
6275         io_done(&io);
6276         io_run_bg(update_index_argv);
6278         return chunk ? TRUE : FALSE;
6281 static bool
6282 stage_update(struct view *view, struct line *line)
6284         struct line *chunk = NULL;
6286         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6287                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6289         if (chunk) {
6290                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6291                         report("Failed to apply chunk");
6292                         return FALSE;
6293                 }
6295         } else if (!stage_status.status) {
6296                 view = VIEW(REQ_VIEW_STATUS);
6298                 for (line = view->line; line < view->line + view->lines; line++)
6299                         if (line->type == stage_line_type)
6300                                 break;
6302                 if (!status_update_files(view, line + 1)) {
6303                         report("Failed to update files");
6304                         return FALSE;
6305                 }
6307         } else if (!status_update_file(&stage_status, stage_line_type)) {
6308                 report("Failed to update file");
6309                 return FALSE;
6310         }
6312         return TRUE;
6315 static bool
6316 stage_revert(struct view *view, struct line *line)
6318         struct line *chunk = NULL;
6320         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6321                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6323         if (chunk) {
6324                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6325                         return FALSE;
6327                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6328                         report("Failed to revert chunk");
6329                         return FALSE;
6330                 }
6331                 return TRUE;
6333         } else {
6334                 return status_revert(stage_status.status ? &stage_status : NULL,
6335                                      stage_line_type, FALSE);
6336         }
6340 static void
6341 stage_next(struct view *view, struct line *line)
6343         int i;
6345         if (!stage_chunks) {
6346                 for (line = view->line; line < view->line + view->lines; line++) {
6347                         if (line->type != LINE_DIFF_CHUNK)
6348                                 continue;
6350                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6351                                 report("Allocation failure");
6352                                 return;
6353                         }
6355                         stage_chunk[stage_chunks++] = line - view->line;
6356                 }
6357         }
6359         for (i = 0; i < stage_chunks; i++) {
6360                 if (stage_chunk[i] > view->lineno) {
6361                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6362                         report("Chunk %d of %d", i + 1, stage_chunks);
6363                         return;
6364                 }
6365         }
6367         report("No next chunk found");
6370 static enum request
6371 stage_request(struct view *view, enum request request, struct line *line)
6373         switch (request) {
6374         case REQ_STATUS_UPDATE:
6375                 if (!stage_update(view, line))
6376                         return REQ_NONE;
6377                 break;
6379         case REQ_STATUS_REVERT:
6380                 if (!stage_revert(view, line))
6381                         return REQ_NONE;
6382                 break;
6384         case REQ_STAGE_NEXT:
6385                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6386                         report("File is untracked; press %s to add",
6387                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6388                         return REQ_NONE;
6389                 }
6390                 stage_next(view, line);
6391                 return REQ_NONE;
6393         case REQ_EDIT:
6394                 if (!stage_status.new.name[0])
6395                         return request;
6396                 if (stage_status.status == 'D') {
6397                         report("File has been deleted.");
6398                         return REQ_NONE;
6399                 }
6401                 open_editor(stage_status.new.name);
6402                 break;
6404         case REQ_REFRESH:
6405                 /* Reload everything ... */
6406                 break;
6408         case REQ_VIEW_BLAME:
6409                 if (stage_status.new.name[0]) {
6410                         string_copy(opt_file, stage_status.new.name);
6411                         opt_ref[0] = 0;
6412                 }
6413                 return request;
6415         case REQ_ENTER:
6416                 return pager_request(view, request, line);
6418         default:
6419                 return request;
6420         }
6422         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6423         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6425         /* Check whether the staged entry still exists, and close the
6426          * stage view if it doesn't. */
6427         if (!status_exists(&stage_status, stage_line_type)) {
6428                 status_restore(VIEW(REQ_VIEW_STATUS));
6429                 return REQ_VIEW_CLOSE;
6430         }
6432         if (stage_line_type == LINE_STAT_UNTRACKED) {
6433                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6434                         report("Cannot display a directory");
6435                         return REQ_NONE;
6436                 }
6438                 if (!prepare_update_file(view, stage_status.new.name)) {
6439                         report("Failed to open file: %s", strerror(errno));
6440                         return REQ_NONE;
6441                 }
6442         }
6443         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6445         return REQ_NONE;
6448 static struct view_ops stage_ops = {
6449         "line",
6450         NULL,
6451         NULL,
6452         pager_read,
6453         pager_draw,
6454         stage_request,
6455         pager_grep,
6456         pager_select,
6457 };
6460 /*
6461  * Revision graph
6462  */
6464 struct commit {
6465         char id[SIZEOF_REV];            /* SHA1 ID. */
6466         char title[128];                /* First line of the commit message. */
6467         const char *author;             /* Author of the commit. */
6468         struct time time;               /* Date from the author ident. */
6469         struct ref_list *refs;          /* Repository references. */
6470         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6471         size_t graph_size;              /* The width of the graph array. */
6472         bool has_parents;               /* Rewritten --parents seen. */
6473 };
6475 /* Size of rev graph with no  "padding" columns */
6476 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6478 struct rev_graph {
6479         struct rev_graph *prev, *next, *parents;
6480         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6481         size_t size;
6482         struct commit *commit;
6483         size_t pos;
6484         unsigned int boundary:1;
6485 };
6487 /* Parents of the commit being visualized. */
6488 static struct rev_graph graph_parents[4];
6490 /* The current stack of revisions on the graph. */
6491 static struct rev_graph graph_stacks[4] = {
6492         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6493         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6494         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6495         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6496 };
6498 static inline bool
6499 graph_parent_is_merge(struct rev_graph *graph)
6501         return graph->parents->size > 1;
6504 static inline void
6505 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6507         struct commit *commit = graph->commit;
6509         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6510                 commit->graph[commit->graph_size++] = symbol;
6513 static void
6514 clear_rev_graph(struct rev_graph *graph)
6516         graph->boundary = 0;
6517         graph->size = graph->pos = 0;
6518         graph->commit = NULL;
6519         memset(graph->parents, 0, sizeof(*graph->parents));
6522 static void
6523 done_rev_graph(struct rev_graph *graph)
6525         if (graph_parent_is_merge(graph) &&
6526             graph->pos < graph->size - 1 &&
6527             graph->next->size == graph->size + graph->parents->size - 1) {
6528                 size_t i = graph->pos + graph->parents->size - 1;
6530                 graph->commit->graph_size = i * 2;
6531                 while (i < graph->next->size - 1) {
6532                         append_to_rev_graph(graph, ' ');
6533                         append_to_rev_graph(graph, '\\');
6534                         i++;
6535                 }
6536         }
6538         clear_rev_graph(graph);
6541 static void
6542 push_rev_graph(struct rev_graph *graph, const char *parent)
6544         int i;
6546         /* "Collapse" duplicate parents lines.
6547          *
6548          * FIXME: This needs to also update update the drawn graph but
6549          * for now it just serves as a method for pruning graph lines. */
6550         for (i = 0; i < graph->size; i++)
6551                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6552                         return;
6554         if (graph->size < SIZEOF_REVITEMS) {
6555                 string_copy_rev(graph->rev[graph->size++], parent);
6556         }
6559 static chtype
6560 get_rev_graph_symbol(struct rev_graph *graph)
6562         chtype symbol;
6564         if (graph->boundary)
6565                 symbol = REVGRAPH_BOUND;
6566         else if (graph->parents->size == 0)
6567                 symbol = REVGRAPH_INIT;
6568         else if (graph_parent_is_merge(graph))
6569                 symbol = REVGRAPH_MERGE;
6570         else if (graph->pos >= graph->size)
6571                 symbol = REVGRAPH_BRANCH;
6572         else
6573                 symbol = REVGRAPH_COMMIT;
6575         return symbol;
6578 static void
6579 draw_rev_graph(struct rev_graph *graph)
6581         struct rev_filler {
6582                 chtype separator, line;
6583         };
6584         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6585         static struct rev_filler fillers[] = {
6586                 { ' ',  '|' },
6587                 { '`',  '.' },
6588                 { '\'', ' ' },
6589                 { '/',  ' ' },
6590         };
6591         chtype symbol = get_rev_graph_symbol(graph);
6592         struct rev_filler *filler;
6593         size_t i;
6595         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6596         filler = &fillers[DEFAULT];
6598         for (i = 0; i < graph->pos; i++) {
6599                 append_to_rev_graph(graph, filler->line);
6600                 if (graph_parent_is_merge(graph->prev) &&
6601                     graph->prev->pos == i)
6602                         filler = &fillers[RSHARP];
6604                 append_to_rev_graph(graph, filler->separator);
6605         }
6607         /* Place the symbol for this revision. */
6608         append_to_rev_graph(graph, symbol);
6610         if (graph->prev->size > graph->size)
6611                 filler = &fillers[RDIAG];
6612         else
6613                 filler = &fillers[DEFAULT];
6615         i++;
6617         for (; i < graph->size; i++) {
6618                 append_to_rev_graph(graph, filler->separator);
6619                 append_to_rev_graph(graph, filler->line);
6620                 if (graph_parent_is_merge(graph->prev) &&
6621                     i < graph->prev->pos + graph->parents->size)
6622                         filler = &fillers[RSHARP];
6623                 if (graph->prev->size > graph->size)
6624                         filler = &fillers[LDIAG];
6625         }
6627         if (graph->prev->size > graph->size) {
6628                 append_to_rev_graph(graph, filler->separator);
6629                 if (filler->line != ' ')
6630                         append_to_rev_graph(graph, filler->line);
6631         }
6634 /* Prepare the next rev graph */
6635 static void
6636 prepare_rev_graph(struct rev_graph *graph)
6638         size_t i;
6640         /* First, traverse all lines of revisions up to the active one. */
6641         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6642                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6643                         break;
6645                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6646         }
6648         /* Interleave the new revision parent(s). */
6649         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6650                 push_rev_graph(graph->next, graph->parents->rev[i]);
6652         /* Lastly, put any remaining revisions. */
6653         for (i = graph->pos + 1; i < graph->size; i++)
6654                 push_rev_graph(graph->next, graph->rev[i]);
6657 static void
6658 update_rev_graph(struct view *view, struct rev_graph *graph)
6660         /* If this is the finalizing update ... */
6661         if (graph->commit)
6662                 prepare_rev_graph(graph);
6664         /* Graph visualization needs a one rev look-ahead,
6665          * so the first update doesn't visualize anything. */
6666         if (!graph->prev->commit)
6667                 return;
6669         if (view->lines > 2)
6670                 view->line[view->lines - 3].dirty = 1;
6671         if (view->lines > 1)
6672                 view->line[view->lines - 2].dirty = 1;
6673         draw_rev_graph(graph->prev);
6674         done_rev_graph(graph->prev->prev);
6678 /*
6679  * Main view backend
6680  */
6682 static const char *main_argv[SIZEOF_ARG] = {
6683         "git", "log", "--no-color", "--pretty=raw", "--parents",
6684                       "--topo-order", "%(head)", NULL
6685 };
6687 static bool
6688 main_draw(struct view *view, struct line *line, unsigned int lineno)
6690         struct commit *commit = line->data;
6692         if (!commit->author)
6693                 return FALSE;
6695         if (opt_date && draw_date(view, &commit->time))
6696                 return TRUE;
6698         if (opt_author && draw_author(view, commit->author))
6699                 return TRUE;
6701         if (opt_rev_graph && commit->graph_size &&
6702             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6703                 return TRUE;
6705         if (opt_show_refs && commit->refs) {
6706                 size_t i;
6708                 for (i = 0; i < commit->refs->size; i++) {
6709                         struct ref *ref = commit->refs->refs[i];
6710                         enum line_type type;
6712                         if (ref->head)
6713                                 type = LINE_MAIN_HEAD;
6714                         else if (ref->ltag)
6715                                 type = LINE_MAIN_LOCAL_TAG;
6716                         else if (ref->tag)
6717                                 type = LINE_MAIN_TAG;
6718                         else if (ref->tracked)
6719                                 type = LINE_MAIN_TRACKED;
6720                         else if (ref->remote)
6721                                 type = LINE_MAIN_REMOTE;
6722                         else
6723                                 type = LINE_MAIN_REF;
6725                         if (draw_text(view, type, "[", TRUE) ||
6726                             draw_text(view, type, ref->name, TRUE) ||
6727                             draw_text(view, type, "]", TRUE))
6728                                 return TRUE;
6730                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6731                                 return TRUE;
6732                 }
6733         }
6735         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6736         return TRUE;
6739 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6740 static bool
6741 main_read(struct view *view, char *line)
6743         static struct rev_graph *graph = graph_stacks;
6744         enum line_type type;
6745         struct commit *commit;
6747         if (!line) {
6748                 int i;
6750                 if (!view->lines && !view->parent)
6751                         die("No revisions match the given arguments.");
6752                 if (view->lines > 0) {
6753                         commit = view->line[view->lines - 1].data;
6754                         view->line[view->lines - 1].dirty = 1;
6755                         if (!commit->author) {
6756                                 view->lines--;
6757                                 free(commit);
6758                                 graph->commit = NULL;
6759                         }
6760                 }
6761                 update_rev_graph(view, graph);
6763                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6764                         clear_rev_graph(&graph_stacks[i]);
6765                 return TRUE;
6766         }
6768         type = get_line_type(line);
6769         if (type == LINE_COMMIT) {
6770                 commit = calloc(1, sizeof(struct commit));
6771                 if (!commit)
6772                         return FALSE;
6774                 line += STRING_SIZE("commit ");
6775                 if (*line == '-') {
6776                         graph->boundary = 1;
6777                         line++;
6778                 }
6780                 string_copy_rev(commit->id, line);
6781                 commit->refs = get_ref_list(commit->id);
6782                 graph->commit = commit;
6783                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6785                 while ((line = strchr(line, ' '))) {
6786                         line++;
6787                         push_rev_graph(graph->parents, line);
6788                         commit->has_parents = TRUE;
6789                 }
6790                 return TRUE;
6791         }
6793         if (!view->lines)
6794                 return TRUE;
6795         commit = view->line[view->lines - 1].data;
6797         switch (type) {
6798         case LINE_PARENT:
6799                 if (commit->has_parents)
6800                         break;
6801                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6802                 break;
6804         case LINE_AUTHOR:
6805                 parse_author_line(line + STRING_SIZE("author "),
6806                                   &commit->author, &commit->time);
6807                 update_rev_graph(view, graph);
6808                 graph = graph->next;
6809                 break;
6811         default:
6812                 /* Fill in the commit title if it has not already been set. */
6813                 if (commit->title[0])
6814                         break;
6816                 /* Require titles to start with a non-space character at the
6817                  * offset used by git log. */
6818                 if (strncmp(line, "    ", 4))
6819                         break;
6820                 line += 4;
6821                 /* Well, if the title starts with a whitespace character,
6822                  * try to be forgiving.  Otherwise we end up with no title. */
6823                 while (isspace(*line))
6824                         line++;
6825                 if (*line == '\0')
6826                         break;
6827                 /* FIXME: More graceful handling of titles; append "..." to
6828                  * shortened titles, etc. */
6830                 string_expand(commit->title, sizeof(commit->title), line, 1);
6831                 view->line[view->lines - 1].dirty = 1;
6832         }
6834         return TRUE;
6837 static enum request
6838 main_request(struct view *view, enum request request, struct line *line)
6840         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6842         switch (request) {
6843         case REQ_ENTER:
6844                 open_view(view, REQ_VIEW_DIFF, flags);
6845                 break;
6846         case REQ_REFRESH:
6847                 load_refs();
6848                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6849                 break;
6850         default:
6851                 return request;
6852         }
6854         return REQ_NONE;
6857 static bool
6858 grep_refs(struct ref_list *list, regex_t *regex)
6860         regmatch_t pmatch;
6861         size_t i;
6863         if (!opt_show_refs || !list)
6864                 return FALSE;
6866         for (i = 0; i < list->size; i++) {
6867                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6868                         return TRUE;
6869         }
6871         return FALSE;
6874 static bool
6875 main_grep(struct view *view, struct line *line)
6877         struct commit *commit = line->data;
6878         const char *text[] = {
6879                 commit->title,
6880                 opt_author ? commit->author : "",
6881                 mkdate(&commit->time, opt_date),
6882                 NULL
6883         };
6885         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6888 static void
6889 main_select(struct view *view, struct line *line)
6891         struct commit *commit = line->data;
6893         string_copy_rev(view->ref, commit->id);
6894         string_copy_rev(ref_commit, view->ref);
6897 static struct view_ops main_ops = {
6898         "commit",
6899         main_argv,
6900         NULL,
6901         main_read,
6902         main_draw,
6903         main_request,
6904         main_grep,
6905         main_select,
6906 };
6909 /*
6910  * Status management
6911  */
6913 /* Whether or not the curses interface has been initialized. */
6914 static bool cursed = FALSE;
6916 /* Terminal hacks and workarounds. */
6917 static bool use_scroll_redrawwin;
6918 static bool use_scroll_status_wclear;
6920 /* The status window is used for polling keystrokes. */
6921 static WINDOW *status_win;
6923 /* Reading from the prompt? */
6924 static bool input_mode = FALSE;
6926 static bool status_empty = FALSE;
6928 /* Update status and title window. */
6929 static void
6930 report(const char *msg, ...)
6932         struct view *view = display[current_view];
6934         if (input_mode)
6935                 return;
6937         if (!view) {
6938                 char buf[SIZEOF_STR];
6939                 va_list args;
6941                 va_start(args, msg);
6942                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6943                         buf[sizeof(buf) - 1] = 0;
6944                         buf[sizeof(buf) - 2] = '.';
6945                         buf[sizeof(buf) - 3] = '.';
6946                         buf[sizeof(buf) - 4] = '.';
6947                 }
6948                 va_end(args);
6949                 die("%s", buf);
6950         }
6952         if (!status_empty || *msg) {
6953                 va_list args;
6955                 va_start(args, msg);
6957                 wmove(status_win, 0, 0);
6958                 if (view->has_scrolled && use_scroll_status_wclear)
6959                         wclear(status_win);
6960                 if (*msg) {
6961                         vwprintw(status_win, msg, args);
6962                         status_empty = FALSE;
6963                 } else {
6964                         status_empty = TRUE;
6965                 }
6966                 wclrtoeol(status_win);
6967                 wnoutrefresh(status_win);
6969                 va_end(args);
6970         }
6972         update_view_title(view);
6975 static void
6976 init_display(void)
6978         const char *term;
6979         int x, y;
6981         /* Initialize the curses library */
6982         if (isatty(STDIN_FILENO)) {
6983                 cursed = !!initscr();
6984                 opt_tty = stdin;
6985         } else {
6986                 /* Leave stdin and stdout alone when acting as a pager. */
6987                 opt_tty = fopen("/dev/tty", "r+");
6988                 if (!opt_tty)
6989                         die("Failed to open /dev/tty");
6990                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6991         }
6993         if (!cursed)
6994                 die("Failed to initialize curses");
6996         nonl();         /* Disable conversion and detect newlines from input. */
6997         cbreak();       /* Take input chars one at a time, no wait for \n */
6998         noecho();       /* Don't echo input */
6999         leaveok(stdscr, FALSE);
7001         if (has_colors())
7002                 init_colors();
7004         getmaxyx(stdscr, y, x);
7005         status_win = newwin(1, 0, y - 1, 0);
7006         if (!status_win)
7007                 die("Failed to create status window");
7009         /* Enable keyboard mapping */
7010         keypad(status_win, TRUE);
7011         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7013         TABSIZE = opt_tab_size;
7015         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7016         if (term && !strcmp(term, "gnome-terminal")) {
7017                 /* In the gnome-terminal-emulator, the message from
7018                  * scrolling up one line when impossible followed by
7019                  * scrolling down one line causes corruption of the
7020                  * status line. This is fixed by calling wclear. */
7021                 use_scroll_status_wclear = TRUE;
7022                 use_scroll_redrawwin = FALSE;
7024         } else if (term && !strcmp(term, "xrvt-xpm")) {
7025                 /* No problems with full optimizations in xrvt-(unicode)
7026                  * and aterm. */
7027                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7029         } else {
7030                 /* When scrolling in (u)xterm the last line in the
7031                  * scrolling direction will update slowly. */
7032                 use_scroll_redrawwin = TRUE;
7033                 use_scroll_status_wclear = FALSE;
7034         }
7037 static int
7038 get_input(int prompt_position)
7040         struct view *view;
7041         int i, key, cursor_y, cursor_x;
7042         bool loading = FALSE;
7044         if (prompt_position)
7045                 input_mode = TRUE;
7047         while (TRUE) {
7048                 foreach_view (view, i) {
7049                         update_view(view);
7050                         if (view_is_displayed(view) && view->has_scrolled &&
7051                             use_scroll_redrawwin)
7052                                 redrawwin(view->win);
7053                         view->has_scrolled = FALSE;
7054                         if (view->pipe)
7055                                 loading = TRUE;
7056                 }
7058                 /* Update the cursor position. */
7059                 if (prompt_position) {
7060                         getbegyx(status_win, cursor_y, cursor_x);
7061                         cursor_x = prompt_position;
7062                 } else {
7063                         view = display[current_view];
7064                         getbegyx(view->win, cursor_y, cursor_x);
7065                         cursor_x = view->width - 1;
7066                         cursor_y += view->lineno - view->offset;
7067                 }
7068                 setsyx(cursor_y, cursor_x);
7070                 /* Refresh, accept single keystroke of input */
7071                 doupdate();
7072                 nodelay(status_win, loading);
7073                 key = wgetch(status_win);
7075                 /* wgetch() with nodelay() enabled returns ERR when
7076                  * there's no input. */
7077                 if (key == ERR) {
7079                 } else if (key == KEY_RESIZE) {
7080                         int height, width;
7082                         getmaxyx(stdscr, height, width);
7084                         wresize(status_win, 1, width);
7085                         mvwin(status_win, height - 1, 0);
7086                         wnoutrefresh(status_win);
7087                         resize_display();
7088                         redraw_display(TRUE);
7090                 } else {
7091                         input_mode = FALSE;
7092                         return key;
7093                 }
7094         }
7097 static char *
7098 prompt_input(const char *prompt, input_handler handler, void *data)
7100         enum input_status status = INPUT_OK;
7101         static char buf[SIZEOF_STR];
7102         size_t pos = 0;
7104         buf[pos] = 0;
7106         while (status == INPUT_OK || status == INPUT_SKIP) {
7107                 int key;
7109                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7110                 wclrtoeol(status_win);
7112                 key = get_input(pos + 1);
7113                 switch (key) {
7114                 case KEY_RETURN:
7115                 case KEY_ENTER:
7116                 case '\n':
7117                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7118                         break;
7120                 case KEY_BACKSPACE:
7121                         if (pos > 0)
7122                                 buf[--pos] = 0;
7123                         else
7124                                 status = INPUT_CANCEL;
7125                         break;
7127                 case KEY_ESC:
7128                         status = INPUT_CANCEL;
7129                         break;
7131                 default:
7132                         if (pos >= sizeof(buf)) {
7133                                 report("Input string too long");
7134                                 return NULL;
7135                         }
7137                         status = handler(data, buf, key);
7138                         if (status == INPUT_OK)
7139                                 buf[pos++] = (char) key;
7140                 }
7141         }
7143         /* Clear the status window */
7144         status_empty = FALSE;
7145         report("");
7147         if (status == INPUT_CANCEL)
7148                 return NULL;
7150         buf[pos++] = 0;
7152         return buf;
7155 static enum input_status
7156 prompt_yesno_handler(void *data, char *buf, int c)
7158         if (c == 'y' || c == 'Y')
7159                 return INPUT_STOP;
7160         if (c == 'n' || c == 'N')
7161                 return INPUT_CANCEL;
7162         return INPUT_SKIP;
7165 static bool
7166 prompt_yesno(const char *prompt)
7168         char prompt2[SIZEOF_STR];
7170         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7171                 return FALSE;
7173         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7176 static enum input_status
7177 read_prompt_handler(void *data, char *buf, int c)
7179         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7182 static char *
7183 read_prompt(const char *prompt)
7185         return prompt_input(prompt, read_prompt_handler, NULL);
7188 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7190         enum input_status status = INPUT_OK;
7191         int size = 0;
7193         while (items[size].text)
7194                 size++;
7196         while (status == INPUT_OK) {
7197                 const struct menu_item *item = &items[*selected];
7198                 int key;
7199                 int i;
7201                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7202                           prompt, *selected + 1, size);
7203                 if (item->hotkey)
7204                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7205                 wprintw(status_win, "%s", item->text);
7206                 wclrtoeol(status_win);
7208                 key = get_input(COLS - 1);
7209                 switch (key) {
7210                 case KEY_RETURN:
7211                 case KEY_ENTER:
7212                 case '\n':
7213                         status = INPUT_STOP;
7214                         break;
7216                 case KEY_LEFT:
7217                 case KEY_UP:
7218                         *selected = *selected - 1;
7219                         if (*selected < 0)
7220                                 *selected = size - 1;
7221                         break;
7223                 case KEY_RIGHT:
7224                 case KEY_DOWN:
7225                         *selected = (*selected + 1) % size;
7226                         break;
7228                 case KEY_ESC:
7229                         status = INPUT_CANCEL;
7230                         break;
7232                 default:
7233                         for (i = 0; items[i].text; i++)
7234                                 if (items[i].hotkey == key) {
7235                                         *selected = i;
7236                                         status = INPUT_STOP;
7237                                         break;
7238                                 }
7239                 }
7240         }
7242         /* Clear the status window */
7243         status_empty = FALSE;
7244         report("");
7246         return status != INPUT_CANCEL;
7249 /*
7250  * Repository properties
7251  */
7253 static struct ref **refs = NULL;
7254 static size_t refs_size = 0;
7255 static struct ref *refs_head = NULL;
7257 static struct ref_list **ref_lists = NULL;
7258 static size_t ref_lists_size = 0;
7260 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7261 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7262 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7264 static int
7265 compare_refs(const void *ref1_, const void *ref2_)
7267         const struct ref *ref1 = *(const struct ref **)ref1_;
7268         const struct ref *ref2 = *(const struct ref **)ref2_;
7270         if (ref1->tag != ref2->tag)
7271                 return ref2->tag - ref1->tag;
7272         if (ref1->ltag != ref2->ltag)
7273                 return ref2->ltag - ref2->ltag;
7274         if (ref1->head != ref2->head)
7275                 return ref2->head - ref1->head;
7276         if (ref1->tracked != ref2->tracked)
7277                 return ref2->tracked - ref1->tracked;
7278         if (ref1->remote != ref2->remote)
7279                 return ref2->remote - ref1->remote;
7280         return strcmp(ref1->name, ref2->name);
7283 static void
7284 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7286         size_t i;
7288         for (i = 0; i < refs_size; i++)
7289                 if (!visitor(data, refs[i]))
7290                         break;
7293 static struct ref *
7294 get_ref_head()
7296         return refs_head;
7299 static struct ref_list *
7300 get_ref_list(const char *id)
7302         struct ref_list *list;
7303         size_t i;
7305         for (i = 0; i < ref_lists_size; i++)
7306                 if (!strcmp(id, ref_lists[i]->id))
7307                         return ref_lists[i];
7309         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7310                 return NULL;
7311         list = calloc(1, sizeof(*list));
7312         if (!list)
7313                 return NULL;
7315         for (i = 0; i < refs_size; i++) {
7316                 if (!strcmp(id, refs[i]->id) &&
7317                     realloc_refs_list(&list->refs, list->size, 1))
7318                         list->refs[list->size++] = refs[i];
7319         }
7321         if (!list->refs) {
7322                 free(list);
7323                 return NULL;
7324         }
7326         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7327         ref_lists[ref_lists_size++] = list;
7328         return list;
7331 static int
7332 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7334         struct ref *ref = NULL;
7335         bool tag = FALSE;
7336         bool ltag = FALSE;
7337         bool remote = FALSE;
7338         bool tracked = FALSE;
7339         bool head = FALSE;
7340         int from = 0, to = refs_size - 1;
7342         if (!prefixcmp(name, "refs/tags/")) {
7343                 if (!suffixcmp(name, namelen, "^{}")) {
7344                         namelen -= 3;
7345                         name[namelen] = 0;
7346                 } else {
7347                         ltag = TRUE;
7348                 }
7350                 tag = TRUE;
7351                 namelen -= STRING_SIZE("refs/tags/");
7352                 name    += STRING_SIZE("refs/tags/");
7354         } else if (!prefixcmp(name, "refs/remotes/")) {
7355                 remote = TRUE;
7356                 namelen -= STRING_SIZE("refs/remotes/");
7357                 name    += STRING_SIZE("refs/remotes/");
7358                 tracked  = !strcmp(opt_remote, name);
7360         } else if (!prefixcmp(name, "refs/heads/")) {
7361                 namelen -= STRING_SIZE("refs/heads/");
7362                 name    += STRING_SIZE("refs/heads/");
7363                 if (!strncmp(opt_head, name, namelen))
7364                         return OK;
7366         } else if (!strcmp(name, "HEAD")) {
7367                 head     = TRUE;
7368                 if (*opt_head) {
7369                         namelen  = strlen(opt_head);
7370                         name     = opt_head;
7371                 }
7372         }
7374         /* If we are reloading or it's an annotated tag, replace the
7375          * previous SHA1 with the resolved commit id; relies on the fact
7376          * git-ls-remote lists the commit id of an annotated tag right
7377          * before the commit id it points to. */
7378         while (from <= to) {
7379                 size_t pos = (to + from) / 2;
7380                 int cmp = strcmp(name, refs[pos]->name);
7382                 if (!cmp) {
7383                         ref = refs[pos];
7384                         break;
7385                 }
7387                 if (cmp < 0)
7388                         to = pos - 1;
7389                 else
7390                         from = pos + 1;
7391         }
7393         if (!ref) {
7394                 if (!realloc_refs(&refs, refs_size, 1))
7395                         return ERR;
7396                 ref = calloc(1, sizeof(*ref) + namelen);
7397                 if (!ref)
7398                         return ERR;
7399                 memmove(refs + from + 1, refs + from,
7400                         (refs_size - from) * sizeof(*refs));
7401                 refs[from] = ref;
7402                 strncpy(ref->name, name, namelen);
7403                 refs_size++;
7404         }
7406         ref->head = head;
7407         ref->tag = tag;
7408         ref->ltag = ltag;
7409         ref->remote = remote;
7410         ref->tracked = tracked;
7411         string_copy_rev(ref->id, id);
7413         if (head)
7414                 refs_head = ref;
7415         return OK;
7418 static int
7419 load_refs(void)
7421         const char *head_argv[] = {
7422                 "git", "symbolic-ref", "HEAD", NULL
7423         };
7424         static const char *ls_remote_argv[SIZEOF_ARG] = {
7425                 "git", "ls-remote", opt_git_dir, NULL
7426         };
7427         static bool init = FALSE;
7428         size_t i;
7430         if (!init) {
7431                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7432                 init = TRUE;
7433         }
7435         if (!*opt_git_dir)
7436                 return OK;
7438         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7439             !prefixcmp(opt_head, "refs/heads/")) {
7440                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7442                 memmove(opt_head, offset, strlen(offset) + 1);
7443         }
7445         refs_head = NULL;
7446         for (i = 0; i < refs_size; i++)
7447                 refs[i]->id[0] = 0;
7449         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7450                 return ERR;
7452         /* Update the ref lists to reflect changes. */
7453         for (i = 0; i < ref_lists_size; i++) {
7454                 struct ref_list *list = ref_lists[i];
7455                 size_t old, new;
7457                 for (old = new = 0; old < list->size; old++)
7458                         if (!strcmp(list->id, list->refs[old]->id))
7459                                 list->refs[new++] = list->refs[old];
7460                 list->size = new;
7461         }
7463         return OK;
7466 static void
7467 set_remote_branch(const char *name, const char *value, size_t valuelen)
7469         if (!strcmp(name, ".remote")) {
7470                 string_ncopy(opt_remote, value, valuelen);
7472         } else if (*opt_remote && !strcmp(name, ".merge")) {
7473                 size_t from = strlen(opt_remote);
7475                 if (!prefixcmp(value, "refs/heads/"))
7476                         value += STRING_SIZE("refs/heads/");
7478                 if (!string_format_from(opt_remote, &from, "/%s", value))
7479                         opt_remote[0] = 0;
7480         }
7483 static void
7484 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7486         const char *argv[SIZEOF_ARG] = { name, "=" };
7487         int argc = 1 + (cmd == option_set_command);
7488         int error = ERR;
7490         if (!argv_from_string(argv, &argc, value))
7491                 config_msg = "Too many option arguments";
7492         else
7493                 error = cmd(argc, argv);
7495         if (error == ERR)
7496                 warn("Option 'tig.%s': %s", name, config_msg);
7499 static bool
7500 set_environment_variable(const char *name, const char *value)
7502         size_t len = strlen(name) + 1 + strlen(value) + 1;
7503         char *env = malloc(len);
7505         if (env &&
7506             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7507             putenv(env) == 0)
7508                 return TRUE;
7509         free(env);
7510         return FALSE;
7513 static void
7514 set_work_tree(const char *value)
7516         char cwd[SIZEOF_STR];
7518         if (!getcwd(cwd, sizeof(cwd)))
7519                 die("Failed to get cwd path: %s", strerror(errno));
7520         if (chdir(opt_git_dir) < 0)
7521                 die("Failed to chdir(%s): %s", strerror(errno));
7522         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7523                 die("Failed to get git path: %s", strerror(errno));
7524         if (chdir(cwd) < 0)
7525                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7526         if (chdir(value) < 0)
7527                 die("Failed to chdir(%s): %s", value, strerror(errno));
7528         if (!getcwd(cwd, sizeof(cwd)))
7529                 die("Failed to get cwd path: %s", strerror(errno));
7530         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7531                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7532         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7533                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7534         opt_is_inside_work_tree = TRUE;
7537 static int
7538 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7540         if (!strcmp(name, "i18n.commitencoding"))
7541                 string_ncopy(opt_encoding, value, valuelen);
7543         else if (!strcmp(name, "core.editor"))
7544                 string_ncopy(opt_editor, value, valuelen);
7546         else if (!strcmp(name, "core.worktree"))
7547                 set_work_tree(value);
7549         else if (!prefixcmp(name, "tig.color."))
7550                 set_repo_config_option(name + 10, value, option_color_command);
7552         else if (!prefixcmp(name, "tig.bind."))
7553                 set_repo_config_option(name + 9, value, option_bind_command);
7555         else if (!prefixcmp(name, "tig."))
7556                 set_repo_config_option(name + 4, value, option_set_command);
7558         else if (*opt_head && !prefixcmp(name, "branch.") &&
7559                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7560                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7562         return OK;
7565 static int
7566 load_git_config(void)
7568         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7570         return io_run_load(config_list_argv, "=", read_repo_config_option);
7573 static int
7574 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7576         if (!opt_git_dir[0]) {
7577                 string_ncopy(opt_git_dir, name, namelen);
7579         } else if (opt_is_inside_work_tree == -1) {
7580                 /* This can be 3 different values depending on the
7581                  * version of git being used. If git-rev-parse does not
7582                  * understand --is-inside-work-tree it will simply echo
7583                  * the option else either "true" or "false" is printed.
7584                  * Default to true for the unknown case. */
7585                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7587         } else if (*name == '.') {
7588                 string_ncopy(opt_cdup, name, namelen);
7590         } else {
7591                 string_ncopy(opt_prefix, name, namelen);
7592         }
7594         return OK;
7597 static int
7598 load_repo_info(void)
7600         const char *rev_parse_argv[] = {
7601                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7602                         "--show-cdup", "--show-prefix", NULL
7603         };
7605         return io_run_load(rev_parse_argv, "=", read_repo_info);
7609 /*
7610  * Main
7611  */
7613 static const char usage[] =
7614 "tig " TIG_VERSION " (" __DATE__ ")\n"
7615 "\n"
7616 "Usage: tig        [options] [revs] [--] [paths]\n"
7617 "   or: tig show   [options] [revs] [--] [paths]\n"
7618 "   or: tig blame  [rev] path\n"
7619 "   or: tig status\n"
7620 "   or: tig <      [git command output]\n"
7621 "\n"
7622 "Options:\n"
7623 "  -v, --version   Show version and exit\n"
7624 "  -h, --help      Show help message and exit";
7626 static void __NORETURN
7627 quit(int sig)
7629         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7630         if (cursed)
7631                 endwin();
7632         exit(0);
7635 static void __NORETURN
7636 die(const char *err, ...)
7638         va_list args;
7640         endwin();
7642         va_start(args, err);
7643         fputs("tig: ", stderr);
7644         vfprintf(stderr, err, args);
7645         fputs("\n", stderr);
7646         va_end(args);
7648         exit(1);
7651 static void
7652 warn(const char *msg, ...)
7654         va_list args;
7656         va_start(args, msg);
7657         fputs("tig warning: ", stderr);
7658         vfprintf(stderr, msg, args);
7659         fputs("\n", stderr);
7660         va_end(args);
7663 static enum request
7664 parse_options(int argc, const char *argv[])
7666         enum request request = REQ_VIEW_MAIN;
7667         const char *subcommand;
7668         bool seen_dashdash = FALSE;
7669         /* XXX: This is vulnerable to the user overriding options
7670          * required for the main view parser. */
7671         const char *custom_argv[SIZEOF_ARG] = {
7672                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7673                         "--topo-order", NULL
7674         };
7675         int i, j = 6;
7677         if (!isatty(STDIN_FILENO)) {
7678                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7679                 return REQ_VIEW_PAGER;
7680         }
7682         if (argc <= 1)
7683                 return REQ_NONE;
7685         subcommand = argv[1];
7686         if (!strcmp(subcommand, "status")) {
7687                 if (argc > 2)
7688                         warn("ignoring arguments after `%s'", subcommand);
7689                 return REQ_VIEW_STATUS;
7691         } else if (!strcmp(subcommand, "blame")) {
7692                 if (argc <= 2 || argc > 4)
7693                         die("invalid number of options to blame\n\n%s", usage);
7695                 i = 2;
7696                 if (argc == 4) {
7697                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7698                         i++;
7699                 }
7701                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7702                 return REQ_VIEW_BLAME;
7704         } else if (!strcmp(subcommand, "show")) {
7705                 request = REQ_VIEW_DIFF;
7707         } else {
7708                 subcommand = NULL;
7709         }
7711         if (subcommand) {
7712                 custom_argv[1] = subcommand;
7713                 j = 2;
7714         }
7716         for (i = 1 + !!subcommand; i < argc; i++) {
7717                 const char *opt = argv[i];
7719                 if (seen_dashdash || !strcmp(opt, "--")) {
7720                         seen_dashdash = TRUE;
7722                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7723                         printf("tig version %s\n", TIG_VERSION);
7724                         quit(0);
7726                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7727                         printf("%s\n", usage);
7728                         quit(0);
7729                 }
7731                 custom_argv[j++] = opt;
7732                 if (j >= ARRAY_SIZE(custom_argv))
7733                         die("command too long");
7734         }
7736         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7737                 die("Failed to format arguments");
7739         return request;
7742 int
7743 main(int argc, const char *argv[])
7745         const char *codeset = "UTF-8";
7746         enum request request = parse_options(argc, argv);
7747         struct view *view;
7748         size_t i;
7750         signal(SIGINT, quit);
7751         signal(SIGPIPE, SIG_IGN);
7753         if (setlocale(LC_ALL, "")) {
7754                 codeset = nl_langinfo(CODESET);
7755         }
7757         if (load_repo_info() == ERR)
7758                 die("Failed to load repo info.");
7760         if (load_options() == ERR)
7761                 die("Failed to load user config.");
7763         if (load_git_config() == ERR)
7764                 die("Failed to load repo config.");
7766         /* Require a git repository unless when running in pager mode. */
7767         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7768                 die("Not a git repository");
7770         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7771                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7772                 if (opt_iconv_in == ICONV_NONE)
7773                         die("Failed to initialize character set conversion");
7774         }
7776         if (codeset && strcmp(codeset, "UTF-8")) {
7777                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7778                 if (opt_iconv_out == ICONV_NONE)
7779                         die("Failed to initialize character set conversion");
7780         }
7782         if (load_refs() == ERR)
7783                 die("Failed to load refs.");
7785         foreach_view (view, i)
7786                 argv_from_env(view->ops->argv, view->cmd_env);
7788         init_display();
7790         if (request != REQ_NONE)
7791                 open_view(NULL, request, OPEN_PREPARED);
7792         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7794         while (view_driver(display[current_view], request)) {
7795                 int key = get_input(0);
7797                 view = display[current_view];
7798                 request = get_keybinding(view->keymap, key);
7800                 /* Some low-level request handling. This keeps access to
7801                  * status_win restricted. */
7802                 switch (request) {
7803                 case REQ_PROMPT:
7804                 {
7805                         char *cmd = read_prompt(":");
7807                         if (cmd && isdigit(*cmd)) {
7808                                 int lineno = view->lineno + 1;
7810                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7811                                         select_view_line(view, lineno - 1);
7812                                         report("");
7813                                 } else {
7814                                         report("Unable to parse '%s' as a line number", cmd);
7815                                 }
7817                         } else if (cmd) {
7818                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7819                                 const char *argv[SIZEOF_ARG] = { "git" };
7820                                 int argc = 1;
7822                                 /* When running random commands, initially show the
7823                                  * command in the title. However, it maybe later be
7824                                  * overwritten if a commit line is selected. */
7825                                 string_ncopy(next->ref, cmd, strlen(cmd));
7827                                 if (!argv_from_string(argv, &argc, cmd)) {
7828                                         report("Too many arguments");
7829                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7830                                         report("Failed to format command");
7831                                 } else {
7832                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7833                                 }
7834                         }
7836                         request = REQ_NONE;
7837                         break;
7838                 }
7839                 case REQ_SEARCH:
7840                 case REQ_SEARCH_BACK:
7841                 {
7842                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7843                         char *search = read_prompt(prompt);
7845                         if (search)
7846                                 string_ncopy(opt_search, search, strlen(search));
7847                         else if (*opt_search)
7848                                 request = request == REQ_SEARCH ?
7849                                         REQ_FIND_NEXT :
7850                                         REQ_FIND_PREV;
7851                         else
7852                                 request = REQ_NONE;
7853                         break;
7854                 }
7855                 default:
7856                         break;
7857                 }
7858         }
7860         quit(0);
7862         return 0;