Code

Branch view: add %(branch) variable tracking currently selected branch
[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 bool
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         return !env || argv_from_string(argv, &argc, env);
676 /*
677  * Executing external commands.
678  */
680 enum io_type {
681         IO_FD,                  /* File descriptor based IO. */
682         IO_BG,                  /* Execute command in the background. */
683         IO_FG,                  /* Execute command with same std{in,out,err}. */
684         IO_RD,                  /* Read only fork+exec IO. */
685         IO_WR,                  /* Write only fork+exec IO. */
686         IO_AP,                  /* Append fork+exec output to file. */
687 };
689 struct io {
690         enum io_type type;      /* The requested type of pipe. */
691         const char *dir;        /* Directory from which to execute. */
692         pid_t pid;              /* PID of spawned process. */
693         int pipe;               /* Pipe end for reading or writing. */
694         int error;              /* Error status. */
695         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
696         char *buf;              /* Read buffer. */
697         size_t bufalloc;        /* Allocated buffer size. */
698         size_t bufsize;         /* Buffer content size. */
699         char *bufpos;           /* Current buffer position. */
700         unsigned int eof:1;     /* Has end of file been reached. */
701 };
703 static void
704 io_reset(struct io *io)
706         io->pipe = -1;
707         io->pid = 0;
708         io->buf = io->bufpos = NULL;
709         io->bufalloc = io->bufsize = 0;
710         io->error = 0;
711         io->eof = 0;
714 static void
715 io_init(struct io *io, const char *dir, enum io_type type)
717         io_reset(io);
718         io->type = type;
719         io->dir = dir;
722 static bool
723 io_format(struct io *io, const char *dir, enum io_type type,
724           const char *argv[], enum format_flags flags)
726         io_init(io, dir, type);
727         return format_argv(io->argv, argv, flags);
730 static bool
731 io_open(struct io *io, const char *fmt, ...)
733         char name[SIZEOF_STR] = "";
734         bool fits;
735         va_list args;
737         io_init(io, NULL, IO_FD);
739         va_start(args, fmt);
740         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
741         va_end(args);
743         if (!fits) {
744                 io->error = ENAMETOOLONG;
745                 return FALSE;
746         }
747         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
748         if (io->pipe == -1)
749                 io->error = errno;
750         return io->pipe != -1;
753 static bool
754 io_kill(struct io *io)
756         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
759 static bool
760 io_done(struct io *io)
762         pid_t pid = io->pid;
764         if (io->pipe != -1)
765                 close(io->pipe);
766         free(io->buf);
767         io_reset(io);
769         while (pid > 0) {
770                 int status;
771                 pid_t waiting = waitpid(pid, &status, 0);
773                 if (waiting < 0) {
774                         if (errno == EINTR)
775                                 continue;
776                         io->error = errno;
777                         return FALSE;
778                 }
780                 return waiting == pid &&
781                        !WIFSIGNALED(status) &&
782                        WIFEXITED(status) &&
783                        !WEXITSTATUS(status);
784         }
786         return TRUE;
789 static bool
790 io_start(struct io *io)
792         int pipefds[2] = { -1, -1 };
794         if (io->type == IO_FD)
795                 return TRUE;
797         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
798                 io->error = errno;
799                 return FALSE;
800         } else if (io->type == IO_AP) {
801                 pipefds[1] = io->pipe;
802         }
804         if ((io->pid = fork())) {
805                 if (io->pid == -1)
806                         io->error = errno;
807                 if (pipefds[!(io->type == IO_WR)] != -1)
808                         close(pipefds[!(io->type == IO_WR)]);
809                 if (io->pid != -1) {
810                         io->pipe = pipefds[!!(io->type == IO_WR)];
811                         return TRUE;
812                 }
814         } else {
815                 if (io->type != IO_FG) {
816                         int devnull = open("/dev/null", O_RDWR);
817                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
818                         int writefd = (io->type == IO_RD || io->type == IO_AP)
819                                                         ? pipefds[1] : devnull;
821                         dup2(readfd,  STDIN_FILENO);
822                         dup2(writefd, STDOUT_FILENO);
823                         dup2(devnull, STDERR_FILENO);
825                         close(devnull);
826                         if (pipefds[0] != -1)
827                                 close(pipefds[0]);
828                         if (pipefds[1] != -1)
829                                 close(pipefds[1]);
830                 }
832                 if (io->dir && *io->dir && chdir(io->dir) == -1)
833                         exit(errno);
835                 execvp(io->argv[0], (char *const*) io->argv);
836                 exit(errno);
837         }
839         if (pipefds[!!(io->type == IO_WR)] != -1)
840                 close(pipefds[!!(io->type == IO_WR)]);
841         return FALSE;
844 static bool
845 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
847         io_init(io, dir, type);
848         if (!format_argv(io->argv, argv, FORMAT_NONE))
849                 return FALSE;
850         return io_start(io);
853 static int
854 io_complete(struct io *io)
856         return io_start(io) && io_done(io);
859 static int
860 io_run_bg(const char **argv)
862         struct io io = {};
864         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
865                 return FALSE;
866         return io_complete(&io);
869 static bool
870 io_run_fg(const char **argv, const char *dir)
872         struct io io = {};
874         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
875                 return FALSE;
876         return io_complete(&io);
879 static bool
880 io_run_append(const char **argv, enum format_flags flags, int fd)
882         struct io io = {};
884         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
885                 close(fd);
886                 return FALSE;
887         }
889         io.pipe = fd;
890         return io_complete(&io);
893 static bool
894 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
896         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
899 static bool
900 io_eof(struct io *io)
902         return io->eof;
905 static int
906 io_error(struct io *io)
908         return io->error;
911 static char *
912 io_strerror(struct io *io)
914         return strerror(io->error);
917 static bool
918 io_can_read(struct io *io)
920         struct timeval tv = { 0, 500 };
921         fd_set fds;
923         FD_ZERO(&fds);
924         FD_SET(io->pipe, &fds);
926         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
929 static ssize_t
930 io_read(struct io *io, void *buf, size_t bufsize)
932         do {
933                 ssize_t readsize = read(io->pipe, buf, bufsize);
935                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
936                         continue;
937                 else if (readsize == -1)
938                         io->error = errno;
939                 else if (readsize == 0)
940                         io->eof = 1;
941                 return readsize;
942         } while (1);
945 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
947 static char *
948 io_get(struct io *io, int c, bool can_read)
950         char *eol;
951         ssize_t readsize;
953         while (TRUE) {
954                 if (io->bufsize > 0) {
955                         eol = memchr(io->bufpos, c, io->bufsize);
956                         if (eol) {
957                                 char *line = io->bufpos;
959                                 *eol = 0;
960                                 io->bufpos = eol + 1;
961                                 io->bufsize -= io->bufpos - line;
962                                 return line;
963                         }
964                 }
966                 if (io_eof(io)) {
967                         if (io->bufsize) {
968                                 io->bufpos[io->bufsize] = 0;
969                                 io->bufsize = 0;
970                                 return io->bufpos;
971                         }
972                         return NULL;
973                 }
975                 if (!can_read)
976                         return NULL;
978                 if (io->bufsize > 0 && io->bufpos > io->buf)
979                         memmove(io->buf, io->bufpos, io->bufsize);
981                 if (io->bufalloc == io->bufsize) {
982                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
983                                 return NULL;
984                         io->bufalloc += BUFSIZ;
985                 }
987                 io->bufpos = io->buf;
988                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
989                 if (io_error(io))
990                         return NULL;
991                 io->bufsize += readsize;
992         }
995 static bool
996 io_write(struct io *io, const void *buf, size_t bufsize)
998         size_t written = 0;
1000         while (!io_error(io) && written < bufsize) {
1001                 ssize_t size;
1003                 size = write(io->pipe, buf + written, bufsize - written);
1004                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1005                         continue;
1006                 else if (size == -1)
1007                         io->error = errno;
1008                 else
1009                         written += size;
1010         }
1012         return written == bufsize;
1015 static bool
1016 io_read_buf(struct io *io, char buf[], size_t bufsize)
1018         char *result = io_get(io, '\n', TRUE);
1020         if (result) {
1021                 result = chomp_string(result);
1022                 string_ncopy_do(buf, bufsize, result, strlen(result));
1023         }
1025         return io_done(io) && result;
1028 static bool
1029 io_run_buf(const char **argv, char buf[], size_t bufsize)
1031         struct io io = {};
1033         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1034             && io_read_buf(&io, buf, bufsize);
1037 static int
1038 io_load(struct io *io, const char *separators,
1039         int (*read_property)(char *, size_t, char *, size_t))
1041         char *name;
1042         int state = OK;
1044         if (!io_start(io))
1045                 return ERR;
1047         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1048                 char *value;
1049                 size_t namelen;
1050                 size_t valuelen;
1052                 name = chomp_string(name);
1053                 namelen = strcspn(name, separators);
1055                 if (name[namelen]) {
1056                         name[namelen] = 0;
1057                         value = chomp_string(name + namelen + 1);
1058                         valuelen = strlen(value);
1060                 } else {
1061                         value = "";
1062                         valuelen = 0;
1063                 }
1065                 state = read_property(name, namelen, value, valuelen);
1066         }
1068         if (state != ERR && io_error(io))
1069                 state = ERR;
1070         io_done(io);
1072         return state;
1075 static int
1076 io_run_load(const char **argv, const char *separators,
1077             int (*read_property)(char *, size_t, char *, size_t))
1079         struct io io = {};
1081         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1082                 ? io_load(&io, separators, read_property) : ERR;
1086 /*
1087  * User requests
1088  */
1090 #define REQ_INFO \
1091         /* XXX: Keep the view request first and in sync with views[]. */ \
1092         REQ_GROUP("View switching") \
1093         REQ_(VIEW_MAIN,         "Show main view"), \
1094         REQ_(VIEW_DIFF,         "Show diff view"), \
1095         REQ_(VIEW_LOG,          "Show log view"), \
1096         REQ_(VIEW_TREE,         "Show tree view"), \
1097         REQ_(VIEW_BLOB,         "Show blob view"), \
1098         REQ_(VIEW_BLAME,        "Show blame view"), \
1099         REQ_(VIEW_BRANCH,       "Show branch view"), \
1100         REQ_(VIEW_HELP,         "Show help page"), \
1101         REQ_(VIEW_PAGER,        "Show pager view"), \
1102         REQ_(VIEW_STATUS,       "Show status view"), \
1103         REQ_(VIEW_STAGE,        "Show stage view"), \
1104         \
1105         REQ_GROUP("View manipulation") \
1106         REQ_(ENTER,             "Enter current line and scroll"), \
1107         REQ_(NEXT,              "Move to next"), \
1108         REQ_(PREVIOUS,          "Move to previous"), \
1109         REQ_(PARENT,            "Move to parent"), \
1110         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1111         REQ_(REFRESH,           "Reload and refresh"), \
1112         REQ_(MAXIMIZE,          "Maximize the current view"), \
1113         REQ_(VIEW_CLOSE,        "Close the current view"), \
1114         REQ_(QUIT,              "Close all views and quit"), \
1115         \
1116         REQ_GROUP("View specific requests") \
1117         REQ_(STATUS_UPDATE,     "Update file status"), \
1118         REQ_(STATUS_REVERT,     "Revert file changes"), \
1119         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1120         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1121         \
1122         REQ_GROUP("Cursor navigation") \
1123         REQ_(MOVE_UP,           "Move cursor one line up"), \
1124         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1125         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1126         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1127         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1128         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1129         \
1130         REQ_GROUP("Scrolling") \
1131         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1132         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1133         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1134         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1135         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1136         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1137         \
1138         REQ_GROUP("Searching") \
1139         REQ_(SEARCH,            "Search the view"), \
1140         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1141         REQ_(FIND_NEXT,         "Find next search match"), \
1142         REQ_(FIND_PREV,         "Find previous search match"), \
1143         \
1144         REQ_GROUP("Option manipulation") \
1145         REQ_(OPTIONS,           "Open option menu"), \
1146         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1147         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1148         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1149         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1150         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1151         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1152         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1153         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1154         \
1155         REQ_GROUP("Misc") \
1156         REQ_(PROMPT,            "Bring up the prompt"), \
1157         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1158         REQ_(SHOW_VERSION,      "Show version information"), \
1159         REQ_(STOP_LOADING,      "Stop all loading views"), \
1160         REQ_(EDIT,              "Open in editor"), \
1161         REQ_(NONE,              "Do nothing")
1164 /* User action requests. */
1165 enum request {
1166 #define REQ_GROUP(help)
1167 #define REQ_(req, help) REQ_##req
1169         /* Offset all requests to avoid conflicts with ncurses getch values. */
1170         REQ_OFFSET = KEY_MAX + 1,
1171         REQ_INFO
1173 #undef  REQ_GROUP
1174 #undef  REQ_
1175 };
1177 struct request_info {
1178         enum request request;
1179         const char *name;
1180         int namelen;
1181         const char *help;
1182 };
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187         REQ_INFO
1188 #undef  REQ_GROUP
1189 #undef  REQ_
1190 };
1192 static enum request
1193 get_request(const char *name)
1195         int namelen = strlen(name);
1196         int i;
1198         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199                 if (enum_equals(req_info[i], name, namelen))
1200                         return req_info[i].request;
1202         return REQ_NONE;
1206 /*
1207  * Options
1208  */
1210 /* Option and state variables. */
1211 static enum date opt_date               = DATE_DEFAULT;
1212 static enum author opt_author           = AUTHOR_DEFAULT;
1213 static bool opt_line_number             = FALSE;
1214 static bool opt_line_graphics           = TRUE;
1215 static bool opt_rev_graph               = FALSE;
1216 static bool opt_show_refs               = TRUE;
1217 static int opt_num_interval             = 5;
1218 static double opt_hscroll               = 0.50;
1219 static double opt_scale_split_view      = 2.0 / 3.0;
1220 static int opt_tab_size                 = 8;
1221 static int opt_author_cols              = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR]        = "";
1223 static char opt_file[SIZEOF_STR]        = "";
1224 static char opt_ref[SIZEOF_REF]         = "";
1225 static char opt_head[SIZEOF_REF]        = "";
1226 static char opt_remote[SIZEOF_REF]      = "";
1227 static char opt_encoding[20]            = "UTF-8";
1228 static iconv_t opt_iconv_in             = ICONV_NONE;
1229 static iconv_t opt_iconv_out            = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR]      = "";
1231 static char opt_cdup[SIZEOF_STR]        = "";
1232 static char opt_prefix[SIZEOF_STR]      = "";
1233 static char opt_git_dir[SIZEOF_STR]     = "";
1234 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR]      = "";
1236 static FILE *opt_tty                    = NULL;
1238 #define is_initial_commit()     (!get_ref_head())
1239 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1242 /*
1243  * Line-oriented content detection.
1244  */
1246 #define LINE_INFO \
1247 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1248 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1249 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1250 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1251 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1252 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1253 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1254 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1255 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1261 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1262 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1263 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1264 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1265 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1266 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1267 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1268 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1269 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1270 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1271 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1272 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1273 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1275 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1276 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1277 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1278 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1279 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1280 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1281 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1282 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1283 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1284 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1285 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1286 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1287 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1288 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1289 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1290 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1291 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1292 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1293 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1294 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1295 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1296 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1297 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1298 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1299 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1301 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1302 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1303 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1305 enum line_type {
1306 #define LINE(type, line, fg, bg, attr) \
1307         LINE_##type
1308         LINE_INFO,
1309         LINE_NONE
1310 #undef  LINE
1311 };
1313 struct line_info {
1314         const char *name;       /* Option name. */
1315         int namelen;            /* Size of option name. */
1316         const char *line;       /* The start of line to match. */
1317         int linelen;            /* Size of string to match. */
1318         int fg, bg, attr;       /* Color and text attributes for the lines. */
1319 };
1321 static struct line_info line_info[] = {
1322 #define LINE(type, line, fg, bg, attr) \
1323         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1324         LINE_INFO
1325 #undef  LINE
1326 };
1328 static enum line_type
1329 get_line_type(const char *line)
1331         int linelen = strlen(line);
1332         enum line_type type;
1334         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1335                 /* Case insensitive search matches Signed-off-by lines better. */
1336                 if (linelen >= line_info[type].linelen &&
1337                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1338                         return type;
1340         return LINE_DEFAULT;
1343 static inline int
1344 get_line_attr(enum line_type type)
1346         assert(type < ARRAY_SIZE(line_info));
1347         return COLOR_PAIR(type) | line_info[type].attr;
1350 static struct line_info *
1351 get_line_info(const char *name)
1353         size_t namelen = strlen(name);
1354         enum line_type type;
1356         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1357                 if (enum_equals(line_info[type], name, namelen))
1358                         return &line_info[type];
1360         return NULL;
1363 static void
1364 init_colors(void)
1366         int default_bg = line_info[LINE_DEFAULT].bg;
1367         int default_fg = line_info[LINE_DEFAULT].fg;
1368         enum line_type type;
1370         start_color();
1372         if (assume_default_colors(default_fg, default_bg) == ERR) {
1373                 default_bg = COLOR_BLACK;
1374                 default_fg = COLOR_WHITE;
1375         }
1377         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1378                 struct line_info *info = &line_info[type];
1379                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1380                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1382                 init_pair(type, fg, bg);
1383         }
1386 struct line {
1387         enum line_type type;
1389         /* State flags */
1390         unsigned int selected:1;
1391         unsigned int dirty:1;
1392         unsigned int cleareol:1;
1393         unsigned int other:16;
1395         void *data;             /* User data */
1396 };
1399 /*
1400  * Keys
1401  */
1403 struct keybinding {
1404         int alias;
1405         enum request request;
1406 };
1408 static const struct keybinding default_keybindings[] = {
1409         /* View switching */
1410         { 'm',          REQ_VIEW_MAIN },
1411         { 'd',          REQ_VIEW_DIFF },
1412         { 'l',          REQ_VIEW_LOG },
1413         { 't',          REQ_VIEW_TREE },
1414         { 'f',          REQ_VIEW_BLOB },
1415         { 'B',          REQ_VIEW_BLAME },
1416         { 'H',          REQ_VIEW_BRANCH },
1417         { 'p',          REQ_VIEW_PAGER },
1418         { 'h',          REQ_VIEW_HELP },
1419         { 'S',          REQ_VIEW_STATUS },
1420         { 'c',          REQ_VIEW_STAGE },
1422         /* View manipulation */
1423         { 'q',          REQ_VIEW_CLOSE },
1424         { KEY_TAB,      REQ_VIEW_NEXT },
1425         { KEY_RETURN,   REQ_ENTER },
1426         { KEY_UP,       REQ_PREVIOUS },
1427         { KEY_DOWN,     REQ_NEXT },
1428         { 'R',          REQ_REFRESH },
1429         { KEY_F(5),     REQ_REFRESH },
1430         { 'O',          REQ_MAXIMIZE },
1432         /* Cursor navigation */
1433         { 'k',          REQ_MOVE_UP },
1434         { 'j',          REQ_MOVE_DOWN },
1435         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1436         { KEY_END,      REQ_MOVE_LAST_LINE },
1437         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1438         { ' ',          REQ_MOVE_PAGE_DOWN },
1439         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1440         { 'b',          REQ_MOVE_PAGE_UP },
1441         { '-',          REQ_MOVE_PAGE_UP },
1443         /* Scrolling */
1444         { KEY_LEFT,     REQ_SCROLL_LEFT },
1445         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1446         { KEY_IC,       REQ_SCROLL_LINE_UP },
1447         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1448         { 'w',          REQ_SCROLL_PAGE_UP },
1449         { 's',          REQ_SCROLL_PAGE_DOWN },
1451         /* Searching */
1452         { '/',          REQ_SEARCH },
1453         { '?',          REQ_SEARCH_BACK },
1454         { 'n',          REQ_FIND_NEXT },
1455         { 'N',          REQ_FIND_PREV },
1457         /* Misc */
1458         { 'Q',          REQ_QUIT },
1459         { 'z',          REQ_STOP_LOADING },
1460         { 'v',          REQ_SHOW_VERSION },
1461         { 'r',          REQ_SCREEN_REDRAW },
1462         { 'o',          REQ_OPTIONS },
1463         { '.',          REQ_TOGGLE_LINENO },
1464         { 'D',          REQ_TOGGLE_DATE },
1465         { 'A',          REQ_TOGGLE_AUTHOR },
1466         { 'g',          REQ_TOGGLE_REV_GRAPH },
1467         { 'F',          REQ_TOGGLE_REFS },
1468         { 'I',          REQ_TOGGLE_SORT_ORDER },
1469         { 'i',          REQ_TOGGLE_SORT_FIELD },
1470         { ':',          REQ_PROMPT },
1471         { 'u',          REQ_STATUS_UPDATE },
1472         { '!',          REQ_STATUS_REVERT },
1473         { 'M',          REQ_STATUS_MERGE },
1474         { '@',          REQ_STAGE_NEXT },
1475         { ',',          REQ_PARENT },
1476         { 'e',          REQ_EDIT },
1477 };
1479 #define KEYMAP_INFO \
1480         KEYMAP_(GENERIC), \
1481         KEYMAP_(MAIN), \
1482         KEYMAP_(DIFF), \
1483         KEYMAP_(LOG), \
1484         KEYMAP_(TREE), \
1485         KEYMAP_(BLOB), \
1486         KEYMAP_(BLAME), \
1487         KEYMAP_(BRANCH), \
1488         KEYMAP_(PAGER), \
1489         KEYMAP_(HELP), \
1490         KEYMAP_(STATUS), \
1491         KEYMAP_(STAGE)
1493 enum keymap {
1494 #define KEYMAP_(name) KEYMAP_##name
1495         KEYMAP_INFO
1496 #undef  KEYMAP_
1497 };
1499 static const struct enum_map keymap_table[] = {
1500 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1501         KEYMAP_INFO
1502 #undef  KEYMAP_
1503 };
1505 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1507 struct keybinding_table {
1508         struct keybinding *data;
1509         size_t size;
1510 };
1512 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1514 static void
1515 add_keybinding(enum keymap keymap, enum request request, int key)
1517         struct keybinding_table *table = &keybindings[keymap];
1519         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1520         if (!table->data)
1521                 die("Failed to allocate keybinding");
1522         table->data[table->size].alias = key;
1523         table->data[table->size++].request = request;
1526 /* Looks for a key binding first in the given map, then in the generic map, and
1527  * lastly in the default keybindings. */
1528 static enum request
1529 get_keybinding(enum keymap keymap, int key)
1531         size_t i;
1533         for (i = 0; i < keybindings[keymap].size; i++)
1534                 if (keybindings[keymap].data[i].alias == key)
1535                         return keybindings[keymap].data[i].request;
1537         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1538                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1539                         return keybindings[KEYMAP_GENERIC].data[i].request;
1541         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1542                 if (default_keybindings[i].alias == key)
1543                         return default_keybindings[i].request;
1545         return (enum request) key;
1549 struct key {
1550         const char *name;
1551         int value;
1552 };
1554 static const struct key key_table[] = {
1555         { "Enter",      KEY_RETURN },
1556         { "Space",      ' ' },
1557         { "Backspace",  KEY_BACKSPACE },
1558         { "Tab",        KEY_TAB },
1559         { "Escape",     KEY_ESC },
1560         { "Left",       KEY_LEFT },
1561         { "Right",      KEY_RIGHT },
1562         { "Up",         KEY_UP },
1563         { "Down",       KEY_DOWN },
1564         { "Insert",     KEY_IC },
1565         { "Delete",     KEY_DC },
1566         { "Hash",       '#' },
1567         { "Home",       KEY_HOME },
1568         { "End",        KEY_END },
1569         { "PageUp",     KEY_PPAGE },
1570         { "PageDown",   KEY_NPAGE },
1571         { "F1",         KEY_F(1) },
1572         { "F2",         KEY_F(2) },
1573         { "F3",         KEY_F(3) },
1574         { "F4",         KEY_F(4) },
1575         { "F5",         KEY_F(5) },
1576         { "F6",         KEY_F(6) },
1577         { "F7",         KEY_F(7) },
1578         { "F8",         KEY_F(8) },
1579         { "F9",         KEY_F(9) },
1580         { "F10",        KEY_F(10) },
1581         { "F11",        KEY_F(11) },
1582         { "F12",        KEY_F(12) },
1583 };
1585 static int
1586 get_key_value(const char *name)
1588         int i;
1590         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1591                 if (!strcasecmp(key_table[i].name, name))
1592                         return key_table[i].value;
1594         if (strlen(name) == 1 && isprint(*name))
1595                 return (int) *name;
1597         return ERR;
1600 static const char *
1601 get_key_name(int key_value)
1603         static char key_char[] = "'X'";
1604         const char *seq = NULL;
1605         int key;
1607         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1608                 if (key_table[key].value == key_value)
1609                         seq = key_table[key].name;
1611         if (seq == NULL &&
1612             key_value < 127 &&
1613             isprint(key_value)) {
1614                 key_char[1] = (char) key_value;
1615                 seq = key_char;
1616         }
1618         return seq ? seq : "(no key)";
1621 static bool
1622 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1624         const char *sep = *pos > 0 ? ", " : "";
1625         const char *keyname = get_key_name(keybinding->alias);
1627         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1630 static bool
1631 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1632                            enum keymap keymap, bool all)
1634         int i;
1636         for (i = 0; i < keybindings[keymap].size; i++) {
1637                 if (keybindings[keymap].data[i].request == request) {
1638                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1639                                 return FALSE;
1640                         if (!all)
1641                                 break;
1642                 }
1643         }
1645         return TRUE;
1648 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1650 static const char *
1651 get_keys(enum keymap keymap, enum request request, bool all)
1653         static char buf[BUFSIZ];
1654         size_t pos = 0;
1655         int i;
1657         buf[pos] = 0;
1659         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1660                 return "Too many keybindings!";
1661         if (pos > 0 && !all)
1662                 return buf;
1664         if (keymap != KEYMAP_GENERIC) {
1665                 /* Only the generic keymap includes the default keybindings when
1666                  * listing all keys. */
1667                 if (all)
1668                         return buf;
1670                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1671                         return "Too many keybindings!";
1672                 if (pos)
1673                         return buf;
1674         }
1676         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1677                 if (default_keybindings[i].request == request) {
1678                         if (!append_key(buf, &pos, &default_keybindings[i]))
1679                                 return "Too many keybindings!";
1680                         if (!all)
1681                                 return buf;
1682                 }
1683         }
1685         return buf;
1688 struct run_request {
1689         enum keymap keymap;
1690         int key;
1691         const char *argv[SIZEOF_ARG];
1692 };
1694 static struct run_request *run_request;
1695 static size_t run_requests;
1697 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1699 static enum request
1700 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1702         struct run_request *req;
1704         if (argc >= ARRAY_SIZE(req->argv) - 1)
1705                 return REQ_NONE;
1707         if (!realloc_run_requests(&run_request, run_requests, 1))
1708                 return REQ_NONE;
1710         req = &run_request[run_requests];
1711         req->keymap = keymap;
1712         req->key = key;
1713         req->argv[0] = NULL;
1715         if (!format_argv(req->argv, argv, FORMAT_NONE))
1716                 return REQ_NONE;
1718         return REQ_NONE + ++run_requests;
1721 static struct run_request *
1722 get_run_request(enum request request)
1724         if (request <= REQ_NONE)
1725                 return NULL;
1726         return &run_request[request - REQ_NONE - 1];
1729 static void
1730 add_builtin_run_requests(void)
1732         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1733         const char *checkout[] = { "git", "checkout", "%(branch)", 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_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1745                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1746         };
1747         int i;
1749         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1750                 enum request req;
1752                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1753                 if (req != REQ_NONE)
1754                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1755         }
1758 /*
1759  * User config file handling.
1760  */
1762 static int   config_lineno;
1763 static bool  config_errors;
1764 static const char *config_msg;
1766 static const struct enum_map color_map[] = {
1767 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1768         COLOR_MAP(DEFAULT),
1769         COLOR_MAP(BLACK),
1770         COLOR_MAP(BLUE),
1771         COLOR_MAP(CYAN),
1772         COLOR_MAP(GREEN),
1773         COLOR_MAP(MAGENTA),
1774         COLOR_MAP(RED),
1775         COLOR_MAP(WHITE),
1776         COLOR_MAP(YELLOW),
1777 };
1779 static const struct enum_map attr_map[] = {
1780 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1781         ATTR_MAP(NORMAL),
1782         ATTR_MAP(BLINK),
1783         ATTR_MAP(BOLD),
1784         ATTR_MAP(DIM),
1785         ATTR_MAP(REVERSE),
1786         ATTR_MAP(STANDOUT),
1787         ATTR_MAP(UNDERLINE),
1788 };
1790 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1792 static int parse_step(double *opt, const char *arg)
1794         *opt = atoi(arg);
1795         if (!strchr(arg, '%'))
1796                 return OK;
1798         /* "Shift down" so 100% and 1 does not conflict. */
1799         *opt = (*opt - 1) / 100;
1800         if (*opt >= 1.0) {
1801                 *opt = 0.99;
1802                 config_msg = "Step value larger than 100%";
1803                 return ERR;
1804         }
1805         if (*opt < 0.0) {
1806                 *opt = 1;
1807                 config_msg = "Invalid step value";
1808                 return ERR;
1809         }
1810         return OK;
1813 static int
1814 parse_int(int *opt, const char *arg, int min, int max)
1816         int value = atoi(arg);
1818         if (min <= value && value <= max) {
1819                 *opt = value;
1820                 return OK;
1821         }
1823         config_msg = "Integer value out of bound";
1824         return ERR;
1827 static bool
1828 set_color(int *color, const char *name)
1830         if (map_enum(color, color_map, name))
1831                 return TRUE;
1832         if (!prefixcmp(name, "color"))
1833                 return parse_int(color, name + 5, 0, 255) == OK;
1834         return FALSE;
1837 /* Wants: object fgcolor bgcolor [attribute] */
1838 static int
1839 option_color_command(int argc, const char *argv[])
1841         struct line_info *info;
1843         if (argc < 3) {
1844                 config_msg = "Wrong number of arguments given to color command";
1845                 return ERR;
1846         }
1848         info = get_line_info(argv[0]);
1849         if (!info) {
1850                 static const struct enum_map obsolete[] = {
1851                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1852                         ENUM_MAP("main-date",   LINE_DATE),
1853                         ENUM_MAP("main-author", LINE_AUTHOR),
1854                 };
1855                 int index;
1857                 if (!map_enum(&index, obsolete, argv[0])) {
1858                         config_msg = "Unknown color name";
1859                         return ERR;
1860                 }
1861                 info = &line_info[index];
1862         }
1864         if (!set_color(&info->fg, argv[1]) ||
1865             !set_color(&info->bg, argv[2])) {
1866                 config_msg = "Unknown color";
1867                 return ERR;
1868         }
1870         info->attr = 0;
1871         while (argc-- > 3) {
1872                 int attr;
1874                 if (!set_attribute(&attr, argv[argc])) {
1875                         config_msg = "Unknown attribute";
1876                         return ERR;
1877                 }
1878                 info->attr |= attr;
1879         }
1881         return OK;
1884 static int parse_bool(bool *opt, const char *arg)
1886         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1887                 ? TRUE : FALSE;
1888         return OK;
1891 static int parse_enum_do(unsigned int *opt, const char *arg,
1892                          const struct enum_map *map, size_t map_size)
1894         bool is_true;
1896         assert(map_size > 1);
1898         if (map_enum_do(map, map_size, (int *) opt, arg))
1899                 return OK;
1901         if (parse_bool(&is_true, arg) != OK)
1902                 return ERR;
1904         *opt = is_true ? map[1].value : map[0].value;
1905         return OK;
1908 #define parse_enum(opt, arg, map) \
1909         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1911 static int
1912 parse_string(char *opt, const char *arg, size_t optsize)
1914         int arglen = strlen(arg);
1916         switch (arg[0]) {
1917         case '\"':
1918         case '\'':
1919                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1920                         config_msg = "Unmatched quotation";
1921                         return ERR;
1922                 }
1923                 arg += 1; arglen -= 2;
1924         default:
1925                 string_ncopy_do(opt, optsize, arg, arglen);
1926                 return OK;
1927         }
1930 /* Wants: name = value */
1931 static int
1932 option_set_command(int argc, const char *argv[])
1934         if (argc != 3) {
1935                 config_msg = "Wrong number of arguments given to set command";
1936                 return ERR;
1937         }
1939         if (strcmp(argv[1], "=")) {
1940                 config_msg = "No value assigned";
1941                 return ERR;
1942         }
1944         if (!strcmp(argv[0], "show-author"))
1945                 return parse_enum(&opt_author, argv[2], author_map);
1947         if (!strcmp(argv[0], "show-date"))
1948                 return parse_enum(&opt_date, argv[2], date_map);
1950         if (!strcmp(argv[0], "show-rev-graph"))
1951                 return parse_bool(&opt_rev_graph, argv[2]);
1953         if (!strcmp(argv[0], "show-refs"))
1954                 return parse_bool(&opt_show_refs, argv[2]);
1956         if (!strcmp(argv[0], "show-line-numbers"))
1957                 return parse_bool(&opt_line_number, argv[2]);
1959         if (!strcmp(argv[0], "line-graphics"))
1960                 return parse_bool(&opt_line_graphics, argv[2]);
1962         if (!strcmp(argv[0], "line-number-interval"))
1963                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1965         if (!strcmp(argv[0], "author-width"))
1966                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1968         if (!strcmp(argv[0], "horizontal-scroll"))
1969                 return parse_step(&opt_hscroll, argv[2]);
1971         if (!strcmp(argv[0], "split-view-height"))
1972                 return parse_step(&opt_scale_split_view, argv[2]);
1974         if (!strcmp(argv[0], "tab-size"))
1975                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1977         if (!strcmp(argv[0], "commit-encoding"))
1978                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1980         config_msg = "Unknown variable name";
1981         return ERR;
1984 /* Wants: mode request key */
1985 static int
1986 option_bind_command(int argc, const char *argv[])
1988         enum request request;
1989         int keymap = -1;
1990         int key;
1992         if (argc < 3) {
1993                 config_msg = "Wrong number of arguments given to bind command";
1994                 return ERR;
1995         }
1997         if (set_keymap(&keymap, argv[0]) == ERR) {
1998                 config_msg = "Unknown key map";
1999                 return ERR;
2000         }
2002         key = get_key_value(argv[1]);
2003         if (key == ERR) {
2004                 config_msg = "Unknown key";
2005                 return ERR;
2006         }
2008         request = get_request(argv[2]);
2009         if (request == REQ_NONE) {
2010                 static const struct enum_map obsolete[] = {
2011                         ENUM_MAP("cherry-pick",         REQ_NONE),
2012                         ENUM_MAP("screen-resize",       REQ_NONE),
2013                         ENUM_MAP("tree-parent",         REQ_PARENT),
2014                 };
2015                 int alias;
2017                 if (map_enum(&alias, obsolete, argv[2])) {
2018                         if (alias != REQ_NONE)
2019                                 add_keybinding(keymap, alias, key);
2020                         config_msg = "Obsolete request name";
2021                         return ERR;
2022                 }
2023         }
2024         if (request == REQ_NONE && *argv[2]++ == '!')
2025                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2026         if (request == REQ_NONE) {
2027                 config_msg = "Unknown request name";
2028                 return ERR;
2029         }
2031         add_keybinding(keymap, request, key);
2033         return OK;
2036 static int
2037 set_option(const char *opt, char *value)
2039         const char *argv[SIZEOF_ARG];
2040         int argc = 0;
2042         if (!argv_from_string(argv, &argc, value)) {
2043                 config_msg = "Too many option arguments";
2044                 return ERR;
2045         }
2047         if (!strcmp(opt, "color"))
2048                 return option_color_command(argc, argv);
2050         if (!strcmp(opt, "set"))
2051                 return option_set_command(argc, argv);
2053         if (!strcmp(opt, "bind"))
2054                 return option_bind_command(argc, argv);
2056         config_msg = "Unknown option command";
2057         return ERR;
2060 static int
2061 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2063         int status = OK;
2065         config_lineno++;
2066         config_msg = "Internal error";
2068         /* Check for comment markers, since read_properties() will
2069          * only ensure opt and value are split at first " \t". */
2070         optlen = strcspn(opt, "#");
2071         if (optlen == 0)
2072                 return OK;
2074         if (opt[optlen] != 0) {
2075                 config_msg = "No option value";
2076                 status = ERR;
2078         }  else {
2079                 /* Look for comment endings in the value. */
2080                 size_t len = strcspn(value, "#");
2082                 if (len < valuelen) {
2083                         valuelen = len;
2084                         value[valuelen] = 0;
2085                 }
2087                 status = set_option(opt, value);
2088         }
2090         if (status == ERR) {
2091                 warn("Error on line %d, near '%.*s': %s",
2092                      config_lineno, (int) optlen, opt, config_msg);
2093                 config_errors = TRUE;
2094         }
2096         /* Always keep going if errors are encountered. */
2097         return OK;
2100 static void
2101 load_option_file(const char *path)
2103         struct io io = {};
2105         /* It's OK that the file doesn't exist. */
2106         if (!io_open(&io, "%s", path))
2107                 return;
2109         config_lineno = 0;
2110         config_errors = FALSE;
2112         if (io_load(&io, " \t", read_option) == ERR ||
2113             config_errors == TRUE)
2114                 warn("Errors while loading %s.", path);
2117 static int
2118 load_options(void)
2120         const char *home = getenv("HOME");
2121         const char *tigrc_user = getenv("TIGRC_USER");
2122         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2123         char buf[SIZEOF_STR];
2125         add_builtin_run_requests();
2127         if (!tigrc_system)
2128                 tigrc_system = SYSCONFDIR "/tigrc";
2129         load_option_file(tigrc_system);
2131         if (!tigrc_user) {
2132                 if (!home || !string_format(buf, "%s/.tigrc", home))
2133                         return ERR;
2134                 tigrc_user = buf;
2135         }
2136         load_option_file(tigrc_user);
2138         return OK;
2142 /*
2143  * The viewer
2144  */
2146 struct view;
2147 struct view_ops;
2149 /* The display array of active views and the index of the current view. */
2150 static struct view *display[2];
2151 static unsigned int current_view;
2153 #define foreach_displayed_view(view, i) \
2154         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2156 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2158 /* Current head and commit ID */
2159 static char ref_blob[SIZEOF_REF]        = "";
2160 static char ref_commit[SIZEOF_REF]      = "HEAD";
2161 static char ref_head[SIZEOF_REF]        = "HEAD";
2162 static char ref_branch[SIZEOF_REF]      = "";
2164 struct view {
2165         const char *name;       /* View name */
2166         const char *cmd_env;    /* Command line set via environment */
2167         const char *id;         /* Points to either of ref_{head,commit,blob} */
2169         struct view_ops *ops;   /* View operations */
2171         enum keymap keymap;     /* What keymap does this view have */
2172         bool git_dir;           /* Whether the view requires a git directory. */
2174         char ref[SIZEOF_REF];   /* Hovered commit reference */
2175         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2177         int height, width;      /* The width and height of the main window */
2178         WINDOW *win;            /* The main window */
2179         WINDOW *title;          /* The title window living below the main window */
2181         /* Navigation */
2182         unsigned long offset;   /* Offset of the window top */
2183         unsigned long yoffset;  /* Offset from the window side. */
2184         unsigned long lineno;   /* Current line number */
2185         unsigned long p_offset; /* Previous offset of the window top */
2186         unsigned long p_yoffset;/* Previous offset from the window side */
2187         unsigned long p_lineno; /* Previous current line number */
2188         bool p_restore;         /* Should the previous position be restored. */
2190         /* Searching */
2191         char grep[SIZEOF_STR];  /* Search string */
2192         regex_t *regex;         /* Pre-compiled regexp */
2194         /* If non-NULL, points to the view that opened this view. If this view
2195          * is closed tig will switch back to the parent view. */
2196         struct view *parent;
2198         /* Buffering */
2199         size_t lines;           /* Total number of lines */
2200         struct line *line;      /* Line index */
2201         unsigned int digits;    /* Number of digits in the lines member. */
2203         /* Drawing */
2204         struct line *curline;   /* Line currently being drawn. */
2205         enum line_type curtype; /* Attribute currently used for drawing. */
2206         unsigned long col;      /* Column when drawing. */
2207         bool has_scrolled;      /* View was scrolled. */
2209         /* Loading */
2210         struct io io;
2211         struct io *pipe;
2212         time_t start_time;
2213         time_t update_secs;
2214 };
2216 struct view_ops {
2217         /* What type of content being displayed. Used in the title bar. */
2218         const char *type;
2219         /* Default command arguments. */
2220         const char **argv;
2221         /* Open and reads in all view content. */
2222         bool (*open)(struct view *view);
2223         /* Read one line; updates view->line. */
2224         bool (*read)(struct view *view, char *data);
2225         /* Draw one line; @lineno must be < view->height. */
2226         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2227         /* Depending on view handle a special requests. */
2228         enum request (*request)(struct view *view, enum request request, struct line *line);
2229         /* Search for regexp in a line. */
2230         bool (*grep)(struct view *view, struct line *line);
2231         /* Select line */
2232         void (*select)(struct view *view, struct line *line);
2233         /* Prepare view for loading */
2234         bool (*prepare)(struct view *view);
2235 };
2237 static struct view_ops blame_ops;
2238 static struct view_ops blob_ops;
2239 static struct view_ops diff_ops;
2240 static struct view_ops help_ops;
2241 static struct view_ops log_ops;
2242 static struct view_ops main_ops;
2243 static struct view_ops pager_ops;
2244 static struct view_ops stage_ops;
2245 static struct view_ops status_ops;
2246 static struct view_ops tree_ops;
2247 static struct view_ops branch_ops;
2249 #define VIEW_STR(name, env, ref, ops, map, git) \
2250         { name, #env, ref, ops, map, git }
2252 #define VIEW_(id, name, ops, git, ref) \
2253         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2256 static struct view views[] = {
2257         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2258         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2259         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2260         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2261         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2262         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2263         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2264         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2265         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2266         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2267         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2268 };
2270 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2271 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2273 #define foreach_view(view, i) \
2274         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2276 #define view_is_displayed(view) \
2277         (view == display[0] || view == display[1])
2280 static inline void
2281 set_view_attr(struct view *view, enum line_type type)
2283         if (!view->curline->selected && view->curtype != type) {
2284                 (void) wattrset(view->win, get_line_attr(type));
2285                 wchgat(view->win, -1, 0, type, NULL);
2286                 view->curtype = type;
2287         }
2290 static int
2291 draw_chars(struct view *view, enum line_type type, const char *string,
2292            int max_len, bool use_tilde)
2294         static char out_buffer[BUFSIZ * 2];
2295         int len = 0;
2296         int col = 0;
2297         int trimmed = FALSE;
2298         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2300         if (max_len <= 0)
2301                 return 0;
2303         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2305         set_view_attr(view, type);
2306         if (len > 0) {
2307                 if (opt_iconv_out != ICONV_NONE) {
2308                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2309                         size_t inlen = len + 1;
2311                         char *outbuf = out_buffer;
2312                         size_t outlen = sizeof(out_buffer);
2314                         size_t ret;
2316                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2317                         if (ret != (size_t) -1) {
2318                                 string = out_buffer;
2319                                 len = sizeof(out_buffer) - outlen;
2320                         }
2321                 }
2323                 waddnstr(view->win, string, len);
2324         }
2325         if (trimmed && use_tilde) {
2326                 set_view_attr(view, LINE_DELIMITER);
2327                 waddch(view->win, '~');
2328                 col++;
2329         }
2331         return col;
2334 static int
2335 draw_space(struct view *view, enum line_type type, int max, int spaces)
2337         static char space[] = "                    ";
2338         int col = 0;
2340         spaces = MIN(max, spaces);
2342         while (spaces > 0) {
2343                 int len = MIN(spaces, sizeof(space) - 1);
2345                 col += draw_chars(view, type, space, len, FALSE);
2346                 spaces -= len;
2347         }
2349         return col;
2352 static bool
2353 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2355         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2356         return view->width + view->yoffset <= view->col;
2359 static bool
2360 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2362         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2363         int max = view->width + view->yoffset - view->col;
2364         int i;
2366         if (max < size)
2367                 size = max;
2369         set_view_attr(view, type);
2370         /* Using waddch() instead of waddnstr() ensures that
2371          * they'll be rendered correctly for the cursor line. */
2372         for (i = skip; i < size; i++)
2373                 waddch(view->win, graphic[i]);
2375         view->col += size;
2376         if (size < max && skip <= size)
2377                 waddch(view->win, ' ');
2378         view->col++;
2380         return view->width + view->yoffset <= view->col;
2383 static bool
2384 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2386         int max = MIN(view->width + view->yoffset - view->col, len);
2387         int col;
2389         if (text)
2390                 col = draw_chars(view, type, text, max - 1, trim);
2391         else
2392                 col = draw_space(view, type, max - 1, max - 1);
2394         view->col += col;
2395         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2396         return view->width + view->yoffset <= view->col;
2399 static bool
2400 draw_date(struct view *view, struct time *time)
2402         const char *date = mkdate(time, opt_date);
2403         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2405         return draw_field(view, LINE_DATE, date, cols, FALSE);
2408 static bool
2409 draw_author(struct view *view, const char *author)
2411         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2412         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2414         if (abbreviate && author)
2415                 author = get_author_initials(author);
2417         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2420 static bool
2421 draw_mode(struct view *view, mode_t mode)
2423         const char *str;
2425         if (S_ISDIR(mode))
2426                 str = "drwxr-xr-x";
2427         else if (S_ISLNK(mode))
2428                 str = "lrwxrwxrwx";
2429         else if (S_ISGITLINK(mode))
2430                 str = "m---------";
2431         else if (S_ISREG(mode) && mode & S_IXUSR)
2432                 str = "-rwxr-xr-x";
2433         else if (S_ISREG(mode))
2434                 str = "-rw-r--r--";
2435         else
2436                 str = "----------";
2438         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2441 static bool
2442 draw_lineno(struct view *view, unsigned int lineno)
2444         char number[10];
2445         int digits3 = view->digits < 3 ? 3 : view->digits;
2446         int max = MIN(view->width + view->yoffset - view->col, digits3);
2447         char *text = NULL;
2448         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2450         lineno += view->offset + 1;
2451         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2452                 static char fmt[] = "%1ld";
2454                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2455                 if (string_format(number, fmt, lineno))
2456                         text = number;
2457         }
2458         if (text)
2459                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2460         else
2461                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2462         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2465 static bool
2466 draw_view_line(struct view *view, unsigned int lineno)
2468         struct line *line;
2469         bool selected = (view->offset + lineno == view->lineno);
2471         assert(view_is_displayed(view));
2473         if (view->offset + lineno >= view->lines)
2474                 return FALSE;
2476         line = &view->line[view->offset + lineno];
2478         wmove(view->win, lineno, 0);
2479         if (line->cleareol)
2480                 wclrtoeol(view->win);
2481         view->col = 0;
2482         view->curline = line;
2483         view->curtype = LINE_NONE;
2484         line->selected = FALSE;
2485         line->dirty = line->cleareol = 0;
2487         if (selected) {
2488                 set_view_attr(view, LINE_CURSOR);
2489                 line->selected = TRUE;
2490                 view->ops->select(view, line);
2491         }
2493         return view->ops->draw(view, line, lineno);
2496 static void
2497 redraw_view_dirty(struct view *view)
2499         bool dirty = FALSE;
2500         int lineno;
2502         for (lineno = 0; lineno < view->height; lineno++) {
2503                 if (view->offset + lineno >= view->lines)
2504                         break;
2505                 if (!view->line[view->offset + lineno].dirty)
2506                         continue;
2507                 dirty = TRUE;
2508                 if (!draw_view_line(view, lineno))
2509                         break;
2510         }
2512         if (!dirty)
2513                 return;
2514         wnoutrefresh(view->win);
2517 static void
2518 redraw_view_from(struct view *view, int lineno)
2520         assert(0 <= lineno && lineno < view->height);
2522         for (; lineno < view->height; lineno++) {
2523                 if (!draw_view_line(view, lineno))
2524                         break;
2525         }
2527         wnoutrefresh(view->win);
2530 static void
2531 redraw_view(struct view *view)
2533         werase(view->win);
2534         redraw_view_from(view, 0);
2538 static void
2539 update_view_title(struct view *view)
2541         char buf[SIZEOF_STR];
2542         char state[SIZEOF_STR];
2543         size_t bufpos = 0, statelen = 0;
2545         assert(view_is_displayed(view));
2547         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2548                 unsigned int view_lines = view->offset + view->height;
2549                 unsigned int lines = view->lines
2550                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2551                                    : 0;
2553                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2554                                    view->ops->type,
2555                                    view->lineno + 1,
2556                                    view->lines,
2557                                    lines);
2559         }
2561         if (view->pipe) {
2562                 time_t secs = time(NULL) - view->start_time;
2564                 /* Three git seconds are a long time ... */
2565                 if (secs > 2)
2566                         string_format_from(state, &statelen, " loading %lds", secs);
2567         }
2569         string_format_from(buf, &bufpos, "[%s]", view->name);
2570         if (*view->ref && bufpos < view->width) {
2571                 size_t refsize = strlen(view->ref);
2572                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2574                 if (minsize < view->width)
2575                         refsize = view->width - minsize + 7;
2576                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2577         }
2579         if (statelen && bufpos < view->width) {
2580                 string_format_from(buf, &bufpos, "%s", state);
2581         }
2583         if (view == display[current_view])
2584                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2585         else
2586                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2588         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2589         wclrtoeol(view->title);
2590         wnoutrefresh(view->title);
2593 static int
2594 apply_step(double step, int value)
2596         if (step >= 1)
2597                 return (int) step;
2598         value *= step + 0.01;
2599         return value ? value : 1;
2602 static void
2603 resize_display(void)
2605         int offset, i;
2606         struct view *base = display[0];
2607         struct view *view = display[1] ? display[1] : display[0];
2609         /* Setup window dimensions */
2611         getmaxyx(stdscr, base->height, base->width);
2613         /* Make room for the status window. */
2614         base->height -= 1;
2616         if (view != base) {
2617                 /* Horizontal split. */
2618                 view->width   = base->width;
2619                 view->height  = apply_step(opt_scale_split_view, base->height);
2620                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2621                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2622                 base->height -= view->height;
2624                 /* Make room for the title bar. */
2625                 view->height -= 1;
2626         }
2628         /* Make room for the title bar. */
2629         base->height -= 1;
2631         offset = 0;
2633         foreach_displayed_view (view, i) {
2634                 if (!view->win) {
2635                         view->win = newwin(view->height, 0, offset, 0);
2636                         if (!view->win)
2637                                 die("Failed to create %s view", view->name);
2639                         scrollok(view->win, FALSE);
2641                         view->title = newwin(1, 0, offset + view->height, 0);
2642                         if (!view->title)
2643                                 die("Failed to create title window");
2645                 } else {
2646                         wresize(view->win, view->height, view->width);
2647                         mvwin(view->win,   offset, 0);
2648                         mvwin(view->title, offset + view->height, 0);
2649                 }
2651                 offset += view->height + 1;
2652         }
2655 static void
2656 redraw_display(bool clear)
2658         struct view *view;
2659         int i;
2661         foreach_displayed_view (view, i) {
2662                 if (clear)
2663                         wclear(view->win);
2664                 redraw_view(view);
2665                 update_view_title(view);
2666         }
2669 static void
2670 toggle_enum_option_do(unsigned int *opt, const char *help,
2671                       const struct enum_map *map, size_t size)
2673         *opt = (*opt + 1) % size;
2674         redraw_display(FALSE);
2675         report("Displaying %s %s", enum_name(map[*opt]), help);
2678 #define toggle_enum_option(opt, help, map) \
2679         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2681 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2682 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2684 static void
2685 toggle_view_option(bool *option, const char *help)
2687         *option = !*option;
2688         redraw_display(FALSE);
2689         report("%sabling %s", *option ? "En" : "Dis", help);
2692 static void
2693 open_option_menu(void)
2695         const struct menu_item menu[] = {
2696                 { '.', "line numbers", &opt_line_number },
2697                 { 'D', "date display", &opt_date },
2698                 { 'A', "author display", &opt_author },
2699                 { 'g', "revision graph display", &opt_rev_graph },
2700                 { 'F', "reference display", &opt_show_refs },
2701                 { 0 }
2702         };
2703         int selected = 0;
2705         if (prompt_menu("Toggle option", menu, &selected)) {
2706                 if (menu[selected].data == &opt_date)
2707                         toggle_date();
2708                 else if (menu[selected].data == &opt_author)
2709                         toggle_author();
2710                 else
2711                         toggle_view_option(menu[selected].data, menu[selected].text);
2712         }
2715 static void
2716 maximize_view(struct view *view)
2718         memset(display, 0, sizeof(display));
2719         current_view = 0;
2720         display[current_view] = view;
2721         resize_display();
2722         redraw_display(FALSE);
2723         report("");
2727 /*
2728  * Navigation
2729  */
2731 static bool
2732 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2734         if (lineno >= view->lines)
2735                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2737         if (offset > lineno || offset + view->height <= lineno) {
2738                 unsigned long half = view->height / 2;
2740                 if (lineno > half)
2741                         offset = lineno - half;
2742                 else
2743                         offset = 0;
2744         }
2746         if (offset != view->offset || lineno != view->lineno) {
2747                 view->offset = offset;
2748                 view->lineno = lineno;
2749                 return TRUE;
2750         }
2752         return FALSE;
2755 /* Scrolling backend */
2756 static void
2757 do_scroll_view(struct view *view, int lines)
2759         bool redraw_current_line = FALSE;
2761         /* The rendering expects the new offset. */
2762         view->offset += lines;
2764         assert(0 <= view->offset && view->offset < view->lines);
2765         assert(lines);
2767         /* Move current line into the view. */
2768         if (view->lineno < view->offset) {
2769                 view->lineno = view->offset;
2770                 redraw_current_line = TRUE;
2771         } else if (view->lineno >= view->offset + view->height) {
2772                 view->lineno = view->offset + view->height - 1;
2773                 redraw_current_line = TRUE;
2774         }
2776         assert(view->offset <= view->lineno && view->lineno < view->lines);
2778         /* Redraw the whole screen if scrolling is pointless. */
2779         if (view->height < ABS(lines)) {
2780                 redraw_view(view);
2782         } else {
2783                 int line = lines > 0 ? view->height - lines : 0;
2784                 int end = line + ABS(lines);
2786                 scrollok(view->win, TRUE);
2787                 wscrl(view->win, lines);
2788                 scrollok(view->win, FALSE);
2790                 while (line < end && draw_view_line(view, line))
2791                         line++;
2793                 if (redraw_current_line)
2794                         draw_view_line(view, view->lineno - view->offset);
2795                 wnoutrefresh(view->win);
2796         }
2798         view->has_scrolled = TRUE;
2799         report("");
2802 /* Scroll frontend */
2803 static void
2804 scroll_view(struct view *view, enum request request)
2806         int lines = 1;
2808         assert(view_is_displayed(view));
2810         switch (request) {
2811         case REQ_SCROLL_LEFT:
2812                 if (view->yoffset == 0) {
2813                         report("Cannot scroll beyond the first column");
2814                         return;
2815                 }
2816                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2817                         view->yoffset = 0;
2818                 else
2819                         view->yoffset -= apply_step(opt_hscroll, view->width);
2820                 redraw_view_from(view, 0);
2821                 report("");
2822                 return;
2823         case REQ_SCROLL_RIGHT:
2824                 view->yoffset += apply_step(opt_hscroll, view->width);
2825                 redraw_view(view);
2826                 report("");
2827                 return;
2828         case REQ_SCROLL_PAGE_DOWN:
2829                 lines = view->height;
2830         case REQ_SCROLL_LINE_DOWN:
2831                 if (view->offset + lines > view->lines)
2832                         lines = view->lines - view->offset;
2834                 if (lines == 0 || view->offset + view->height >= view->lines) {
2835                         report("Cannot scroll beyond the last line");
2836                         return;
2837                 }
2838                 break;
2840         case REQ_SCROLL_PAGE_UP:
2841                 lines = view->height;
2842         case REQ_SCROLL_LINE_UP:
2843                 if (lines > view->offset)
2844                         lines = view->offset;
2846                 if (lines == 0) {
2847                         report("Cannot scroll beyond the first line");
2848                         return;
2849                 }
2851                 lines = -lines;
2852                 break;
2854         default:
2855                 die("request %d not handled in switch", request);
2856         }
2858         do_scroll_view(view, lines);
2861 /* Cursor moving */
2862 static void
2863 move_view(struct view *view, enum request request)
2865         int scroll_steps = 0;
2866         int steps;
2868         switch (request) {
2869         case REQ_MOVE_FIRST_LINE:
2870                 steps = -view->lineno;
2871                 break;
2873         case REQ_MOVE_LAST_LINE:
2874                 steps = view->lines - view->lineno - 1;
2875                 break;
2877         case REQ_MOVE_PAGE_UP:
2878                 steps = view->height > view->lineno
2879                       ? -view->lineno : -view->height;
2880                 break;
2882         case REQ_MOVE_PAGE_DOWN:
2883                 steps = view->lineno + view->height >= view->lines
2884                       ? view->lines - view->lineno - 1 : view->height;
2885                 break;
2887         case REQ_MOVE_UP:
2888                 steps = -1;
2889                 break;
2891         case REQ_MOVE_DOWN:
2892                 steps = 1;
2893                 break;
2895         default:
2896                 die("request %d not handled in switch", request);
2897         }
2899         if (steps <= 0 && view->lineno == 0) {
2900                 report("Cannot move beyond the first line");
2901                 return;
2903         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2904                 report("Cannot move beyond the last line");
2905                 return;
2906         }
2908         /* Move the current line */
2909         view->lineno += steps;
2910         assert(0 <= view->lineno && view->lineno < view->lines);
2912         /* Check whether the view needs to be scrolled */
2913         if (view->lineno < view->offset ||
2914             view->lineno >= view->offset + view->height) {
2915                 scroll_steps = steps;
2916                 if (steps < 0 && -steps > view->offset) {
2917                         scroll_steps = -view->offset;
2919                 } else if (steps > 0) {
2920                         if (view->lineno == view->lines - 1 &&
2921                             view->lines > view->height) {
2922                                 scroll_steps = view->lines - view->offset - 1;
2923                                 if (scroll_steps >= view->height)
2924                                         scroll_steps -= view->height - 1;
2925                         }
2926                 }
2927         }
2929         if (!view_is_displayed(view)) {
2930                 view->offset += scroll_steps;
2931                 assert(0 <= view->offset && view->offset < view->lines);
2932                 view->ops->select(view, &view->line[view->lineno]);
2933                 return;
2934         }
2936         /* Repaint the old "current" line if we be scrolling */
2937         if (ABS(steps) < view->height)
2938                 draw_view_line(view, view->lineno - steps - view->offset);
2940         if (scroll_steps) {
2941                 do_scroll_view(view, scroll_steps);
2942                 return;
2943         }
2945         /* Draw the current line */
2946         draw_view_line(view, view->lineno - view->offset);
2948         wnoutrefresh(view->win);
2949         report("");
2953 /*
2954  * Searching
2955  */
2957 static void search_view(struct view *view, enum request request);
2959 static bool
2960 grep_text(struct view *view, const char *text[])
2962         regmatch_t pmatch;
2963         size_t i;
2965         for (i = 0; text[i]; i++)
2966                 if (*text[i] &&
2967                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2968                         return TRUE;
2969         return FALSE;
2972 static void
2973 select_view_line(struct view *view, unsigned long lineno)
2975         unsigned long old_lineno = view->lineno;
2976         unsigned long old_offset = view->offset;
2978         if (goto_view_line(view, view->offset, lineno)) {
2979                 if (view_is_displayed(view)) {
2980                         if (old_offset != view->offset) {
2981                                 redraw_view(view);
2982                         } else {
2983                                 draw_view_line(view, old_lineno - view->offset);
2984                                 draw_view_line(view, view->lineno - view->offset);
2985                                 wnoutrefresh(view->win);
2986                         }
2987                 } else {
2988                         view->ops->select(view, &view->line[view->lineno]);
2989                 }
2990         }
2993 static void
2994 find_next(struct view *view, enum request request)
2996         unsigned long lineno = view->lineno;
2997         int direction;
2999         if (!*view->grep) {
3000                 if (!*opt_search)
3001                         report("No previous search");
3002                 else
3003                         search_view(view, request);
3004                 return;
3005         }
3007         switch (request) {
3008         case REQ_SEARCH:
3009         case REQ_FIND_NEXT:
3010                 direction = 1;
3011                 break;
3013         case REQ_SEARCH_BACK:
3014         case REQ_FIND_PREV:
3015                 direction = -1;
3016                 break;
3018         default:
3019                 return;
3020         }
3022         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3023                 lineno += direction;
3025         /* Note, lineno is unsigned long so will wrap around in which case it
3026          * will become bigger than view->lines. */
3027         for (; lineno < view->lines; lineno += direction) {
3028                 if (view->ops->grep(view, &view->line[lineno])) {
3029                         select_view_line(view, lineno);
3030                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3031                         return;
3032                 }
3033         }
3035         report("No match found for '%s'", view->grep);
3038 static void
3039 search_view(struct view *view, enum request request)
3041         int regex_err;
3043         if (view->regex) {
3044                 regfree(view->regex);
3045                 *view->grep = 0;
3046         } else {
3047                 view->regex = calloc(1, sizeof(*view->regex));
3048                 if (!view->regex)
3049                         return;
3050         }
3052         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3053         if (regex_err != 0) {
3054                 char buf[SIZEOF_STR] = "unknown error";
3056                 regerror(regex_err, view->regex, buf, sizeof(buf));
3057                 report("Search failed: %s", buf);
3058                 return;
3059         }
3061         string_copy(view->grep, opt_search);
3063         find_next(view, request);
3066 /*
3067  * Incremental updating
3068  */
3070 static void
3071 reset_view(struct view *view)
3073         int i;
3075         for (i = 0; i < view->lines; i++)
3076                 free(view->line[i].data);
3077         free(view->line);
3079         view->p_offset = view->offset;
3080         view->p_yoffset = view->yoffset;
3081         view->p_lineno = view->lineno;
3083         view->line = NULL;
3084         view->offset = 0;
3085         view->yoffset = 0;
3086         view->lines  = 0;
3087         view->lineno = 0;
3088         view->vid[0] = 0;
3089         view->update_secs = 0;
3092 static void
3093 free_argv(const char *argv[])
3095         int argc;
3097         for (argc = 0; argv[argc]; argc++)
3098                 free((void *) argv[argc]);
3101 static const char *
3102 format_arg(const char *name)
3104         static struct {
3105                 const char *name;
3106                 size_t namelen;
3107                 const char *value;
3108                 const char *value_if_empty;
3109         } vars[] = {
3110 #define FORMAT_VAR(name, value, value_if_empty) \
3111         { name, STRING_SIZE(name), value, value_if_empty }
3112                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3113                 FORMAT_VAR("%(file)",           opt_file,       ""),
3114                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3115                 FORMAT_VAR("%(head)",           ref_head,       ""),
3116                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3117                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3118                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3119         };
3120         int i;
3122         for (i = 0; i < ARRAY_SIZE(vars); i++)
3123                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3124                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3126         report("Unknown replacement: `%s`", name);
3127         return NULL;
3130 static bool
3131 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3133         char buf[SIZEOF_STR];
3134         int argc;
3135         bool noreplace = flags == FORMAT_NONE;
3137         free_argv(dst_argv);
3139         for (argc = 0; src_argv[argc]; argc++) {
3140                 const char *arg = src_argv[argc];
3141                 size_t bufpos = 0;
3143                 while (arg) {
3144                         char *next = strstr(arg, "%(");
3145                         int len = next - arg;
3146                         const char *value;
3148                         if (!next || noreplace) {
3149                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
3150                                         noreplace = TRUE;
3151                                 len = strlen(arg);
3152                                 value = "";
3154                         } else {
3155                                 value = format_arg(next);
3157                                 if (!value) {
3158                                         return FALSE;
3159                                 }
3160                         }
3162                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3163                                 return FALSE;
3165                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3166                 }
3168                 dst_argv[argc] = strdup(buf);
3169                 if (!dst_argv[argc])
3170                         break;
3171         }
3173         dst_argv[argc] = NULL;
3175         return src_argv[argc] == NULL;
3178 static bool
3179 restore_view_position(struct view *view)
3181         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3182                 return FALSE;
3184         /* Changing the view position cancels the restoring. */
3185         /* FIXME: Changing back to the first line is not detected. */
3186         if (view->offset != 0 || view->lineno != 0) {
3187                 view->p_restore = FALSE;
3188                 return FALSE;
3189         }
3191         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3192             view_is_displayed(view))
3193                 werase(view->win);
3195         view->yoffset = view->p_yoffset;
3196         view->p_restore = FALSE;
3198         return TRUE;
3201 static void
3202 end_update(struct view *view, bool force)
3204         if (!view->pipe)
3205                 return;
3206         while (!view->ops->read(view, NULL))
3207                 if (!force)
3208                         return;
3209         if (force)
3210                 io_kill(view->pipe);
3211         io_done(view->pipe);
3212         view->pipe = NULL;
3215 static void
3216 setup_update(struct view *view, const char *vid)
3218         reset_view(view);
3219         string_copy_rev(view->vid, vid);
3220         view->pipe = &view->io;
3221         view->start_time = time(NULL);
3224 static bool
3225 prepare_update(struct view *view, const char *argv[], const char *dir,
3226                enum format_flags flags)
3228         if (view->pipe)
3229                 end_update(view, TRUE);
3230         return io_format(&view->io, dir, IO_RD, argv, flags);
3233 static bool
3234 prepare_update_file(struct view *view, const char *name)
3236         if (view->pipe)
3237                 end_update(view, TRUE);
3238         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3241 static bool
3242 begin_update(struct view *view, bool refresh)
3244         if (view->pipe)
3245                 end_update(view, TRUE);
3247         if (!refresh) {
3248                 if (view->ops->prepare) {
3249                         if (!view->ops->prepare(view))
3250                                 return FALSE;
3251                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3252                         return FALSE;
3253                 }
3255                 /* Put the current ref_* value to the view title ref
3256                  * member. This is needed by the blob view. Most other
3257                  * views sets it automatically after loading because the
3258                  * first line is a commit line. */
3259                 string_copy_rev(view->ref, view->id);
3260         }
3262         if (!io_start(&view->io))
3263                 return FALSE;
3265         setup_update(view, view->id);
3267         return TRUE;
3270 static bool
3271 update_view(struct view *view)
3273         char out_buffer[BUFSIZ * 2];
3274         char *line;
3275         /* Clear the view and redraw everything since the tree sorting
3276          * might have rearranged things. */
3277         bool redraw = view->lines == 0;
3278         bool can_read = TRUE;
3280         if (!view->pipe)
3281                 return TRUE;
3283         if (!io_can_read(view->pipe)) {
3284                 if (view->lines == 0 && view_is_displayed(view)) {
3285                         time_t secs = time(NULL) - view->start_time;
3287                         if (secs > 1 && secs > view->update_secs) {
3288                                 if (view->update_secs == 0)
3289                                         redraw_view(view);
3290                                 update_view_title(view);
3291                                 view->update_secs = secs;
3292                         }
3293                 }
3294                 return TRUE;
3295         }
3297         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3298                 if (opt_iconv_in != ICONV_NONE) {
3299                         ICONV_CONST char *inbuf = line;
3300                         size_t inlen = strlen(line) + 1;
3302                         char *outbuf = out_buffer;
3303                         size_t outlen = sizeof(out_buffer);
3305                         size_t ret;
3307                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3308                         if (ret != (size_t) -1)
3309                                 line = out_buffer;
3310                 }
3312                 if (!view->ops->read(view, line)) {
3313                         report("Allocation failure");
3314                         end_update(view, TRUE);
3315                         return FALSE;
3316                 }
3317         }
3319         {
3320                 unsigned long lines = view->lines;
3321                 int digits;
3323                 for (digits = 0; lines; digits++)
3324                         lines /= 10;
3326                 /* Keep the displayed view in sync with line number scaling. */
3327                 if (digits != view->digits) {
3328                         view->digits = digits;
3329                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3330                                 redraw = TRUE;
3331                 }
3332         }
3334         if (io_error(view->pipe)) {
3335                 report("Failed to read: %s", io_strerror(view->pipe));
3336                 end_update(view, TRUE);
3338         } else if (io_eof(view->pipe)) {
3339                 report("");
3340                 end_update(view, FALSE);
3341         }
3343         if (restore_view_position(view))
3344                 redraw = TRUE;
3346         if (!view_is_displayed(view))
3347                 return TRUE;
3349         if (redraw)
3350                 redraw_view_from(view, 0);
3351         else
3352                 redraw_view_dirty(view);
3354         /* Update the title _after_ the redraw so that if the redraw picks up a
3355          * commit reference in view->ref it'll be available here. */
3356         update_view_title(view);
3357         return TRUE;
3360 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3362 static struct line *
3363 add_line_data(struct view *view, void *data, enum line_type type)
3365         struct line *line;
3367         if (!realloc_lines(&view->line, view->lines, 1))
3368                 return NULL;
3370         line = &view->line[view->lines++];
3371         memset(line, 0, sizeof(*line));
3372         line->type = type;
3373         line->data = data;
3374         line->dirty = 1;
3376         return line;
3379 static struct line *
3380 add_line_text(struct view *view, const char *text, enum line_type type)
3382         char *data = text ? strdup(text) : NULL;
3384         return data ? add_line_data(view, data, type) : NULL;
3387 static struct line *
3388 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3390         char buf[SIZEOF_STR];
3391         va_list args;
3393         va_start(args, fmt);
3394         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3395                 buf[0] = 0;
3396         va_end(args);
3398         return buf[0] ? add_line_text(view, buf, type) : NULL;
3401 /*
3402  * View opening
3403  */
3405 enum open_flags {
3406         OPEN_DEFAULT = 0,       /* Use default view switching. */
3407         OPEN_SPLIT = 1,         /* Split current view. */
3408         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3409         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3410         OPEN_PREPARED = 32,     /* Open already prepared command. */
3411 };
3413 static void
3414 open_view(struct view *prev, enum request request, enum open_flags flags)
3416         bool split = !!(flags & OPEN_SPLIT);
3417         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3418         bool nomaximize = !!(flags & OPEN_REFRESH);
3419         struct view *view = VIEW(request);
3420         int nviews = displayed_views();
3421         struct view *base_view = display[0];
3423         if (view == prev && nviews == 1 && !reload) {
3424                 report("Already in %s view", view->name);
3425                 return;
3426         }
3428         if (view->git_dir && !opt_git_dir[0]) {
3429                 report("The %s view is disabled in pager view", view->name);
3430                 return;
3431         }
3433         if (split) {
3434                 display[1] = view;
3435                 current_view = 1;
3436         } else if (!nomaximize) {
3437                 /* Maximize the current view. */
3438                 memset(display, 0, sizeof(display));
3439                 current_view = 0;
3440                 display[current_view] = view;
3441         }
3443         /* No parent signals that this is the first loaded view. */
3444         if (prev && view != prev) {
3445                 view->parent = prev;
3446         }
3448         /* Resize the view when switching between split- and full-screen,
3449          * or when switching between two different full-screen views. */
3450         if (nviews != displayed_views() ||
3451             (nviews == 1 && base_view != display[0]))
3452                 resize_display();
3454         if (view->ops->open) {
3455                 if (view->pipe)
3456                         end_update(view, TRUE);
3457                 if (!view->ops->open(view)) {
3458                         report("Failed to load %s view", view->name);
3459                         return;
3460                 }
3461                 restore_view_position(view);
3463         } else if ((reload || strcmp(view->vid, view->id)) &&
3464                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3465                 report("Failed to load %s view", view->name);
3466                 return;
3467         }
3469         if (split && prev->lineno - prev->offset >= prev->height) {
3470                 /* Take the title line into account. */
3471                 int lines = prev->lineno - prev->offset - prev->height + 1;
3473                 /* Scroll the view that was split if the current line is
3474                  * outside the new limited view. */
3475                 do_scroll_view(prev, lines);
3476         }
3478         if (prev && view != prev && split && view_is_displayed(prev)) {
3479                 /* "Blur" the previous view. */
3480                 update_view_title(prev);
3481         }
3483         if (view->pipe && view->lines == 0) {
3484                 /* Clear the old view and let the incremental updating refill
3485                  * the screen. */
3486                 werase(view->win);
3487                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3488                 report("");
3489         } else if (view_is_displayed(view)) {
3490                 redraw_view(view);
3491                 report("");
3492         }
3495 static void
3496 open_external_viewer(const char *argv[], const char *dir)
3498         def_prog_mode();           /* save current tty modes */
3499         endwin();                  /* restore original tty modes */
3500         io_run_fg(argv, dir);
3501         fprintf(stderr, "Press Enter to continue");
3502         getc(opt_tty);
3503         reset_prog_mode();
3504         redraw_display(TRUE);
3507 static void
3508 open_mergetool(const char *file)
3510         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3512         open_external_viewer(mergetool_argv, opt_cdup);
3515 static void
3516 open_editor(const char *file)
3518         const char *editor_argv[] = { "vi", file, NULL };
3519         const char *editor;
3521         editor = getenv("GIT_EDITOR");
3522         if (!editor && *opt_editor)
3523                 editor = opt_editor;
3524         if (!editor)
3525                 editor = getenv("VISUAL");
3526         if (!editor)
3527                 editor = getenv("EDITOR");
3528         if (!editor)
3529                 editor = "vi";
3531         editor_argv[0] = editor;
3532         open_external_viewer(editor_argv, opt_cdup);
3535 static void
3536 open_run_request(enum request request)
3538         struct run_request *req = get_run_request(request);
3539         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3541         if (!req) {
3542                 report("Unknown run request");
3543                 return;
3544         }
3546         if (format_argv(argv, req->argv, FORMAT_ALL))
3547                 open_external_viewer(argv, NULL);
3548         free_argv(argv);
3551 /*
3552  * User request switch noodle
3553  */
3555 static int
3556 view_driver(struct view *view, enum request request)
3558         int i;
3560         if (request == REQ_NONE)
3561                 return TRUE;
3563         if (request > REQ_NONE) {
3564                 open_run_request(request);
3565                 /* FIXME: When all views can refresh always do this. */
3566                 if (view == VIEW(REQ_VIEW_STATUS) ||
3567                     view == VIEW(REQ_VIEW_MAIN) ||
3568                     view == VIEW(REQ_VIEW_LOG) ||
3569                     view == VIEW(REQ_VIEW_BRANCH) ||
3570                     view == VIEW(REQ_VIEW_STAGE))
3571                         request = REQ_REFRESH;
3572                 else
3573                         return TRUE;
3574         }
3576         if (view && view->lines) {
3577                 request = view->ops->request(view, request, &view->line[view->lineno]);
3578                 if (request == REQ_NONE)
3579                         return TRUE;
3580         }
3582         switch (request) {
3583         case REQ_MOVE_UP:
3584         case REQ_MOVE_DOWN:
3585         case REQ_MOVE_PAGE_UP:
3586         case REQ_MOVE_PAGE_DOWN:
3587         case REQ_MOVE_FIRST_LINE:
3588         case REQ_MOVE_LAST_LINE:
3589                 move_view(view, request);
3590                 break;
3592         case REQ_SCROLL_LEFT:
3593         case REQ_SCROLL_RIGHT:
3594         case REQ_SCROLL_LINE_DOWN:
3595         case REQ_SCROLL_LINE_UP:
3596         case REQ_SCROLL_PAGE_DOWN:
3597         case REQ_SCROLL_PAGE_UP:
3598                 scroll_view(view, request);
3599                 break;
3601         case REQ_VIEW_BLAME:
3602                 if (!opt_file[0]) {
3603                         report("No file chosen, press %s to open tree view",
3604                                get_key(view->keymap, REQ_VIEW_TREE));
3605                         break;
3606                 }
3607                 open_view(view, request, OPEN_DEFAULT);
3608                 break;
3610         case REQ_VIEW_BLOB:
3611                 if (!ref_blob[0]) {
3612                         report("No file chosen, press %s to open tree view",
3613                                get_key(view->keymap, REQ_VIEW_TREE));
3614                         break;
3615                 }
3616                 open_view(view, request, OPEN_DEFAULT);
3617                 break;
3619         case REQ_VIEW_PAGER:
3620                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3621                         report("No pager content, press %s to run command from prompt",
3622                                get_key(view->keymap, REQ_PROMPT));
3623                         break;
3624                 }
3625                 open_view(view, request, OPEN_DEFAULT);
3626                 break;
3628         case REQ_VIEW_STAGE:
3629                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3630                         report("No stage content, press %s to open the status view and choose file",
3631                                get_key(view->keymap, REQ_VIEW_STATUS));
3632                         break;
3633                 }
3634                 open_view(view, request, OPEN_DEFAULT);
3635                 break;
3637         case REQ_VIEW_STATUS:
3638                 if (opt_is_inside_work_tree == FALSE) {
3639                         report("The status view requires a working tree");
3640                         break;
3641                 }
3642                 open_view(view, request, OPEN_DEFAULT);
3643                 break;
3645         case REQ_VIEW_MAIN:
3646         case REQ_VIEW_DIFF:
3647         case REQ_VIEW_LOG:
3648         case REQ_VIEW_TREE:
3649         case REQ_VIEW_HELP:
3650         case REQ_VIEW_BRANCH:
3651                 open_view(view, request, OPEN_DEFAULT);
3652                 break;
3654         case REQ_NEXT:
3655         case REQ_PREVIOUS:
3656                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3658                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3659                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3660                    (view == VIEW(REQ_VIEW_DIFF) &&
3661                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3662                    (view == VIEW(REQ_VIEW_STAGE) &&
3663                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3664                    (view == VIEW(REQ_VIEW_BLOB) &&
3665                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3666                    (view == VIEW(REQ_VIEW_MAIN) &&
3667                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3668                         int line;
3670                         view = view->parent;
3671                         line = view->lineno;
3672                         move_view(view, request);
3673                         if (view_is_displayed(view))
3674                                 update_view_title(view);
3675                         if (line != view->lineno)
3676                                 view->ops->request(view, REQ_ENTER,
3677                                                    &view->line[view->lineno]);
3679                 } else {
3680                         move_view(view, request);
3681                 }
3682                 break;
3684         case REQ_VIEW_NEXT:
3685         {
3686                 int nviews = displayed_views();
3687                 int next_view = (current_view + 1) % nviews;
3689                 if (next_view == current_view) {
3690                         report("Only one view is displayed");
3691                         break;
3692                 }
3694                 current_view = next_view;
3695                 /* Blur out the title of the previous view. */
3696                 update_view_title(view);
3697                 report("");
3698                 break;
3699         }
3700         case REQ_REFRESH:
3701                 report("Refreshing is not yet supported for the %s view", view->name);
3702                 break;
3704         case REQ_MAXIMIZE:
3705                 if (displayed_views() == 2)
3706                         maximize_view(view);
3707                 break;
3709         case REQ_OPTIONS:
3710                 open_option_menu();
3711                 break;
3713         case REQ_TOGGLE_LINENO:
3714                 toggle_view_option(&opt_line_number, "line numbers");
3715                 break;
3717         case REQ_TOGGLE_DATE:
3718                 toggle_date();
3719                 break;
3721         case REQ_TOGGLE_AUTHOR:
3722                 toggle_author();
3723                 break;
3725         case REQ_TOGGLE_REV_GRAPH:
3726                 toggle_view_option(&opt_rev_graph, "revision graph display");
3727                 break;
3729         case REQ_TOGGLE_REFS:
3730                 toggle_view_option(&opt_show_refs, "reference display");
3731                 break;
3733         case REQ_TOGGLE_SORT_FIELD:
3734         case REQ_TOGGLE_SORT_ORDER:
3735                 report("Sorting is not yet supported for the %s view", view->name);
3736                 break;
3738         case REQ_SEARCH:
3739         case REQ_SEARCH_BACK:
3740                 search_view(view, request);
3741                 break;
3743         case REQ_FIND_NEXT:
3744         case REQ_FIND_PREV:
3745                 find_next(view, request);
3746                 break;
3748         case REQ_STOP_LOADING:
3749                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3750                         view = &views[i];
3751                         if (view->pipe)
3752                                 report("Stopped loading the %s view", view->name),
3753                         end_update(view, TRUE);
3754                 }
3755                 break;
3757         case REQ_SHOW_VERSION:
3758                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3759                 return TRUE;
3761         case REQ_SCREEN_REDRAW:
3762                 redraw_display(TRUE);
3763                 break;
3765         case REQ_EDIT:
3766                 report("Nothing to edit");
3767                 break;
3769         case REQ_ENTER:
3770                 report("Nothing to enter");
3771                 break;
3773         case REQ_VIEW_CLOSE:
3774                 /* XXX: Mark closed views by letting view->parent point to the
3775                  * view itself. Parents to closed view should never be
3776                  * followed. */
3777                 if (view->parent &&
3778                     view->parent->parent != view->parent) {
3779                         maximize_view(view->parent);
3780                         view->parent = view;
3781                         break;
3782                 }
3783                 /* Fall-through */
3784         case REQ_QUIT:
3785                 return FALSE;
3787         default:
3788                 report("Unknown key, press %s for help",
3789                        get_key(view->keymap, REQ_VIEW_HELP));
3790                 return TRUE;
3791         }
3793         return TRUE;
3797 /*
3798  * View backend utilities
3799  */
3801 enum sort_field {
3802         ORDERBY_NAME,
3803         ORDERBY_DATE,
3804         ORDERBY_AUTHOR,
3805 };
3807 struct sort_state {
3808         const enum sort_field *fields;
3809         size_t size, current;
3810         bool reverse;
3811 };
3813 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3814 #define get_sort_field(state) ((state).fields[(state).current])
3815 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3817 static void
3818 sort_view(struct view *view, enum request request, struct sort_state *state,
3819           int (*compare)(const void *, const void *))
3821         switch (request) {
3822         case REQ_TOGGLE_SORT_FIELD:
3823                 state->current = (state->current + 1) % state->size;
3824                 break;
3826         case REQ_TOGGLE_SORT_ORDER:
3827                 state->reverse = !state->reverse;
3828                 break;
3829         default:
3830                 die("Not a sort request");
3831         }
3833         qsort(view->line, view->lines, sizeof(*view->line), compare);
3834         redraw_view(view);
3837 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3839 /* Small author cache to reduce memory consumption. It uses binary
3840  * search to lookup or find place to position new entries. No entries
3841  * are ever freed. */
3842 static const char *
3843 get_author(const char *name)
3845         static const char **authors;
3846         static size_t authors_size;
3847         int from = 0, to = authors_size - 1;
3849         while (from <= to) {
3850                 size_t pos = (to + from) / 2;
3851                 int cmp = strcmp(name, authors[pos]);
3853                 if (!cmp)
3854                         return authors[pos];
3856                 if (cmp < 0)
3857                         to = pos - 1;
3858                 else
3859                         from = pos + 1;
3860         }
3862         if (!realloc_authors(&authors, authors_size, 1))
3863                 return NULL;
3864         name = strdup(name);
3865         if (!name)
3866                 return NULL;
3868         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3869         authors[from] = name;
3870         authors_size++;
3872         return name;
3875 static void
3876 parse_timesec(struct time *time, const char *sec)
3878         time->sec = (time_t) atol(sec);
3881 static void
3882 parse_timezone(struct time *time, const char *zone)
3884         long tz;
3886         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3887         tz += ('0' - zone[2]) * 60 * 60;
3888         tz += ('0' - zone[3]) * 60;
3889         tz += ('0' - zone[4]);
3891         if (zone[0] == '-')
3892                 tz = -tz;
3894         time->tz = tz;
3895         time->sec -= tz;
3898 /* Parse author lines where the name may be empty:
3899  *      author  <email@address.tld> 1138474660 +0100
3900  */
3901 static void
3902 parse_author_line(char *ident, const char **author, struct time *time)
3904         char *nameend = strchr(ident, '<');
3905         char *emailend = strchr(ident, '>');
3907         if (nameend && emailend)
3908                 *nameend = *emailend = 0;
3909         ident = chomp_string(ident);
3910         if (!*ident) {
3911                 if (nameend)
3912                         ident = chomp_string(nameend + 1);
3913                 if (!*ident)
3914                         ident = "Unknown";
3915         }
3917         *author = get_author(ident);
3919         /* Parse epoch and timezone */
3920         if (emailend && emailend[1] == ' ') {
3921                 char *secs = emailend + 2;
3922                 char *zone = strchr(secs, ' ');
3924                 parse_timesec(time, secs);
3926                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3927                         parse_timezone(time, zone + 1);
3928         }
3931 static bool
3932 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3934         char rev[SIZEOF_REV];
3935         const char *revlist_argv[] = {
3936                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3937         };
3938         struct menu_item *items;
3939         char text[SIZEOF_STR];
3940         bool ok = TRUE;
3941         int i;
3943         items = calloc(*parents + 1, sizeof(*items));
3944         if (!items)
3945                 return FALSE;
3947         for (i = 0; i < *parents; i++) {
3948                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3949                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3950                     !(items[i].text = strdup(text))) {
3951                         ok = FALSE;
3952                         break;
3953                 }
3954         }
3956         if (ok) {
3957                 *parents = 0;
3958                 ok = prompt_menu("Select parent", items, parents);
3959         }
3960         for (i = 0; items[i].text; i++)
3961                 free((char *) items[i].text);
3962         free(items);
3963         return ok;
3966 static bool
3967 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3969         char buf[SIZEOF_STR * 4];
3970         const char *revlist_argv[] = {
3971                 "git", "log", "--no-color", "-1",
3972                         "--pretty=format:%P", id, "--", path, NULL
3973         };
3974         int parents;
3976         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3977             (parents = strlen(buf) / 40) < 0) {
3978                 report("Failed to get parent information");
3979                 return FALSE;
3981         } else if (parents == 0) {
3982                 if (path)
3983                         report("Path '%s' does not exist in the parent", path);
3984                 else
3985                         report("The selected commit has no parents");
3986                 return FALSE;
3987         }
3989         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3990                 return FALSE;
3992         string_copy_rev(rev, &buf[41 * parents]);
3993         return TRUE;
3996 /*
3997  * Pager backend
3998  */
4000 static bool
4001 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4003         char text[SIZEOF_STR];
4005         if (opt_line_number && draw_lineno(view, lineno))
4006                 return TRUE;
4008         string_expand(text, sizeof(text), line->data, opt_tab_size);
4009         draw_text(view, line->type, text, TRUE);
4010         return TRUE;
4013 static bool
4014 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4016         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4017         char ref[SIZEOF_STR];
4019         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4020                 return TRUE;
4022         /* This is the only fatal call, since it can "corrupt" the buffer. */
4023         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4024                 return FALSE;
4026         return TRUE;
4029 static void
4030 add_pager_refs(struct view *view, struct line *line)
4032         char buf[SIZEOF_STR];
4033         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4034         struct ref_list *list;
4035         size_t bufpos = 0, i;
4036         const char *sep = "Refs: ";
4037         bool is_tag = FALSE;
4039         assert(line->type == LINE_COMMIT);
4041         list = get_ref_list(commit_id);
4042         if (!list) {
4043                 if (view == VIEW(REQ_VIEW_DIFF))
4044                         goto try_add_describe_ref;
4045                 return;
4046         }
4048         for (i = 0; i < list->size; i++) {
4049                 struct ref *ref = list->refs[i];
4050                 const char *fmt = ref->tag    ? "%s[%s]" :
4051                                   ref->remote ? "%s<%s>" : "%s%s";
4053                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4054                         return;
4055                 sep = ", ";
4056                 if (ref->tag)
4057                         is_tag = TRUE;
4058         }
4060         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4061 try_add_describe_ref:
4062                 /* Add <tag>-g<commit_id> "fake" reference. */
4063                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4064                         return;
4065         }
4067         if (bufpos == 0)
4068                 return;
4070         add_line_text(view, buf, LINE_PP_REFS);
4073 static bool
4074 pager_read(struct view *view, char *data)
4076         struct line *line;
4078         if (!data)
4079                 return TRUE;
4081         line = add_line_text(view, data, get_line_type(data));
4082         if (!line)
4083                 return FALSE;
4085         if (line->type == LINE_COMMIT &&
4086             (view == VIEW(REQ_VIEW_DIFF) ||
4087              view == VIEW(REQ_VIEW_LOG)))
4088                 add_pager_refs(view, line);
4090         return TRUE;
4093 static enum request
4094 pager_request(struct view *view, enum request request, struct line *line)
4096         int split = 0;
4098         if (request != REQ_ENTER)
4099                 return request;
4101         if (line->type == LINE_COMMIT &&
4102            (view == VIEW(REQ_VIEW_LOG) ||
4103             view == VIEW(REQ_VIEW_PAGER))) {
4104                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4105                 split = 1;
4106         }
4108         /* Always scroll the view even if it was split. That way
4109          * you can use Enter to scroll through the log view and
4110          * split open each commit diff. */
4111         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4113         /* FIXME: A minor workaround. Scrolling the view will call report("")
4114          * but if we are scrolling a non-current view this won't properly
4115          * update the view title. */
4116         if (split)
4117                 update_view_title(view);
4119         return REQ_NONE;
4122 static bool
4123 pager_grep(struct view *view, struct line *line)
4125         const char *text[] = { line->data, NULL };
4127         return grep_text(view, text);
4130 static void
4131 pager_select(struct view *view, struct line *line)
4133         if (line->type == LINE_COMMIT) {
4134                 char *text = (char *)line->data + STRING_SIZE("commit ");
4136                 if (view != VIEW(REQ_VIEW_PAGER))
4137                         string_copy_rev(view->ref, text);
4138                 string_copy_rev(ref_commit, text);
4139         }
4142 static struct view_ops pager_ops = {
4143         "line",
4144         NULL,
4145         NULL,
4146         pager_read,
4147         pager_draw,
4148         pager_request,
4149         pager_grep,
4150         pager_select,
4151 };
4153 static const char *log_argv[SIZEOF_ARG] = {
4154         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4155 };
4157 static enum request
4158 log_request(struct view *view, enum request request, struct line *line)
4160         switch (request) {
4161         case REQ_REFRESH:
4162                 load_refs();
4163                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4164                 return REQ_NONE;
4165         default:
4166                 return pager_request(view, request, line);
4167         }
4170 static struct view_ops log_ops = {
4171         "line",
4172         log_argv,
4173         NULL,
4174         pager_read,
4175         pager_draw,
4176         log_request,
4177         pager_grep,
4178         pager_select,
4179 };
4181 static const char *diff_argv[SIZEOF_ARG] = {
4182         "git", "show", "--pretty=fuller", "--no-color", "--root",
4183                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4184 };
4186 static struct view_ops diff_ops = {
4187         "line",
4188         diff_argv,
4189         NULL,
4190         pager_read,
4191         pager_draw,
4192         pager_request,
4193         pager_grep,
4194         pager_select,
4195 };
4197 /*
4198  * Help backend
4199  */
4201 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4203 static bool
4204 help_open_keymap_title(struct view *view, enum keymap keymap)
4206         struct line *line;
4208         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4209                                help_keymap_hidden[keymap] ? '+' : '-',
4210                                enum_name(keymap_table[keymap]));
4211         if (line)
4212                 line->other = keymap;
4214         return help_keymap_hidden[keymap];
4217 static void
4218 help_open_keymap(struct view *view, enum keymap keymap)
4220         const char *group = NULL;
4221         char buf[SIZEOF_STR];
4222         size_t bufpos;
4223         bool add_title = TRUE;
4224         int i;
4226         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4227                 const char *key = NULL;
4229                 if (req_info[i].request == REQ_NONE)
4230                         continue;
4232                 if (!req_info[i].request) {
4233                         group = req_info[i].help;
4234                         continue;
4235                 }
4237                 key = get_keys(keymap, req_info[i].request, TRUE);
4238                 if (!key || !*key)
4239                         continue;
4241                 if (add_title && help_open_keymap_title(view, keymap))
4242                         return;
4243                 add_title = FALSE;
4245                 if (group) {
4246                         add_line_text(view, group, LINE_HELP_GROUP);
4247                         group = NULL;
4248                 }
4250                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4251                                 enum_name(req_info[i]), req_info[i].help);
4252         }
4254         group = "External commands:";
4256         for (i = 0; i < run_requests; i++) {
4257                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4258                 const char *key;
4259                 int argc;
4261                 if (!req || req->keymap != keymap)
4262                         continue;
4264                 key = get_key_name(req->key);
4265                 if (!*key)
4266                         key = "(no key defined)";
4268                 if (add_title && help_open_keymap_title(view, keymap))
4269                         return;
4270                 if (group) {
4271                         add_line_text(view, group, LINE_HELP_GROUP);
4272                         group = NULL;
4273                 }
4275                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4276                         if (!string_format_from(buf, &bufpos, "%s%s",
4277                                                 argc ? " " : "", req->argv[argc]))
4278                                 return;
4280                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4281         }
4284 static bool
4285 help_open(struct view *view)
4287         enum keymap keymap;
4289         reset_view(view);
4290         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4291         add_line_text(view, "", LINE_DEFAULT);
4293         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4294                 help_open_keymap(view, keymap);
4296         return TRUE;
4299 static enum request
4300 help_request(struct view *view, enum request request, struct line *line)
4302         switch (request) {
4303         case REQ_ENTER:
4304                 if (line->type == LINE_HELP_KEYMAP) {
4305                         help_keymap_hidden[line->other] =
4306                                 !help_keymap_hidden[line->other];
4307                         view->p_restore = TRUE;
4308                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4309                 }
4311                 return REQ_NONE;
4312         default:
4313                 return pager_request(view, request, line);
4314         }
4317 static struct view_ops help_ops = {
4318         "line",
4319         NULL,
4320         help_open,
4321         NULL,
4322         pager_draw,
4323         help_request,
4324         pager_grep,
4325         pager_select,
4326 };
4329 /*
4330  * Tree backend
4331  */
4333 struct tree_stack_entry {
4334         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4335         unsigned long lineno;           /* Line number to restore */
4336         char *name;                     /* Position of name in opt_path */
4337 };
4339 /* The top of the path stack. */
4340 static struct tree_stack_entry *tree_stack = NULL;
4341 unsigned long tree_lineno = 0;
4343 static void
4344 pop_tree_stack_entry(void)
4346         struct tree_stack_entry *entry = tree_stack;
4348         tree_lineno = entry->lineno;
4349         entry->name[0] = 0;
4350         tree_stack = entry->prev;
4351         free(entry);
4354 static void
4355 push_tree_stack_entry(const char *name, unsigned long lineno)
4357         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4358         size_t pathlen = strlen(opt_path);
4360         if (!entry)
4361                 return;
4363         entry->prev = tree_stack;
4364         entry->name = opt_path + pathlen;
4365         tree_stack = entry;
4367         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4368                 pop_tree_stack_entry();
4369                 return;
4370         }
4372         /* Move the current line to the first tree entry. */
4373         tree_lineno = 1;
4374         entry->lineno = lineno;
4377 /* Parse output from git-ls-tree(1):
4378  *
4379  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4380  */
4382 #define SIZEOF_TREE_ATTR \
4383         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4385 #define SIZEOF_TREE_MODE \
4386         STRING_SIZE("100644 ")
4388 #define TREE_ID_OFFSET \
4389         STRING_SIZE("100644 blob ")
4391 struct tree_entry {
4392         char id[SIZEOF_REV];
4393         mode_t mode;
4394         struct time time;               /* Date from the author ident. */
4395         const char *author;             /* Author of the commit. */
4396         char name[1];
4397 };
4399 static const char *
4400 tree_path(const struct line *line)
4402         return ((struct tree_entry *) line->data)->name;
4405 static int
4406 tree_compare_entry(const struct line *line1, const struct line *line2)
4408         if (line1->type != line2->type)
4409                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4410         return strcmp(tree_path(line1), tree_path(line2));
4413 static const enum sort_field tree_sort_fields[] = {
4414         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4415 };
4416 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4418 static int
4419 tree_compare(const void *l1, const void *l2)
4421         const struct line *line1 = (const struct line *) l1;
4422         const struct line *line2 = (const struct line *) l2;
4423         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4424         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4426         if (line1->type == LINE_TREE_HEAD)
4427                 return -1;
4428         if (line2->type == LINE_TREE_HEAD)
4429                 return 1;
4431         switch (get_sort_field(tree_sort_state)) {
4432         case ORDERBY_DATE:
4433                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4435         case ORDERBY_AUTHOR:
4436                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4438         case ORDERBY_NAME:
4439         default:
4440                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4441         }
4445 static struct line *
4446 tree_entry(struct view *view, enum line_type type, const char *path,
4447            const char *mode, const char *id)
4449         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4450         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4452         if (!entry || !line) {
4453                 free(entry);
4454                 return NULL;
4455         }
4457         strncpy(entry->name, path, strlen(path));
4458         if (mode)
4459                 entry->mode = strtoul(mode, NULL, 8);
4460         if (id)
4461                 string_copy_rev(entry->id, id);
4463         return line;
4466 static bool
4467 tree_read_date(struct view *view, char *text, bool *read_date)
4469         static const char *author_name;
4470         static struct time author_time;
4472         if (!text && *read_date) {
4473                 *read_date = FALSE;
4474                 return TRUE;
4476         } else if (!text) {
4477                 char *path = *opt_path ? opt_path : ".";
4478                 /* Find next entry to process */
4479                 const char *log_file[] = {
4480                         "git", "log", "--no-color", "--pretty=raw",
4481                                 "--cc", "--raw", view->id, "--", path, NULL
4482                 };
4483                 struct io io = {};
4485                 if (!view->lines) {
4486                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4487                         report("Tree is empty");
4488                         return TRUE;
4489                 }
4491                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4492                         report("Failed to load tree data");
4493                         return TRUE;
4494                 }
4496                 io_done(view->pipe);
4497                 view->io = io;
4498                 *read_date = TRUE;
4499                 return FALSE;
4501         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4502                 parse_author_line(text + STRING_SIZE("author "),
4503                                   &author_name, &author_time);
4505         } else if (*text == ':') {
4506                 char *pos;
4507                 size_t annotated = 1;
4508                 size_t i;
4510                 pos = strchr(text, '\t');
4511                 if (!pos)
4512                         return TRUE;
4513                 text = pos + 1;
4514                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4515                         text += strlen(opt_path);
4516                 pos = strchr(text, '/');
4517                 if (pos)
4518                         *pos = 0;
4520                 for (i = 1; i < view->lines; i++) {
4521                         struct line *line = &view->line[i];
4522                         struct tree_entry *entry = line->data;
4524                         annotated += !!entry->author;
4525                         if (entry->author || strcmp(entry->name, text))
4526                                 continue;
4528                         entry->author = author_name;
4529                         entry->time = author_time;
4530                         line->dirty = 1;
4531                         break;
4532                 }
4534                 if (annotated == view->lines)
4535                         io_kill(view->pipe);
4536         }
4537         return TRUE;
4540 static bool
4541 tree_read(struct view *view, char *text)
4543         static bool read_date = FALSE;
4544         struct tree_entry *data;
4545         struct line *entry, *line;
4546         enum line_type type;
4547         size_t textlen = text ? strlen(text) : 0;
4548         char *path = text + SIZEOF_TREE_ATTR;
4550         if (read_date || !text)
4551                 return tree_read_date(view, text, &read_date);
4553         if (textlen <= SIZEOF_TREE_ATTR)
4554                 return FALSE;
4555         if (view->lines == 0 &&
4556             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4557                 return FALSE;
4559         /* Strip the path part ... */
4560         if (*opt_path) {
4561                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4562                 size_t striplen = strlen(opt_path);
4564                 if (pathlen > striplen)
4565                         memmove(path, path + striplen,
4566                                 pathlen - striplen + 1);
4568                 /* Insert "link" to parent directory. */
4569                 if (view->lines == 1 &&
4570                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4571                         return FALSE;
4572         }
4574         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4575         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4576         if (!entry)
4577                 return FALSE;
4578         data = entry->data;
4580         /* Skip "Directory ..." and ".." line. */
4581         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4582                 if (tree_compare_entry(line, entry) <= 0)
4583                         continue;
4585                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4587                 line->data = data;
4588                 line->type = type;
4589                 for (; line <= entry; line++)
4590                         line->dirty = line->cleareol = 1;
4591                 return TRUE;
4592         }
4594         if (tree_lineno > view->lineno) {
4595                 view->lineno = tree_lineno;
4596                 tree_lineno = 0;
4597         }
4599         return TRUE;
4602 static bool
4603 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4605         struct tree_entry *entry = line->data;
4607         if (line->type == LINE_TREE_HEAD) {
4608                 if (draw_text(view, line->type, "Directory path /", TRUE))
4609                         return TRUE;
4610         } else {
4611                 if (draw_mode(view, entry->mode))
4612                         return TRUE;
4614                 if (opt_author && draw_author(view, entry->author))
4615                         return TRUE;
4617                 if (opt_date && draw_date(view, &entry->time))
4618                         return TRUE;
4619         }
4620         if (draw_text(view, line->type, entry->name, TRUE))
4621                 return TRUE;
4622         return TRUE;
4625 static void
4626 open_blob_editor()
4628         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4629         int fd = mkstemp(file);
4631         if (fd == -1)
4632                 report("Failed to create temporary file");
4633         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4634                 report("Failed to save blob data to file");
4635         else
4636                 open_editor(file);
4637         if (fd != -1)
4638                 unlink(file);
4641 static enum request
4642 tree_request(struct view *view, enum request request, struct line *line)
4644         enum open_flags flags;
4646         switch (request) {
4647         case REQ_VIEW_BLAME:
4648                 if (line->type != LINE_TREE_FILE) {
4649                         report("Blame only supported for files");
4650                         return REQ_NONE;
4651                 }
4653                 string_copy(opt_ref, view->vid);
4654                 return request;
4656         case REQ_EDIT:
4657                 if (line->type != LINE_TREE_FILE) {
4658                         report("Edit only supported for files");
4659                 } else if (!is_head_commit(view->vid)) {
4660                         open_blob_editor();
4661                 } else {
4662                         open_editor(opt_file);
4663                 }
4664                 return REQ_NONE;
4666         case REQ_TOGGLE_SORT_FIELD:
4667         case REQ_TOGGLE_SORT_ORDER:
4668                 sort_view(view, request, &tree_sort_state, tree_compare);
4669                 return REQ_NONE;
4671         case REQ_PARENT:
4672                 if (!*opt_path) {
4673                         /* quit view if at top of tree */
4674                         return REQ_VIEW_CLOSE;
4675                 }
4676                 /* fake 'cd  ..' */
4677                 line = &view->line[1];
4678                 break;
4680         case REQ_ENTER:
4681                 break;
4683         default:
4684                 return request;
4685         }
4687         /* Cleanup the stack if the tree view is at a different tree. */
4688         while (!*opt_path && tree_stack)
4689                 pop_tree_stack_entry();
4691         switch (line->type) {
4692         case LINE_TREE_DIR:
4693                 /* Depending on whether it is a subdirectory or parent link
4694                  * mangle the path buffer. */
4695                 if (line == &view->line[1] && *opt_path) {
4696                         pop_tree_stack_entry();
4698                 } else {
4699                         const char *basename = tree_path(line);
4701                         push_tree_stack_entry(basename, view->lineno);
4702                 }
4704                 /* Trees and subtrees share the same ID, so they are not not
4705                  * unique like blobs. */
4706                 flags = OPEN_RELOAD;
4707                 request = REQ_VIEW_TREE;
4708                 break;
4710         case LINE_TREE_FILE:
4711                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4712                 request = REQ_VIEW_BLOB;
4713                 break;
4715         default:
4716                 return REQ_NONE;
4717         }
4719         open_view(view, request, flags);
4720         if (request == REQ_VIEW_TREE)
4721                 view->lineno = tree_lineno;
4723         return REQ_NONE;
4726 static bool
4727 tree_grep(struct view *view, struct line *line)
4729         struct tree_entry *entry = line->data;
4730         const char *text[] = {
4731                 entry->name,
4732                 opt_author ? entry->author : "",
4733                 mkdate(&entry->time, opt_date),
4734                 NULL
4735         };
4737         return grep_text(view, text);
4740 static void
4741 tree_select(struct view *view, struct line *line)
4743         struct tree_entry *entry = line->data;
4745         if (line->type == LINE_TREE_FILE) {
4746                 string_copy_rev(ref_blob, entry->id);
4747                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4749         } else if (line->type != LINE_TREE_DIR) {
4750                 return;
4751         }
4753         string_copy_rev(view->ref, entry->id);
4756 static bool
4757 tree_prepare(struct view *view)
4759         if (view->lines == 0 && opt_prefix[0]) {
4760                 char *pos = opt_prefix;
4762                 while (pos && *pos) {
4763                         char *end = strchr(pos, '/');
4765                         if (end)
4766                                 *end = 0;
4767                         push_tree_stack_entry(pos, 0);
4768                         pos = end;
4769                         if (end) {
4770                                 *end = '/';
4771                                 pos++;
4772                         }
4773                 }
4775         } else if (strcmp(view->vid, view->id)) {
4776                 opt_path[0] = 0;
4777         }
4779         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4782 static const char *tree_argv[SIZEOF_ARG] = {
4783         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4784 };
4786 static struct view_ops tree_ops = {
4787         "file",
4788         tree_argv,
4789         NULL,
4790         tree_read,
4791         tree_draw,
4792         tree_request,
4793         tree_grep,
4794         tree_select,
4795         tree_prepare,
4796 };
4798 static bool
4799 blob_read(struct view *view, char *line)
4801         if (!line)
4802                 return TRUE;
4803         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4806 static enum request
4807 blob_request(struct view *view, enum request request, struct line *line)
4809         switch (request) {
4810         case REQ_EDIT:
4811                 open_blob_editor();
4812                 return REQ_NONE;
4813         default:
4814                 return pager_request(view, request, line);
4815         }
4818 static const char *blob_argv[SIZEOF_ARG] = {
4819         "git", "cat-file", "blob", "%(blob)", NULL
4820 };
4822 static struct view_ops blob_ops = {
4823         "line",
4824         blob_argv,
4825         NULL,
4826         blob_read,
4827         pager_draw,
4828         blob_request,
4829         pager_grep,
4830         pager_select,
4831 };
4833 /*
4834  * Blame backend
4835  *
4836  * Loading the blame view is a two phase job:
4837  *
4838  *  1. File content is read either using opt_file from the
4839  *     filesystem or using git-cat-file.
4840  *  2. Then blame information is incrementally added by
4841  *     reading output from git-blame.
4842  */
4844 static const char *blame_head_argv[] = {
4845         "git", "blame", "--incremental", "--", "%(file)", NULL
4846 };
4848 static const char *blame_ref_argv[] = {
4849         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4850 };
4852 static const char *blame_cat_file_argv[] = {
4853         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4854 };
4856 struct blame_commit {
4857         char id[SIZEOF_REV];            /* SHA1 ID. */
4858         char title[128];                /* First line of the commit message. */
4859         const char *author;             /* Author of the commit. */
4860         struct time time;               /* Date from the author ident. */
4861         char filename[128];             /* Name of file. */
4862         bool has_previous;              /* Was a "previous" line detected. */
4863 };
4865 struct blame {
4866         struct blame_commit *commit;
4867         unsigned long lineno;
4868         char text[1];
4869 };
4871 static bool
4872 blame_open(struct view *view)
4874         char path[SIZEOF_STR];
4876         if (!view->parent && *opt_prefix) {
4877                 string_copy(path, opt_file);
4878                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4879                         return FALSE;
4880         }
4882         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4883                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4884                         return FALSE;
4885         }
4887         setup_update(view, opt_file);
4888         string_format(view->ref, "%s ...", opt_file);
4890         return TRUE;
4893 static struct blame_commit *
4894 get_blame_commit(struct view *view, const char *id)
4896         size_t i;
4898         for (i = 0; i < view->lines; i++) {
4899                 struct blame *blame = view->line[i].data;
4901                 if (!blame->commit)
4902                         continue;
4904                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4905                         return blame->commit;
4906         }
4908         {
4909                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4911                 if (commit)
4912                         string_ncopy(commit->id, id, SIZEOF_REV);
4913                 return commit;
4914         }
4917 static bool
4918 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4920         const char *pos = *posref;
4922         *posref = NULL;
4923         pos = strchr(pos + 1, ' ');
4924         if (!pos || !isdigit(pos[1]))
4925                 return FALSE;
4926         *number = atoi(pos + 1);
4927         if (*number < min || *number > max)
4928                 return FALSE;
4930         *posref = pos;
4931         return TRUE;
4934 static struct blame_commit *
4935 parse_blame_commit(struct view *view, const char *text, int *blamed)
4937         struct blame_commit *commit;
4938         struct blame *blame;
4939         const char *pos = text + SIZEOF_REV - 2;
4940         size_t orig_lineno = 0;
4941         size_t lineno;
4942         size_t group;
4944         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4945                 return NULL;
4947         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4948             !parse_number(&pos, &lineno, 1, view->lines) ||
4949             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4950                 return NULL;
4952         commit = get_blame_commit(view, text);
4953         if (!commit)
4954                 return NULL;
4956         *blamed += group;
4957         while (group--) {
4958                 struct line *line = &view->line[lineno + group - 1];
4960                 blame = line->data;
4961                 blame->commit = commit;
4962                 blame->lineno = orig_lineno + group - 1;
4963                 line->dirty = 1;
4964         }
4966         return commit;
4969 static bool
4970 blame_read_file(struct view *view, const char *line, bool *read_file)
4972         if (!line) {
4973                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4974                 struct io io = {};
4976                 if (view->lines == 0 && !view->parent)
4977                         die("No blame exist for %s", view->vid);
4979                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4980                         report("Failed to load blame data");
4981                         return TRUE;
4982                 }
4984                 io_done(view->pipe);
4985                 view->io = io;
4986                 *read_file = FALSE;
4987                 return FALSE;
4989         } else {
4990                 size_t linelen = strlen(line);
4991                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4993                 if (!blame)
4994                         return FALSE;
4996                 blame->commit = NULL;
4997                 strncpy(blame->text, line, linelen);
4998                 blame->text[linelen] = 0;
4999                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5000         }
5003 static bool
5004 match_blame_header(const char *name, char **line)
5006         size_t namelen = strlen(name);
5007         bool matched = !strncmp(name, *line, namelen);
5009         if (matched)
5010                 *line += namelen;
5012         return matched;
5015 static bool
5016 blame_read(struct view *view, char *line)
5018         static struct blame_commit *commit = NULL;
5019         static int blamed = 0;
5020         static bool read_file = TRUE;
5022         if (read_file)
5023                 return blame_read_file(view, line, &read_file);
5025         if (!line) {
5026                 /* Reset all! */
5027                 commit = NULL;
5028                 blamed = 0;
5029                 read_file = TRUE;
5030                 string_format(view->ref, "%s", view->vid);
5031                 if (view_is_displayed(view)) {
5032                         update_view_title(view);
5033                         redraw_view_from(view, 0);
5034                 }
5035                 return TRUE;
5036         }
5038         if (!commit) {
5039                 commit = parse_blame_commit(view, line, &blamed);
5040                 string_format(view->ref, "%s %2d%%", view->vid,
5041                               view->lines ? blamed * 100 / view->lines : 0);
5043         } else if (match_blame_header("author ", &line)) {
5044                 commit->author = get_author(line);
5046         } else if (match_blame_header("author-time ", &line)) {
5047                 parse_timesec(&commit->time, line);
5049         } else if (match_blame_header("author-tz ", &line)) {
5050                 parse_timezone(&commit->time, line);
5052         } else if (match_blame_header("summary ", &line)) {
5053                 string_ncopy(commit->title, line, strlen(line));
5055         } else if (match_blame_header("previous ", &line)) {
5056                 commit->has_previous = TRUE;
5058         } else if (match_blame_header("filename ", &line)) {
5059                 string_ncopy(commit->filename, line, strlen(line));
5060                 commit = NULL;
5061         }
5063         return TRUE;
5066 static bool
5067 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5069         struct blame *blame = line->data;
5070         struct time *time = NULL;
5071         const char *id = NULL, *author = NULL;
5072         char text[SIZEOF_STR];
5074         if (blame->commit && *blame->commit->filename) {
5075                 id = blame->commit->id;
5076                 author = blame->commit->author;
5077                 time = &blame->commit->time;
5078         }
5080         if (opt_date && draw_date(view, time))
5081                 return TRUE;
5083         if (opt_author && draw_author(view, author))
5084                 return TRUE;
5086         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5087                 return TRUE;
5089         if (draw_lineno(view, lineno))
5090                 return TRUE;
5092         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5093         draw_text(view, LINE_DEFAULT, text, TRUE);
5094         return TRUE;
5097 static bool
5098 check_blame_commit(struct blame *blame, bool check_null_id)
5100         if (!blame->commit)
5101                 report("Commit data not loaded yet");
5102         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5103                 report("No commit exist for the selected line");
5104         else
5105                 return TRUE;
5106         return FALSE;
5109 static void
5110 setup_blame_parent_line(struct view *view, struct blame *blame)
5112         const char *diff_tree_argv[] = {
5113                 "git", "diff-tree", "-U0", blame->commit->id,
5114                         "--", blame->commit->filename, NULL
5115         };
5116         struct io io = {};
5117         int parent_lineno = -1;
5118         int blamed_lineno = -1;
5119         char *line;
5121         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5122                 return;
5124         while ((line = io_get(&io, '\n', TRUE))) {
5125                 if (*line == '@') {
5126                         char *pos = strchr(line, '+');
5128                         parent_lineno = atoi(line + 4);
5129                         if (pos)
5130                                 blamed_lineno = atoi(pos + 1);
5132                 } else if (*line == '+' && parent_lineno != -1) {
5133                         if (blame->lineno == blamed_lineno - 1 &&
5134                             !strcmp(blame->text, line + 1)) {
5135                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5136                                 break;
5137                         }
5138                         blamed_lineno++;
5139                 }
5140         }
5142         io_done(&io);
5145 static enum request
5146 blame_request(struct view *view, enum request request, struct line *line)
5148         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5149         struct blame *blame = line->data;
5151         switch (request) {
5152         case REQ_VIEW_BLAME:
5153                 if (check_blame_commit(blame, TRUE)) {
5154                         string_copy(opt_ref, blame->commit->id);
5155                         string_copy(opt_file, blame->commit->filename);
5156                         if (blame->lineno)
5157                                 view->lineno = blame->lineno;
5158                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5159                 }
5160                 break;
5162         case REQ_PARENT:
5163                 if (check_blame_commit(blame, TRUE) &&
5164                     select_commit_parent(blame->commit->id, opt_ref,
5165                                          blame->commit->filename)) {
5166                         string_copy(opt_file, blame->commit->filename);
5167                         setup_blame_parent_line(view, blame);
5168                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5169                 }
5170                 break;
5172         case REQ_ENTER:
5173                 if (!check_blame_commit(blame, FALSE))
5174                         break;
5176                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5177                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5178                         break;
5180                 if (!strcmp(blame->commit->id, NULL_ID)) {
5181                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5182                         const char *diff_index_argv[] = {
5183                                 "git", "diff-index", "--root", "--patch-with-stat",
5184                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5185                         };
5187                         if (!blame->commit->has_previous) {
5188                                 diff_index_argv[1] = "diff";
5189                                 diff_index_argv[2] = "--no-color";
5190                                 diff_index_argv[6] = "--";
5191                                 diff_index_argv[7] = "/dev/null";
5192                         }
5194                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5195                                 report("Failed to allocate diff command");
5196                                 break;
5197                         }
5198                         flags |= OPEN_PREPARED;
5199                 }
5201                 open_view(view, REQ_VIEW_DIFF, flags);
5202                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5203                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5204                 break;
5206         default:
5207                 return request;
5208         }
5210         return REQ_NONE;
5213 static bool
5214 blame_grep(struct view *view, struct line *line)
5216         struct blame *blame = line->data;
5217         struct blame_commit *commit = blame->commit;
5218         const char *text[] = {
5219                 blame->text,
5220                 commit ? commit->title : "",
5221                 commit ? commit->id : "",
5222                 commit && opt_author ? commit->author : "",
5223                 commit ? mkdate(&commit->time, opt_date) : "",
5224                 NULL
5225         };
5227         return grep_text(view, text);
5230 static void
5231 blame_select(struct view *view, struct line *line)
5233         struct blame *blame = line->data;
5234         struct blame_commit *commit = blame->commit;
5236         if (!commit)
5237                 return;
5239         if (!strcmp(commit->id, NULL_ID))
5240                 string_ncopy(ref_commit, "HEAD", 4);
5241         else
5242                 string_copy_rev(ref_commit, commit->id);
5245 static struct view_ops blame_ops = {
5246         "line",
5247         NULL,
5248         blame_open,
5249         blame_read,
5250         blame_draw,
5251         blame_request,
5252         blame_grep,
5253         blame_select,
5254 };
5256 /*
5257  * Branch backend
5258  */
5260 struct branch {
5261         const char *author;             /* Author of the last commit. */
5262         struct time time;               /* Date of the last activity. */
5263         const struct ref *ref;          /* Name and commit ID information. */
5264 };
5266 static const struct ref branch_all;
5268 static const enum sort_field branch_sort_fields[] = {
5269         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5270 };
5271 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5273 static int
5274 branch_compare(const void *l1, const void *l2)
5276         const struct branch *branch1 = ((const struct line *) l1)->data;
5277         const struct branch *branch2 = ((const struct line *) l2)->data;
5279         switch (get_sort_field(branch_sort_state)) {
5280         case ORDERBY_DATE:
5281                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5283         case ORDERBY_AUTHOR:
5284                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5286         case ORDERBY_NAME:
5287         default:
5288                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5289         }
5292 static bool
5293 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5295         struct branch *branch = line->data;
5296         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5298         if (opt_date && draw_date(view, &branch->time))
5299                 return TRUE;
5301         if (opt_author && draw_author(view, branch->author))
5302                 return TRUE;
5304         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5305         return TRUE;
5308 static enum request
5309 branch_request(struct view *view, enum request request, struct line *line)
5311         struct branch *branch = line->data;
5313         switch (request) {
5314         case REQ_REFRESH:
5315                 load_refs();
5316                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5317                 return REQ_NONE;
5319         case REQ_TOGGLE_SORT_FIELD:
5320         case REQ_TOGGLE_SORT_ORDER:
5321                 sort_view(view, request, &branch_sort_state, branch_compare);
5322                 return REQ_NONE;
5324         case REQ_ENTER:
5325                 if (branch->ref == &branch_all) {
5326                         const char *all_branches_argv[] = {
5327                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5328                                       "--topo-order", "--all", NULL
5329                         };
5330                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5332                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5333                                 report("Failed to load view of all branches");
5334                                 return REQ_NONE;
5335                         }
5336                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5337                 } else {
5338                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5339                 }
5340                 return REQ_NONE;
5342         default:
5343                 return request;
5344         }
5347 static bool
5348 branch_read(struct view *view, char *line)
5350         static char id[SIZEOF_REV];
5351         struct branch *reference;
5352         size_t i;
5354         if (!line)
5355                 return TRUE;
5357         switch (get_line_type(line)) {
5358         case LINE_COMMIT:
5359                 string_copy_rev(id, line + STRING_SIZE("commit "));
5360                 return TRUE;
5362         case LINE_AUTHOR:
5363                 for (i = 0, reference = NULL; i < view->lines; i++) {
5364                         struct branch *branch = view->line[i].data;
5366                         if (strcmp(branch->ref->id, id))
5367                                 continue;
5369                         view->line[i].dirty = TRUE;
5370                         if (reference) {
5371                                 branch->author = reference->author;
5372                                 branch->time = reference->time;
5373                                 continue;
5374                         }
5376                         parse_author_line(line + STRING_SIZE("author "),
5377                                           &branch->author, &branch->time);
5378                         reference = branch;
5379                 }
5380                 return TRUE;
5382         default:
5383                 return TRUE;
5384         }
5388 static bool
5389 branch_open_visitor(void *data, const struct ref *ref)
5391         struct view *view = data;
5392         struct branch *branch;
5394         if (ref->tag || ref->ltag || ref->remote)
5395                 return TRUE;
5397         branch = calloc(1, sizeof(*branch));
5398         if (!branch)
5399                 return FALSE;
5401         branch->ref = ref;
5402         return !!add_line_data(view, branch, LINE_DEFAULT);
5405 static bool
5406 branch_open(struct view *view)
5408         const char *branch_log[] = {
5409                 "git", "log", "--no-color", "--pretty=raw",
5410                         "--simplify-by-decoration", "--all", NULL
5411         };
5413         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5414                 report("Failed to load branch data");
5415                 return TRUE;
5416         }
5418         setup_update(view, view->id);
5419         branch_open_visitor(view, &branch_all);
5420         foreach_ref(branch_open_visitor, view);
5421         view->p_restore = TRUE;
5423         return TRUE;
5426 static bool
5427 branch_grep(struct view *view, struct line *line)
5429         struct branch *branch = line->data;
5430         const char *text[] = {
5431                 branch->ref->name,
5432                 branch->author,
5433                 NULL
5434         };
5436         return grep_text(view, text);
5439 static void
5440 branch_select(struct view *view, struct line *line)
5442         struct branch *branch = line->data;
5444         string_copy_rev(view->ref, branch->ref->id);
5445         string_copy_rev(ref_commit, branch->ref->id);
5446         string_copy_rev(ref_head, branch->ref->id);
5447         string_copy_rev(ref_branch, branch->ref->name);
5450 static struct view_ops branch_ops = {
5451         "branch",
5452         NULL,
5453         branch_open,
5454         branch_read,
5455         branch_draw,
5456         branch_request,
5457         branch_grep,
5458         branch_select,
5459 };
5461 /*
5462  * Status backend
5463  */
5465 struct status {
5466         char status;
5467         struct {
5468                 mode_t mode;
5469                 char rev[SIZEOF_REV];
5470                 char name[SIZEOF_STR];
5471         } old;
5472         struct {
5473                 mode_t mode;
5474                 char rev[SIZEOF_REV];
5475                 char name[SIZEOF_STR];
5476         } new;
5477 };
5479 static char status_onbranch[SIZEOF_STR];
5480 static struct status stage_status;
5481 static enum line_type stage_line_type;
5482 static size_t stage_chunks;
5483 static int *stage_chunk;
5485 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5487 /* This should work even for the "On branch" line. */
5488 static inline bool
5489 status_has_none(struct view *view, struct line *line)
5491         return line < view->line + view->lines && !line[1].data;
5494 /* Get fields from the diff line:
5495  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5496  */
5497 static inline bool
5498 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5500         const char *old_mode = buf +  1;
5501         const char *new_mode = buf +  8;
5502         const char *old_rev  = buf + 15;
5503         const char *new_rev  = buf + 56;
5504         const char *status   = buf + 97;
5506         if (bufsize < 98 ||
5507             old_mode[-1] != ':' ||
5508             new_mode[-1] != ' ' ||
5509             old_rev[-1]  != ' ' ||
5510             new_rev[-1]  != ' ' ||
5511             status[-1]   != ' ')
5512                 return FALSE;
5514         file->status = *status;
5516         string_copy_rev(file->old.rev, old_rev);
5517         string_copy_rev(file->new.rev, new_rev);
5519         file->old.mode = strtoul(old_mode, NULL, 8);
5520         file->new.mode = strtoul(new_mode, NULL, 8);
5522         file->old.name[0] = file->new.name[0] = 0;
5524         return TRUE;
5527 static bool
5528 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5530         struct status *unmerged = NULL;
5531         char *buf;
5532         struct io io = {};
5534         if (!io_run(&io, argv, opt_cdup, IO_RD))
5535                 return FALSE;
5537         add_line_data(view, NULL, type);
5539         while ((buf = io_get(&io, 0, TRUE))) {
5540                 struct status *file = unmerged;
5542                 if (!file) {
5543                         file = calloc(1, sizeof(*file));
5544                         if (!file || !add_line_data(view, file, type))
5545                                 goto error_out;
5546                 }
5548                 /* Parse diff info part. */
5549                 if (status) {
5550                         file->status = status;
5551                         if (status == 'A')
5552                                 string_copy(file->old.rev, NULL_ID);
5554                 } else if (!file->status || file == unmerged) {
5555                         if (!status_get_diff(file, buf, strlen(buf)))
5556                                 goto error_out;
5558                         buf = io_get(&io, 0, TRUE);
5559                         if (!buf)
5560                                 break;
5562                         /* Collapse all modified entries that follow an
5563                          * associated unmerged entry. */
5564                         if (unmerged == file) {
5565                                 unmerged->status = 'U';
5566                                 unmerged = NULL;
5567                         } else if (file->status == 'U') {
5568                                 unmerged = file;
5569                         }
5570                 }
5572                 /* Grab the old name for rename/copy. */
5573                 if (!*file->old.name &&
5574                     (file->status == 'R' || file->status == 'C')) {
5575                         string_ncopy(file->old.name, buf, strlen(buf));
5577                         buf = io_get(&io, 0, TRUE);
5578                         if (!buf)
5579                                 break;
5580                 }
5582                 /* git-ls-files just delivers a NUL separated list of
5583                  * file names similar to the second half of the
5584                  * git-diff-* output. */
5585                 string_ncopy(file->new.name, buf, strlen(buf));
5586                 if (!*file->old.name)
5587                         string_copy(file->old.name, file->new.name);
5588                 file = NULL;
5589         }
5591         if (io_error(&io)) {
5592 error_out:
5593                 io_done(&io);
5594                 return FALSE;
5595         }
5597         if (!view->line[view->lines - 1].data)
5598                 add_line_data(view, NULL, LINE_STAT_NONE);
5600         io_done(&io);
5601         return TRUE;
5604 /* Don't show unmerged entries in the staged section. */
5605 static const char *status_diff_index_argv[] = {
5606         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5607                              "--cached", "-M", "HEAD", NULL
5608 };
5610 static const char *status_diff_files_argv[] = {
5611         "git", "diff-files", "-z", NULL
5612 };
5614 static const char *status_list_other_argv[] = {
5615         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5616 };
5618 static const char *status_list_no_head_argv[] = {
5619         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5620 };
5622 static const char *update_index_argv[] = {
5623         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5624 };
5626 /* Restore the previous line number to stay in the context or select a
5627  * line with something that can be updated. */
5628 static void
5629 status_restore(struct view *view)
5631         if (view->p_lineno >= view->lines)
5632                 view->p_lineno = view->lines - 1;
5633         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5634                 view->p_lineno++;
5635         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5636                 view->p_lineno--;
5638         /* If the above fails, always skip the "On branch" line. */
5639         if (view->p_lineno < view->lines)
5640                 view->lineno = view->p_lineno;
5641         else
5642                 view->lineno = 1;
5644         if (view->lineno < view->offset)
5645                 view->offset = view->lineno;
5646         else if (view->offset + view->height <= view->lineno)
5647                 view->offset = view->lineno - view->height + 1;
5649         view->p_restore = FALSE;
5652 static void
5653 status_update_onbranch(void)
5655         static const char *paths[][2] = {
5656                 { "rebase-apply/rebasing",      "Rebasing" },
5657                 { "rebase-apply/applying",      "Applying mailbox" },
5658                 { "rebase-apply/",              "Rebasing mailbox" },
5659                 { "rebase-merge/interactive",   "Interactive rebase" },
5660                 { "rebase-merge/",              "Rebase merge" },
5661                 { "MERGE_HEAD",                 "Merging" },
5662                 { "BISECT_LOG",                 "Bisecting" },
5663                 { "HEAD",                       "On branch" },
5664         };
5665         char buf[SIZEOF_STR];
5666         struct stat stat;
5667         int i;
5669         if (is_initial_commit()) {
5670                 string_copy(status_onbranch, "Initial commit");
5671                 return;
5672         }
5674         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5675                 char *head = opt_head;
5677                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5678                     lstat(buf, &stat) < 0)
5679                         continue;
5681                 if (!*opt_head) {
5682                         struct io io = {};
5684                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5685                             io_read_buf(&io, buf, sizeof(buf))) {
5686                                 head = buf;
5687                                 if (!prefixcmp(head, "refs/heads/"))
5688                                         head += STRING_SIZE("refs/heads/");
5689                         }
5690                 }
5692                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5693                         string_copy(status_onbranch, opt_head);
5694                 return;
5695         }
5697         string_copy(status_onbranch, "Not currently on any branch");
5700 /* First parse staged info using git-diff-index(1), then parse unstaged
5701  * info using git-diff-files(1), and finally untracked files using
5702  * git-ls-files(1). */
5703 static bool
5704 status_open(struct view *view)
5706         reset_view(view);
5708         add_line_data(view, NULL, LINE_STAT_HEAD);
5709         status_update_onbranch();
5711         io_run_bg(update_index_argv);
5713         if (is_initial_commit()) {
5714                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5715                         return FALSE;
5716         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5717                 return FALSE;
5718         }
5720         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5721             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5722                 return FALSE;
5724         /* Restore the exact position or use the specialized restore
5725          * mode? */
5726         if (!view->p_restore)
5727                 status_restore(view);
5728         return TRUE;
5731 static bool
5732 status_draw(struct view *view, struct line *line, unsigned int lineno)
5734         struct status *status = line->data;
5735         enum line_type type;
5736         const char *text;
5738         if (!status) {
5739                 switch (line->type) {
5740                 case LINE_STAT_STAGED:
5741                         type = LINE_STAT_SECTION;
5742                         text = "Changes to be committed:";
5743                         break;
5745                 case LINE_STAT_UNSTAGED:
5746                         type = LINE_STAT_SECTION;
5747                         text = "Changed but not updated:";
5748                         break;
5750                 case LINE_STAT_UNTRACKED:
5751                         type = LINE_STAT_SECTION;
5752                         text = "Untracked files:";
5753                         break;
5755                 case LINE_STAT_NONE:
5756                         type = LINE_DEFAULT;
5757                         text = "  (no files)";
5758                         break;
5760                 case LINE_STAT_HEAD:
5761                         type = LINE_STAT_HEAD;
5762                         text = status_onbranch;
5763                         break;
5765                 default:
5766                         return FALSE;
5767                 }
5768         } else {
5769                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5771                 buf[0] = status->status;
5772                 if (draw_text(view, line->type, buf, TRUE))
5773                         return TRUE;
5774                 type = LINE_DEFAULT;
5775                 text = status->new.name;
5776         }
5778         draw_text(view, type, text, TRUE);
5779         return TRUE;
5782 static enum request
5783 status_load_error(struct view *view, struct view *stage, const char *path)
5785         if (displayed_views() == 2 || display[current_view] != view)
5786                 maximize_view(view);
5787         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5788         return REQ_NONE;
5791 static enum request
5792 status_enter(struct view *view, struct line *line)
5794         struct status *status = line->data;
5795         const char *oldpath = status ? status->old.name : NULL;
5796         /* Diffs for unmerged entries are empty when passing the new
5797          * path, so leave it empty. */
5798         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5799         const char *info;
5800         enum open_flags split;
5801         struct view *stage = VIEW(REQ_VIEW_STAGE);
5803         if (line->type == LINE_STAT_NONE ||
5804             (!status && line[1].type == LINE_STAT_NONE)) {
5805                 report("No file to diff");
5806                 return REQ_NONE;
5807         }
5809         switch (line->type) {
5810         case LINE_STAT_STAGED:
5811                 if (is_initial_commit()) {
5812                         const char *no_head_diff_argv[] = {
5813                                 "git", "diff", "--no-color", "--patch-with-stat",
5814                                         "--", "/dev/null", newpath, NULL
5815                         };
5817                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5818                                 return status_load_error(view, stage, newpath);
5819                 } else {
5820                         const char *index_show_argv[] = {
5821                                 "git", "diff-index", "--root", "--patch-with-stat",
5822                                         "-C", "-M", "--cached", "HEAD", "--",
5823                                         oldpath, newpath, NULL
5824                         };
5826                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5827                                 return status_load_error(view, stage, newpath);
5828                 }
5830                 if (status)
5831                         info = "Staged changes to %s";
5832                 else
5833                         info = "Staged changes";
5834                 break;
5836         case LINE_STAT_UNSTAGED:
5837         {
5838                 const char *files_show_argv[] = {
5839                         "git", "diff-files", "--root", "--patch-with-stat",
5840                                 "-C", "-M", "--", oldpath, newpath, NULL
5841                 };
5843                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5844                         return status_load_error(view, stage, newpath);
5845                 if (status)
5846                         info = "Unstaged changes to %s";
5847                 else
5848                         info = "Unstaged changes";
5849                 break;
5850         }
5851         case LINE_STAT_UNTRACKED:
5852                 if (!newpath) {
5853                         report("No file to show");
5854                         return REQ_NONE;
5855                 }
5857                 if (!suffixcmp(status->new.name, -1, "/")) {
5858                         report("Cannot display a directory");
5859                         return REQ_NONE;
5860                 }
5862                 if (!prepare_update_file(stage, newpath))
5863                         return status_load_error(view, stage, newpath);
5864                 info = "Untracked file %s";
5865                 break;
5867         case LINE_STAT_HEAD:
5868                 return REQ_NONE;
5870         default:
5871                 die("line type %d not handled in switch", line->type);
5872         }
5874         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5875         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5876         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5877                 if (status) {
5878                         stage_status = *status;
5879                 } else {
5880                         memset(&stage_status, 0, sizeof(stage_status));
5881                 }
5883                 stage_line_type = line->type;
5884                 stage_chunks = 0;
5885                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5886         }
5888         return REQ_NONE;
5891 static bool
5892 status_exists(struct status *status, enum line_type type)
5894         struct view *view = VIEW(REQ_VIEW_STATUS);
5895         unsigned long lineno;
5897         for (lineno = 0; lineno < view->lines; lineno++) {
5898                 struct line *line = &view->line[lineno];
5899                 struct status *pos = line->data;
5901                 if (line->type != type)
5902                         continue;
5903                 if (!pos && (!status || !status->status) && line[1].data) {
5904                         select_view_line(view, lineno);
5905                         return TRUE;
5906                 }
5907                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5908                         select_view_line(view, lineno);
5909                         return TRUE;
5910                 }
5911         }
5913         return FALSE;
5917 static bool
5918 status_update_prepare(struct io *io, enum line_type type)
5920         const char *staged_argv[] = {
5921                 "git", "update-index", "-z", "--index-info", NULL
5922         };
5923         const char *others_argv[] = {
5924                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5925         };
5927         switch (type) {
5928         case LINE_STAT_STAGED:
5929                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5931         case LINE_STAT_UNSTAGED:
5932         case LINE_STAT_UNTRACKED:
5933                 return io_run(io, others_argv, opt_cdup, IO_WR);
5935         default:
5936                 die("line type %d not handled in switch", type);
5937                 return FALSE;
5938         }
5941 static bool
5942 status_update_write(struct io *io, struct status *status, enum line_type type)
5944         char buf[SIZEOF_STR];
5945         size_t bufsize = 0;
5947         switch (type) {
5948         case LINE_STAT_STAGED:
5949                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5950                                         status->old.mode,
5951                                         status->old.rev,
5952                                         status->old.name, 0))
5953                         return FALSE;
5954                 break;
5956         case LINE_STAT_UNSTAGED:
5957         case LINE_STAT_UNTRACKED:
5958                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5959                         return FALSE;
5960                 break;
5962         default:
5963                 die("line type %d not handled in switch", type);
5964         }
5966         return io_write(io, buf, bufsize);
5969 static bool
5970 status_update_file(struct status *status, enum line_type type)
5972         struct io io = {};
5973         bool result;
5975         if (!status_update_prepare(&io, type))
5976                 return FALSE;
5978         result = status_update_write(&io, status, type);
5979         return io_done(&io) && result;
5982 static bool
5983 status_update_files(struct view *view, struct line *line)
5985         char buf[sizeof(view->ref)];
5986         struct io io = {};
5987         bool result = TRUE;
5988         struct line *pos = view->line + view->lines;
5989         int files = 0;
5990         int file, done;
5991         int cursor_y = -1, cursor_x = -1;
5993         if (!status_update_prepare(&io, line->type))
5994                 return FALSE;
5996         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5997                 files++;
5999         string_copy(buf, view->ref);
6000         getsyx(cursor_y, cursor_x);
6001         for (file = 0, done = 5; result && file < files; line++, file++) {
6002                 int almost_done = file * 100 / files;
6004                 if (almost_done > done) {
6005                         done = almost_done;
6006                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6007                                       file, files, done);
6008                         update_view_title(view);
6009                         setsyx(cursor_y, cursor_x);
6010                         doupdate();
6011                 }
6012                 result = status_update_write(&io, line->data, line->type);
6013         }
6014         string_copy(view->ref, buf);
6016         return io_done(&io) && result;
6019 static bool
6020 status_update(struct view *view)
6022         struct line *line = &view->line[view->lineno];
6024         assert(view->lines);
6026         if (!line->data) {
6027                 /* This should work even for the "On branch" line. */
6028                 if (line < view->line + view->lines && !line[1].data) {
6029                         report("Nothing to update");
6030                         return FALSE;
6031                 }
6033                 if (!status_update_files(view, line + 1)) {
6034                         report("Failed to update file status");
6035                         return FALSE;
6036                 }
6038         } else if (!status_update_file(line->data, line->type)) {
6039                 report("Failed to update file status");
6040                 return FALSE;
6041         }
6043         return TRUE;
6046 static bool
6047 status_revert(struct status *status, enum line_type type, bool has_none)
6049         if (!status || type != LINE_STAT_UNSTAGED) {
6050                 if (type == LINE_STAT_STAGED) {
6051                         report("Cannot revert changes to staged files");
6052                 } else if (type == LINE_STAT_UNTRACKED) {
6053                         report("Cannot revert changes to untracked files");
6054                 } else if (has_none) {
6055                         report("Nothing to revert");
6056                 } else {
6057                         report("Cannot revert changes to multiple files");
6058                 }
6060         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6061                 char mode[10] = "100644";
6062                 const char *reset_argv[] = {
6063                         "git", "update-index", "--cacheinfo", mode,
6064                                 status->old.rev, status->old.name, NULL
6065                 };
6066                 const char *checkout_argv[] = {
6067                         "git", "checkout", "--", status->old.name, NULL
6068                 };
6070                 if (status->status == 'U') {
6071                         string_format(mode, "%5o", status->old.mode);
6073                         if (status->old.mode == 0 && status->new.mode == 0) {
6074                                 reset_argv[2] = "--force-remove";
6075                                 reset_argv[3] = status->old.name;
6076                                 reset_argv[4] = NULL;
6077                         }
6079                         if (!io_run_fg(reset_argv, opt_cdup))
6080                                 return FALSE;
6081                         if (status->old.mode == 0 && status->new.mode == 0)
6082                                 return TRUE;
6083                 }
6085                 return io_run_fg(checkout_argv, opt_cdup);
6086         }
6088         return FALSE;
6091 static enum request
6092 status_request(struct view *view, enum request request, struct line *line)
6094         struct status *status = line->data;
6096         switch (request) {
6097         case REQ_STATUS_UPDATE:
6098                 if (!status_update(view))
6099                         return REQ_NONE;
6100                 break;
6102         case REQ_STATUS_REVERT:
6103                 if (!status_revert(status, line->type, status_has_none(view, line)))
6104                         return REQ_NONE;
6105                 break;
6107         case REQ_STATUS_MERGE:
6108                 if (!status || status->status != 'U') {
6109                         report("Merging only possible for files with unmerged status ('U').");
6110                         return REQ_NONE;
6111                 }
6112                 open_mergetool(status->new.name);
6113                 break;
6115         case REQ_EDIT:
6116                 if (!status)
6117                         return request;
6118                 if (status->status == 'D') {
6119                         report("File has been deleted.");
6120                         return REQ_NONE;
6121                 }
6123                 open_editor(status->new.name);
6124                 break;
6126         case REQ_VIEW_BLAME:
6127                 if (status)
6128                         opt_ref[0] = 0;
6129                 return request;
6131         case REQ_ENTER:
6132                 /* After returning the status view has been split to
6133                  * show the stage view. No further reloading is
6134                  * necessary. */
6135                 return status_enter(view, line);
6137         case REQ_REFRESH:
6138                 /* Simply reload the view. */
6139                 break;
6141         default:
6142                 return request;
6143         }
6145         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6147         return REQ_NONE;
6150 static void
6151 status_select(struct view *view, struct line *line)
6153         struct status *status = line->data;
6154         char file[SIZEOF_STR] = "all files";
6155         const char *text;
6156         const char *key;
6158         if (status && !string_format(file, "'%s'", status->new.name))
6159                 return;
6161         if (!status && line[1].type == LINE_STAT_NONE)
6162                 line++;
6164         switch (line->type) {
6165         case LINE_STAT_STAGED:
6166                 text = "Press %s to unstage %s for commit";
6167                 break;
6169         case LINE_STAT_UNSTAGED:
6170                 text = "Press %s to stage %s for commit";
6171                 break;
6173         case LINE_STAT_UNTRACKED:
6174                 text = "Press %s to stage %s for addition";
6175                 break;
6177         case LINE_STAT_HEAD:
6178         case LINE_STAT_NONE:
6179                 text = "Nothing to update";
6180                 break;
6182         default:
6183                 die("line type %d not handled in switch", line->type);
6184         }
6186         if (status && status->status == 'U') {
6187                 text = "Press %s to resolve conflict in %s";
6188                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6190         } else {
6191                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6192         }
6194         string_format(view->ref, text, key, file);
6195         if (status)
6196                 string_copy(opt_file, status->new.name);
6199 static bool
6200 status_grep(struct view *view, struct line *line)
6202         struct status *status = line->data;
6204         if (status) {
6205                 const char buf[2] = { status->status, 0 };
6206                 const char *text[] = { status->new.name, buf, NULL };
6208                 return grep_text(view, text);
6209         }
6211         return FALSE;
6214 static struct view_ops status_ops = {
6215         "file",
6216         NULL,
6217         status_open,
6218         NULL,
6219         status_draw,
6220         status_request,
6221         status_grep,
6222         status_select,
6223 };
6226 static bool
6227 stage_diff_write(struct io *io, struct line *line, struct line *end)
6229         while (line < end) {
6230                 if (!io_write(io, line->data, strlen(line->data)) ||
6231                     !io_write(io, "\n", 1))
6232                         return FALSE;
6233                 line++;
6234                 if (line->type == LINE_DIFF_CHUNK ||
6235                     line->type == LINE_DIFF_HEADER)
6236                         break;
6237         }
6239         return TRUE;
6242 static struct line *
6243 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6245         for (; view->line < line; line--)
6246                 if (line->type == type)
6247                         return line;
6249         return NULL;
6252 static bool
6253 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6255         const char *apply_argv[SIZEOF_ARG] = {
6256                 "git", "apply", "--whitespace=nowarn", NULL
6257         };
6258         struct line *diff_hdr;
6259         struct io io = {};
6260         int argc = 3;
6262         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6263         if (!diff_hdr)
6264                 return FALSE;
6266         if (!revert)
6267                 apply_argv[argc++] = "--cached";
6268         if (revert || stage_line_type == LINE_STAT_STAGED)
6269                 apply_argv[argc++] = "-R";
6270         apply_argv[argc++] = "-";
6271         apply_argv[argc++] = NULL;
6272         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6273                 return FALSE;
6275         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6276             !stage_diff_write(&io, chunk, view->line + view->lines))
6277                 chunk = NULL;
6279         io_done(&io);
6280         io_run_bg(update_index_argv);
6282         return chunk ? TRUE : FALSE;
6285 static bool
6286 stage_update(struct view *view, struct line *line)
6288         struct line *chunk = NULL;
6290         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6291                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6293         if (chunk) {
6294                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6295                         report("Failed to apply chunk");
6296                         return FALSE;
6297                 }
6299         } else if (!stage_status.status) {
6300                 view = VIEW(REQ_VIEW_STATUS);
6302                 for (line = view->line; line < view->line + view->lines; line++)
6303                         if (line->type == stage_line_type)
6304                                 break;
6306                 if (!status_update_files(view, line + 1)) {
6307                         report("Failed to update files");
6308                         return FALSE;
6309                 }
6311         } else if (!status_update_file(&stage_status, stage_line_type)) {
6312                 report("Failed to update file");
6313                 return FALSE;
6314         }
6316         return TRUE;
6319 static bool
6320 stage_revert(struct view *view, struct line *line)
6322         struct line *chunk = NULL;
6324         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6325                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6327         if (chunk) {
6328                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6329                         return FALSE;
6331                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6332                         report("Failed to revert chunk");
6333                         return FALSE;
6334                 }
6335                 return TRUE;
6337         } else {
6338                 return status_revert(stage_status.status ? &stage_status : NULL,
6339                                      stage_line_type, FALSE);
6340         }
6344 static void
6345 stage_next(struct view *view, struct line *line)
6347         int i;
6349         if (!stage_chunks) {
6350                 for (line = view->line; line < view->line + view->lines; line++) {
6351                         if (line->type != LINE_DIFF_CHUNK)
6352                                 continue;
6354                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6355                                 report("Allocation failure");
6356                                 return;
6357                         }
6359                         stage_chunk[stage_chunks++] = line - view->line;
6360                 }
6361         }
6363         for (i = 0; i < stage_chunks; i++) {
6364                 if (stage_chunk[i] > view->lineno) {
6365                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6366                         report("Chunk %d of %d", i + 1, stage_chunks);
6367                         return;
6368                 }
6369         }
6371         report("No next chunk found");
6374 static enum request
6375 stage_request(struct view *view, enum request request, struct line *line)
6377         switch (request) {
6378         case REQ_STATUS_UPDATE:
6379                 if (!stage_update(view, line))
6380                         return REQ_NONE;
6381                 break;
6383         case REQ_STATUS_REVERT:
6384                 if (!stage_revert(view, line))
6385                         return REQ_NONE;
6386                 break;
6388         case REQ_STAGE_NEXT:
6389                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6390                         report("File is untracked; press %s to add",
6391                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6392                         return REQ_NONE;
6393                 }
6394                 stage_next(view, line);
6395                 return REQ_NONE;
6397         case REQ_EDIT:
6398                 if (!stage_status.new.name[0])
6399                         return request;
6400                 if (stage_status.status == 'D') {
6401                         report("File has been deleted.");
6402                         return REQ_NONE;
6403                 }
6405                 open_editor(stage_status.new.name);
6406                 break;
6408         case REQ_REFRESH:
6409                 /* Reload everything ... */
6410                 break;
6412         case REQ_VIEW_BLAME:
6413                 if (stage_status.new.name[0]) {
6414                         string_copy(opt_file, stage_status.new.name);
6415                         opt_ref[0] = 0;
6416                 }
6417                 return request;
6419         case REQ_ENTER:
6420                 return pager_request(view, request, line);
6422         default:
6423                 return request;
6424         }
6426         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6427         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6429         /* Check whether the staged entry still exists, and close the
6430          * stage view if it doesn't. */
6431         if (!status_exists(&stage_status, stage_line_type)) {
6432                 status_restore(VIEW(REQ_VIEW_STATUS));
6433                 return REQ_VIEW_CLOSE;
6434         }
6436         if (stage_line_type == LINE_STAT_UNTRACKED) {
6437                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6438                         report("Cannot display a directory");
6439                         return REQ_NONE;
6440                 }
6442                 if (!prepare_update_file(view, stage_status.new.name)) {
6443                         report("Failed to open file: %s", strerror(errno));
6444                         return REQ_NONE;
6445                 }
6446         }
6447         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6449         return REQ_NONE;
6452 static struct view_ops stage_ops = {
6453         "line",
6454         NULL,
6455         NULL,
6456         pager_read,
6457         pager_draw,
6458         stage_request,
6459         pager_grep,
6460         pager_select,
6461 };
6464 /*
6465  * Revision graph
6466  */
6468 struct commit {
6469         char id[SIZEOF_REV];            /* SHA1 ID. */
6470         char title[128];                /* First line of the commit message. */
6471         const char *author;             /* Author of the commit. */
6472         struct time time;               /* Date from the author ident. */
6473         struct ref_list *refs;          /* Repository references. */
6474         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6475         size_t graph_size;              /* The width of the graph array. */
6476         bool has_parents;               /* Rewritten --parents seen. */
6477 };
6479 /* Size of rev graph with no  "padding" columns */
6480 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6482 struct rev_graph {
6483         struct rev_graph *prev, *next, *parents;
6484         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6485         size_t size;
6486         struct commit *commit;
6487         size_t pos;
6488         unsigned int boundary:1;
6489 };
6491 /* Parents of the commit being visualized. */
6492 static struct rev_graph graph_parents[4];
6494 /* The current stack of revisions on the graph. */
6495 static struct rev_graph graph_stacks[4] = {
6496         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6497         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6498         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6499         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6500 };
6502 static inline bool
6503 graph_parent_is_merge(struct rev_graph *graph)
6505         return graph->parents->size > 1;
6508 static inline void
6509 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6511         struct commit *commit = graph->commit;
6513         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6514                 commit->graph[commit->graph_size++] = symbol;
6517 static void
6518 clear_rev_graph(struct rev_graph *graph)
6520         graph->boundary = 0;
6521         graph->size = graph->pos = 0;
6522         graph->commit = NULL;
6523         memset(graph->parents, 0, sizeof(*graph->parents));
6526 static void
6527 done_rev_graph(struct rev_graph *graph)
6529         if (graph_parent_is_merge(graph) &&
6530             graph->pos < graph->size - 1 &&
6531             graph->next->size == graph->size + graph->parents->size - 1) {
6532                 size_t i = graph->pos + graph->parents->size - 1;
6534                 graph->commit->graph_size = i * 2;
6535                 while (i < graph->next->size - 1) {
6536                         append_to_rev_graph(graph, ' ');
6537                         append_to_rev_graph(graph, '\\');
6538                         i++;
6539                 }
6540         }
6542         clear_rev_graph(graph);
6545 static void
6546 push_rev_graph(struct rev_graph *graph, const char *parent)
6548         int i;
6550         /* "Collapse" duplicate parents lines.
6551          *
6552          * FIXME: This needs to also update update the drawn graph but
6553          * for now it just serves as a method for pruning graph lines. */
6554         for (i = 0; i < graph->size; i++)
6555                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6556                         return;
6558         if (graph->size < SIZEOF_REVITEMS) {
6559                 string_copy_rev(graph->rev[graph->size++], parent);
6560         }
6563 static chtype
6564 get_rev_graph_symbol(struct rev_graph *graph)
6566         chtype symbol;
6568         if (graph->boundary)
6569                 symbol = REVGRAPH_BOUND;
6570         else if (graph->parents->size == 0)
6571                 symbol = REVGRAPH_INIT;
6572         else if (graph_parent_is_merge(graph))
6573                 symbol = REVGRAPH_MERGE;
6574         else if (graph->pos >= graph->size)
6575                 symbol = REVGRAPH_BRANCH;
6576         else
6577                 symbol = REVGRAPH_COMMIT;
6579         return symbol;
6582 static void
6583 draw_rev_graph(struct rev_graph *graph)
6585         struct rev_filler {
6586                 chtype separator, line;
6587         };
6588         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6589         static struct rev_filler fillers[] = {
6590                 { ' ',  '|' },
6591                 { '`',  '.' },
6592                 { '\'', ' ' },
6593                 { '/',  ' ' },
6594         };
6595         chtype symbol = get_rev_graph_symbol(graph);
6596         struct rev_filler *filler;
6597         size_t i;
6599         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6600         filler = &fillers[DEFAULT];
6602         for (i = 0; i < graph->pos; i++) {
6603                 append_to_rev_graph(graph, filler->line);
6604                 if (graph_parent_is_merge(graph->prev) &&
6605                     graph->prev->pos == i)
6606                         filler = &fillers[RSHARP];
6608                 append_to_rev_graph(graph, filler->separator);
6609         }
6611         /* Place the symbol for this revision. */
6612         append_to_rev_graph(graph, symbol);
6614         if (graph->prev->size > graph->size)
6615                 filler = &fillers[RDIAG];
6616         else
6617                 filler = &fillers[DEFAULT];
6619         i++;
6621         for (; i < graph->size; i++) {
6622                 append_to_rev_graph(graph, filler->separator);
6623                 append_to_rev_graph(graph, filler->line);
6624                 if (graph_parent_is_merge(graph->prev) &&
6625                     i < graph->prev->pos + graph->parents->size)
6626                         filler = &fillers[RSHARP];
6627                 if (graph->prev->size > graph->size)
6628                         filler = &fillers[LDIAG];
6629         }
6631         if (graph->prev->size > graph->size) {
6632                 append_to_rev_graph(graph, filler->separator);
6633                 if (filler->line != ' ')
6634                         append_to_rev_graph(graph, filler->line);
6635         }
6638 /* Prepare the next rev graph */
6639 static void
6640 prepare_rev_graph(struct rev_graph *graph)
6642         size_t i;
6644         /* First, traverse all lines of revisions up to the active one. */
6645         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6646                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6647                         break;
6649                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6650         }
6652         /* Interleave the new revision parent(s). */
6653         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6654                 push_rev_graph(graph->next, graph->parents->rev[i]);
6656         /* Lastly, put any remaining revisions. */
6657         for (i = graph->pos + 1; i < graph->size; i++)
6658                 push_rev_graph(graph->next, graph->rev[i]);
6661 static void
6662 update_rev_graph(struct view *view, struct rev_graph *graph)
6664         /* If this is the finalizing update ... */
6665         if (graph->commit)
6666                 prepare_rev_graph(graph);
6668         /* Graph visualization needs a one rev look-ahead,
6669          * so the first update doesn't visualize anything. */
6670         if (!graph->prev->commit)
6671                 return;
6673         if (view->lines > 2)
6674                 view->line[view->lines - 3].dirty = 1;
6675         if (view->lines > 1)
6676                 view->line[view->lines - 2].dirty = 1;
6677         draw_rev_graph(graph->prev);
6678         done_rev_graph(graph->prev->prev);
6682 /*
6683  * Main view backend
6684  */
6686 static const char *main_argv[SIZEOF_ARG] = {
6687         "git", "log", "--no-color", "--pretty=raw", "--parents",
6688                       "--topo-order", "%(head)", NULL
6689 };
6691 static bool
6692 main_draw(struct view *view, struct line *line, unsigned int lineno)
6694         struct commit *commit = line->data;
6696         if (!commit->author)
6697                 return FALSE;
6699         if (opt_date && draw_date(view, &commit->time))
6700                 return TRUE;
6702         if (opt_author && draw_author(view, commit->author))
6703                 return TRUE;
6705         if (opt_rev_graph && commit->graph_size &&
6706             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6707                 return TRUE;
6709         if (opt_show_refs && commit->refs) {
6710                 size_t i;
6712                 for (i = 0; i < commit->refs->size; i++) {
6713                         struct ref *ref = commit->refs->refs[i];
6714                         enum line_type type;
6716                         if (ref->head)
6717                                 type = LINE_MAIN_HEAD;
6718                         else if (ref->ltag)
6719                                 type = LINE_MAIN_LOCAL_TAG;
6720                         else if (ref->tag)
6721                                 type = LINE_MAIN_TAG;
6722                         else if (ref->tracked)
6723                                 type = LINE_MAIN_TRACKED;
6724                         else if (ref->remote)
6725                                 type = LINE_MAIN_REMOTE;
6726                         else
6727                                 type = LINE_MAIN_REF;
6729                         if (draw_text(view, type, "[", TRUE) ||
6730                             draw_text(view, type, ref->name, TRUE) ||
6731                             draw_text(view, type, "]", TRUE))
6732                                 return TRUE;
6734                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6735                                 return TRUE;
6736                 }
6737         }
6739         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6740         return TRUE;
6743 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6744 static bool
6745 main_read(struct view *view, char *line)
6747         static struct rev_graph *graph = graph_stacks;
6748         enum line_type type;
6749         struct commit *commit;
6751         if (!line) {
6752                 int i;
6754                 if (!view->lines && !view->parent)
6755                         die("No revisions match the given arguments.");
6756                 if (view->lines > 0) {
6757                         commit = view->line[view->lines - 1].data;
6758                         view->line[view->lines - 1].dirty = 1;
6759                         if (!commit->author) {
6760                                 view->lines--;
6761                                 free(commit);
6762                                 graph->commit = NULL;
6763                         }
6764                 }
6765                 update_rev_graph(view, graph);
6767                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6768                         clear_rev_graph(&graph_stacks[i]);
6769                 return TRUE;
6770         }
6772         type = get_line_type(line);
6773         if (type == LINE_COMMIT) {
6774                 commit = calloc(1, sizeof(struct commit));
6775                 if (!commit)
6776                         return FALSE;
6778                 line += STRING_SIZE("commit ");
6779                 if (*line == '-') {
6780                         graph->boundary = 1;
6781                         line++;
6782                 }
6784                 string_copy_rev(commit->id, line);
6785                 commit->refs = get_ref_list(commit->id);
6786                 graph->commit = commit;
6787                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6789                 while ((line = strchr(line, ' '))) {
6790                         line++;
6791                         push_rev_graph(graph->parents, line);
6792                         commit->has_parents = TRUE;
6793                 }
6794                 return TRUE;
6795         }
6797         if (!view->lines)
6798                 return TRUE;
6799         commit = view->line[view->lines - 1].data;
6801         switch (type) {
6802         case LINE_PARENT:
6803                 if (commit->has_parents)
6804                         break;
6805                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6806                 break;
6808         case LINE_AUTHOR:
6809                 parse_author_line(line + STRING_SIZE("author "),
6810                                   &commit->author, &commit->time);
6811                 update_rev_graph(view, graph);
6812                 graph = graph->next;
6813                 break;
6815         default:
6816                 /* Fill in the commit title if it has not already been set. */
6817                 if (commit->title[0])
6818                         break;
6820                 /* Require titles to start with a non-space character at the
6821                  * offset used by git log. */
6822                 if (strncmp(line, "    ", 4))
6823                         break;
6824                 line += 4;
6825                 /* Well, if the title starts with a whitespace character,
6826                  * try to be forgiving.  Otherwise we end up with no title. */
6827                 while (isspace(*line))
6828                         line++;
6829                 if (*line == '\0')
6830                         break;
6831                 /* FIXME: More graceful handling of titles; append "..." to
6832                  * shortened titles, etc. */
6834                 string_expand(commit->title, sizeof(commit->title), line, 1);
6835                 view->line[view->lines - 1].dirty = 1;
6836         }
6838         return TRUE;
6841 static enum request
6842 main_request(struct view *view, enum request request, struct line *line)
6844         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6846         switch (request) {
6847         case REQ_ENTER:
6848                 open_view(view, REQ_VIEW_DIFF, flags);
6849                 break;
6850         case REQ_REFRESH:
6851                 load_refs();
6852                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6853                 break;
6854         default:
6855                 return request;
6856         }
6858         return REQ_NONE;
6861 static bool
6862 grep_refs(struct ref_list *list, regex_t *regex)
6864         regmatch_t pmatch;
6865         size_t i;
6867         if (!opt_show_refs || !list)
6868                 return FALSE;
6870         for (i = 0; i < list->size; i++) {
6871                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6872                         return TRUE;
6873         }
6875         return FALSE;
6878 static bool
6879 main_grep(struct view *view, struct line *line)
6881         struct commit *commit = line->data;
6882         const char *text[] = {
6883                 commit->title,
6884                 opt_author ? commit->author : "",
6885                 mkdate(&commit->time, opt_date),
6886                 NULL
6887         };
6889         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6892 static void
6893 main_select(struct view *view, struct line *line)
6895         struct commit *commit = line->data;
6897         string_copy_rev(view->ref, commit->id);
6898         string_copy_rev(ref_commit, view->ref);
6901 static struct view_ops main_ops = {
6902         "commit",
6903         main_argv,
6904         NULL,
6905         main_read,
6906         main_draw,
6907         main_request,
6908         main_grep,
6909         main_select,
6910 };
6913 /*
6914  * Status management
6915  */
6917 /* Whether or not the curses interface has been initialized. */
6918 static bool cursed = FALSE;
6920 /* Terminal hacks and workarounds. */
6921 static bool use_scroll_redrawwin;
6922 static bool use_scroll_status_wclear;
6924 /* The status window is used for polling keystrokes. */
6925 static WINDOW *status_win;
6927 /* Reading from the prompt? */
6928 static bool input_mode = FALSE;
6930 static bool status_empty = FALSE;
6932 /* Update status and title window. */
6933 static void
6934 report(const char *msg, ...)
6936         struct view *view = display[current_view];
6938         if (input_mode)
6939                 return;
6941         if (!view) {
6942                 char buf[SIZEOF_STR];
6943                 va_list args;
6945                 va_start(args, msg);
6946                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6947                         buf[sizeof(buf) - 1] = 0;
6948                         buf[sizeof(buf) - 2] = '.';
6949                         buf[sizeof(buf) - 3] = '.';
6950                         buf[sizeof(buf) - 4] = '.';
6951                 }
6952                 va_end(args);
6953                 die("%s", buf);
6954         }
6956         if (!status_empty || *msg) {
6957                 va_list args;
6959                 va_start(args, msg);
6961                 wmove(status_win, 0, 0);
6962                 if (view->has_scrolled && use_scroll_status_wclear)
6963                         wclear(status_win);
6964                 if (*msg) {
6965                         vwprintw(status_win, msg, args);
6966                         status_empty = FALSE;
6967                 } else {
6968                         status_empty = TRUE;
6969                 }
6970                 wclrtoeol(status_win);
6971                 wnoutrefresh(status_win);
6973                 va_end(args);
6974         }
6976         update_view_title(view);
6979 static void
6980 init_display(void)
6982         const char *term;
6983         int x, y;
6985         /* Initialize the curses library */
6986         if (isatty(STDIN_FILENO)) {
6987                 cursed = !!initscr();
6988                 opt_tty = stdin;
6989         } else {
6990                 /* Leave stdin and stdout alone when acting as a pager. */
6991                 opt_tty = fopen("/dev/tty", "r+");
6992                 if (!opt_tty)
6993                         die("Failed to open /dev/tty");
6994                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6995         }
6997         if (!cursed)
6998                 die("Failed to initialize curses");
7000         nonl();         /* Disable conversion and detect newlines from input. */
7001         cbreak();       /* Take input chars one at a time, no wait for \n */
7002         noecho();       /* Don't echo input */
7003         leaveok(stdscr, FALSE);
7005         if (has_colors())
7006                 init_colors();
7008         getmaxyx(stdscr, y, x);
7009         status_win = newwin(1, 0, y - 1, 0);
7010         if (!status_win)
7011                 die("Failed to create status window");
7013         /* Enable keyboard mapping */
7014         keypad(status_win, TRUE);
7015         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7017         TABSIZE = opt_tab_size;
7019         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7020         if (term && !strcmp(term, "gnome-terminal")) {
7021                 /* In the gnome-terminal-emulator, the message from
7022                  * scrolling up one line when impossible followed by
7023                  * scrolling down one line causes corruption of the
7024                  * status line. This is fixed by calling wclear. */
7025                 use_scroll_status_wclear = TRUE;
7026                 use_scroll_redrawwin = FALSE;
7028         } else if (term && !strcmp(term, "xrvt-xpm")) {
7029                 /* No problems with full optimizations in xrvt-(unicode)
7030                  * and aterm. */
7031                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7033         } else {
7034                 /* When scrolling in (u)xterm the last line in the
7035                  * scrolling direction will update slowly. */
7036                 use_scroll_redrawwin = TRUE;
7037                 use_scroll_status_wclear = FALSE;
7038         }
7041 static int
7042 get_input(int prompt_position)
7044         struct view *view;
7045         int i, key, cursor_y, cursor_x;
7046         bool loading = FALSE;
7048         if (prompt_position)
7049                 input_mode = TRUE;
7051         while (TRUE) {
7052                 foreach_view (view, i) {
7053                         update_view(view);
7054                         if (view_is_displayed(view) && view->has_scrolled &&
7055                             use_scroll_redrawwin)
7056                                 redrawwin(view->win);
7057                         view->has_scrolled = FALSE;
7058                         if (view->pipe)
7059                                 loading = TRUE;
7060                 }
7062                 /* Update the cursor position. */
7063                 if (prompt_position) {
7064                         getbegyx(status_win, cursor_y, cursor_x);
7065                         cursor_x = prompt_position;
7066                 } else {
7067                         view = display[current_view];
7068                         getbegyx(view->win, cursor_y, cursor_x);
7069                         cursor_x = view->width - 1;
7070                         cursor_y += view->lineno - view->offset;
7071                 }
7072                 setsyx(cursor_y, cursor_x);
7074                 /* Refresh, accept single keystroke of input */
7075                 doupdate();
7076                 nodelay(status_win, loading);
7077                 key = wgetch(status_win);
7079                 /* wgetch() with nodelay() enabled returns ERR when
7080                  * there's no input. */
7081                 if (key == ERR) {
7083                 } else if (key == KEY_RESIZE) {
7084                         int height, width;
7086                         getmaxyx(stdscr, height, width);
7088                         wresize(status_win, 1, width);
7089                         mvwin(status_win, height - 1, 0);
7090                         wnoutrefresh(status_win);
7091                         resize_display();
7092                         redraw_display(TRUE);
7094                 } else {
7095                         input_mode = FALSE;
7096                         return key;
7097                 }
7098         }
7101 static char *
7102 prompt_input(const char *prompt, input_handler handler, void *data)
7104         enum input_status status = INPUT_OK;
7105         static char buf[SIZEOF_STR];
7106         size_t pos = 0;
7108         buf[pos] = 0;
7110         while (status == INPUT_OK || status == INPUT_SKIP) {
7111                 int key;
7113                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7114                 wclrtoeol(status_win);
7116                 key = get_input(pos + 1);
7117                 switch (key) {
7118                 case KEY_RETURN:
7119                 case KEY_ENTER:
7120                 case '\n':
7121                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7122                         break;
7124                 case KEY_BACKSPACE:
7125                         if (pos > 0)
7126                                 buf[--pos] = 0;
7127                         else
7128                                 status = INPUT_CANCEL;
7129                         break;
7131                 case KEY_ESC:
7132                         status = INPUT_CANCEL;
7133                         break;
7135                 default:
7136                         if (pos >= sizeof(buf)) {
7137                                 report("Input string too long");
7138                                 return NULL;
7139                         }
7141                         status = handler(data, buf, key);
7142                         if (status == INPUT_OK)
7143                                 buf[pos++] = (char) key;
7144                 }
7145         }
7147         /* Clear the status window */
7148         status_empty = FALSE;
7149         report("");
7151         if (status == INPUT_CANCEL)
7152                 return NULL;
7154         buf[pos++] = 0;
7156         return buf;
7159 static enum input_status
7160 prompt_yesno_handler(void *data, char *buf, int c)
7162         if (c == 'y' || c == 'Y')
7163                 return INPUT_STOP;
7164         if (c == 'n' || c == 'N')
7165                 return INPUT_CANCEL;
7166         return INPUT_SKIP;
7169 static bool
7170 prompt_yesno(const char *prompt)
7172         char prompt2[SIZEOF_STR];
7174         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7175                 return FALSE;
7177         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7180 static enum input_status
7181 read_prompt_handler(void *data, char *buf, int c)
7183         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7186 static char *
7187 read_prompt(const char *prompt)
7189         return prompt_input(prompt, read_prompt_handler, NULL);
7192 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7194         enum input_status status = INPUT_OK;
7195         int size = 0;
7197         while (items[size].text)
7198                 size++;
7200         while (status == INPUT_OK) {
7201                 const struct menu_item *item = &items[*selected];
7202                 int key;
7203                 int i;
7205                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7206                           prompt, *selected + 1, size);
7207                 if (item->hotkey)
7208                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7209                 wprintw(status_win, "%s", item->text);
7210                 wclrtoeol(status_win);
7212                 key = get_input(COLS - 1);
7213                 switch (key) {
7214                 case KEY_RETURN:
7215                 case KEY_ENTER:
7216                 case '\n':
7217                         status = INPUT_STOP;
7218                         break;
7220                 case KEY_LEFT:
7221                 case KEY_UP:
7222                         *selected = *selected - 1;
7223                         if (*selected < 0)
7224                                 *selected = size - 1;
7225                         break;
7227                 case KEY_RIGHT:
7228                 case KEY_DOWN:
7229                         *selected = (*selected + 1) % size;
7230                         break;
7232                 case KEY_ESC:
7233                         status = INPUT_CANCEL;
7234                         break;
7236                 default:
7237                         for (i = 0; items[i].text; i++)
7238                                 if (items[i].hotkey == key) {
7239                                         *selected = i;
7240                                         status = INPUT_STOP;
7241                                         break;
7242                                 }
7243                 }
7244         }
7246         /* Clear the status window */
7247         status_empty = FALSE;
7248         report("");
7250         return status != INPUT_CANCEL;
7253 /*
7254  * Repository properties
7255  */
7257 static struct ref **refs = NULL;
7258 static size_t refs_size = 0;
7259 static struct ref *refs_head = NULL;
7261 static struct ref_list **ref_lists = NULL;
7262 static size_t ref_lists_size = 0;
7264 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7265 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7266 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7268 static int
7269 compare_refs(const void *ref1_, const void *ref2_)
7271         const struct ref *ref1 = *(const struct ref **)ref1_;
7272         const struct ref *ref2 = *(const struct ref **)ref2_;
7274         if (ref1->tag != ref2->tag)
7275                 return ref2->tag - ref1->tag;
7276         if (ref1->ltag != ref2->ltag)
7277                 return ref2->ltag - ref2->ltag;
7278         if (ref1->head != ref2->head)
7279                 return ref2->head - ref1->head;
7280         if (ref1->tracked != ref2->tracked)
7281                 return ref2->tracked - ref1->tracked;
7282         if (ref1->remote != ref2->remote)
7283                 return ref2->remote - ref1->remote;
7284         return strcmp(ref1->name, ref2->name);
7287 static void
7288 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7290         size_t i;
7292         for (i = 0; i < refs_size; i++)
7293                 if (!visitor(data, refs[i]))
7294                         break;
7297 static struct ref *
7298 get_ref_head()
7300         return refs_head;
7303 static struct ref_list *
7304 get_ref_list(const char *id)
7306         struct ref_list *list;
7307         size_t i;
7309         for (i = 0; i < ref_lists_size; i++)
7310                 if (!strcmp(id, ref_lists[i]->id))
7311                         return ref_lists[i];
7313         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7314                 return NULL;
7315         list = calloc(1, sizeof(*list));
7316         if (!list)
7317                 return NULL;
7319         for (i = 0; i < refs_size; i++) {
7320                 if (!strcmp(id, refs[i]->id) &&
7321                     realloc_refs_list(&list->refs, list->size, 1))
7322                         list->refs[list->size++] = refs[i];
7323         }
7325         if (!list->refs) {
7326                 free(list);
7327                 return NULL;
7328         }
7330         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7331         ref_lists[ref_lists_size++] = list;
7332         return list;
7335 static int
7336 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7338         struct ref *ref = NULL;
7339         bool tag = FALSE;
7340         bool ltag = FALSE;
7341         bool remote = FALSE;
7342         bool tracked = FALSE;
7343         bool head = FALSE;
7344         int from = 0, to = refs_size - 1;
7346         if (!prefixcmp(name, "refs/tags/")) {
7347                 if (!suffixcmp(name, namelen, "^{}")) {
7348                         namelen -= 3;
7349                         name[namelen] = 0;
7350                 } else {
7351                         ltag = TRUE;
7352                 }
7354                 tag = TRUE;
7355                 namelen -= STRING_SIZE("refs/tags/");
7356                 name    += STRING_SIZE("refs/tags/");
7358         } else if (!prefixcmp(name, "refs/remotes/")) {
7359                 remote = TRUE;
7360                 namelen -= STRING_SIZE("refs/remotes/");
7361                 name    += STRING_SIZE("refs/remotes/");
7362                 tracked  = !strcmp(opt_remote, name);
7364         } else if (!prefixcmp(name, "refs/heads/")) {
7365                 namelen -= STRING_SIZE("refs/heads/");
7366                 name    += STRING_SIZE("refs/heads/");
7367                 if (!strncmp(opt_head, name, namelen))
7368                         return OK;
7370         } else if (!strcmp(name, "HEAD")) {
7371                 head     = TRUE;
7372                 if (*opt_head) {
7373                         namelen  = strlen(opt_head);
7374                         name     = opt_head;
7375                 }
7376         }
7378         /* If we are reloading or it's an annotated tag, replace the
7379          * previous SHA1 with the resolved commit id; relies on the fact
7380          * git-ls-remote lists the commit id of an annotated tag right
7381          * before the commit id it points to. */
7382         while (from <= to) {
7383                 size_t pos = (to + from) / 2;
7384                 int cmp = strcmp(name, refs[pos]->name);
7386                 if (!cmp) {
7387                         ref = refs[pos];
7388                         break;
7389                 }
7391                 if (cmp < 0)
7392                         to = pos - 1;
7393                 else
7394                         from = pos + 1;
7395         }
7397         if (!ref) {
7398                 if (!realloc_refs(&refs, refs_size, 1))
7399                         return ERR;
7400                 ref = calloc(1, sizeof(*ref) + namelen);
7401                 if (!ref)
7402                         return ERR;
7403                 memmove(refs + from + 1, refs + from,
7404                         (refs_size - from) * sizeof(*refs));
7405                 refs[from] = ref;
7406                 strncpy(ref->name, name, namelen);
7407                 refs_size++;
7408         }
7410         ref->head = head;
7411         ref->tag = tag;
7412         ref->ltag = ltag;
7413         ref->remote = remote;
7414         ref->tracked = tracked;
7415         string_copy_rev(ref->id, id);
7417         if (head)
7418                 refs_head = ref;
7419         return OK;
7422 static int
7423 load_refs(void)
7425         const char *head_argv[] = {
7426                 "git", "symbolic-ref", "HEAD", NULL
7427         };
7428         static const char *ls_remote_argv[SIZEOF_ARG] = {
7429                 "git", "ls-remote", opt_git_dir, NULL
7430         };
7431         static bool init = FALSE;
7432         size_t i;
7434         if (!init) {
7435                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7436                         die("TIG_LS_REMOTE contains too many arguments");
7437                 init = TRUE;
7438         }
7440         if (!*opt_git_dir)
7441                 return OK;
7443         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7444             !prefixcmp(opt_head, "refs/heads/")) {
7445                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7447                 memmove(opt_head, offset, strlen(offset) + 1);
7448         }
7450         refs_head = NULL;
7451         for (i = 0; i < refs_size; i++)
7452                 refs[i]->id[0] = 0;
7454         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7455                 return ERR;
7457         /* Update the ref lists to reflect changes. */
7458         for (i = 0; i < ref_lists_size; i++) {
7459                 struct ref_list *list = ref_lists[i];
7460                 size_t old, new;
7462                 for (old = new = 0; old < list->size; old++)
7463                         if (!strcmp(list->id, list->refs[old]->id))
7464                                 list->refs[new++] = list->refs[old];
7465                 list->size = new;
7466         }
7468         return OK;
7471 static void
7472 set_remote_branch(const char *name, const char *value, size_t valuelen)
7474         if (!strcmp(name, ".remote")) {
7475                 string_ncopy(opt_remote, value, valuelen);
7477         } else if (*opt_remote && !strcmp(name, ".merge")) {
7478                 size_t from = strlen(opt_remote);
7480                 if (!prefixcmp(value, "refs/heads/"))
7481                         value += STRING_SIZE("refs/heads/");
7483                 if (!string_format_from(opt_remote, &from, "/%s", value))
7484                         opt_remote[0] = 0;
7485         }
7488 static void
7489 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7491         const char *argv[SIZEOF_ARG] = { name, "=" };
7492         int argc = 1 + (cmd == option_set_command);
7493         int error = ERR;
7495         if (!argv_from_string(argv, &argc, value))
7496                 config_msg = "Too many option arguments";
7497         else
7498                 error = cmd(argc, argv);
7500         if (error == ERR)
7501                 warn("Option 'tig.%s': %s", name, config_msg);
7504 static bool
7505 set_environment_variable(const char *name, const char *value)
7507         size_t len = strlen(name) + 1 + strlen(value) + 1;
7508         char *env = malloc(len);
7510         if (env &&
7511             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7512             putenv(env) == 0)
7513                 return TRUE;
7514         free(env);
7515         return FALSE;
7518 static void
7519 set_work_tree(const char *value)
7521         char cwd[SIZEOF_STR];
7523         if (!getcwd(cwd, sizeof(cwd)))
7524                 die("Failed to get cwd path: %s", strerror(errno));
7525         if (chdir(opt_git_dir) < 0)
7526                 die("Failed to chdir(%s): %s", strerror(errno));
7527         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7528                 die("Failed to get git path: %s", strerror(errno));
7529         if (chdir(cwd) < 0)
7530                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7531         if (chdir(value) < 0)
7532                 die("Failed to chdir(%s): %s", value, strerror(errno));
7533         if (!getcwd(cwd, sizeof(cwd)))
7534                 die("Failed to get cwd path: %s", strerror(errno));
7535         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7536                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7537         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7538                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7539         opt_is_inside_work_tree = TRUE;
7542 static int
7543 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7545         if (!strcmp(name, "i18n.commitencoding"))
7546                 string_ncopy(opt_encoding, value, valuelen);
7548         else if (!strcmp(name, "core.editor"))
7549                 string_ncopy(opt_editor, value, valuelen);
7551         else if (!strcmp(name, "core.worktree"))
7552                 set_work_tree(value);
7554         else if (!prefixcmp(name, "tig.color."))
7555                 set_repo_config_option(name + 10, value, option_color_command);
7557         else if (!prefixcmp(name, "tig.bind."))
7558                 set_repo_config_option(name + 9, value, option_bind_command);
7560         else if (!prefixcmp(name, "tig."))
7561                 set_repo_config_option(name + 4, value, option_set_command);
7563         else if (*opt_head && !prefixcmp(name, "branch.") &&
7564                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7565                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7567         return OK;
7570 static int
7571 load_git_config(void)
7573         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7575         return io_run_load(config_list_argv, "=", read_repo_config_option);
7578 static int
7579 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7581         if (!opt_git_dir[0]) {
7582                 string_ncopy(opt_git_dir, name, namelen);
7584         } else if (opt_is_inside_work_tree == -1) {
7585                 /* This can be 3 different values depending on the
7586                  * version of git being used. If git-rev-parse does not
7587                  * understand --is-inside-work-tree it will simply echo
7588                  * the option else either "true" or "false" is printed.
7589                  * Default to true for the unknown case. */
7590                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7592         } else if (*name == '.') {
7593                 string_ncopy(opt_cdup, name, namelen);
7595         } else {
7596                 string_ncopy(opt_prefix, name, namelen);
7597         }
7599         return OK;
7602 static int
7603 load_repo_info(void)
7605         const char *rev_parse_argv[] = {
7606                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7607                         "--show-cdup", "--show-prefix", NULL
7608         };
7610         return io_run_load(rev_parse_argv, "=", read_repo_info);
7614 /*
7615  * Main
7616  */
7618 static const char usage[] =
7619 "tig " TIG_VERSION " (" __DATE__ ")\n"
7620 "\n"
7621 "Usage: tig        [options] [revs] [--] [paths]\n"
7622 "   or: tig show   [options] [revs] [--] [paths]\n"
7623 "   or: tig blame  [rev] path\n"
7624 "   or: tig status\n"
7625 "   or: tig <      [git command output]\n"
7626 "\n"
7627 "Options:\n"
7628 "  -v, --version   Show version and exit\n"
7629 "  -h, --help      Show help message and exit";
7631 static void __NORETURN
7632 quit(int sig)
7634         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7635         if (cursed)
7636                 endwin();
7637         exit(0);
7640 static void __NORETURN
7641 die(const char *err, ...)
7643         va_list args;
7645         endwin();
7647         va_start(args, err);
7648         fputs("tig: ", stderr);
7649         vfprintf(stderr, err, args);
7650         fputs("\n", stderr);
7651         va_end(args);
7653         exit(1);
7656 static void
7657 warn(const char *msg, ...)
7659         va_list args;
7661         va_start(args, msg);
7662         fputs("tig warning: ", stderr);
7663         vfprintf(stderr, msg, args);
7664         fputs("\n", stderr);
7665         va_end(args);
7668 static enum request
7669 parse_options(int argc, const char *argv[])
7671         enum request request = REQ_VIEW_MAIN;
7672         const char *subcommand;
7673         bool seen_dashdash = FALSE;
7674         /* XXX: This is vulnerable to the user overriding options
7675          * required for the main view parser. */
7676         const char *custom_argv[SIZEOF_ARG] = {
7677                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7678                         "--topo-order", NULL
7679         };
7680         int i, j = 6;
7682         if (!isatty(STDIN_FILENO)) {
7683                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7684                 return REQ_VIEW_PAGER;
7685         }
7687         if (argc <= 1)
7688                 return REQ_NONE;
7690         subcommand = argv[1];
7691         if (!strcmp(subcommand, "status")) {
7692                 if (argc > 2)
7693                         warn("ignoring arguments after `%s'", subcommand);
7694                 return REQ_VIEW_STATUS;
7696         } else if (!strcmp(subcommand, "blame")) {
7697                 if (argc <= 2 || argc > 4)
7698                         die("invalid number of options to blame\n\n%s", usage);
7700                 i = 2;
7701                 if (argc == 4) {
7702                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7703                         i++;
7704                 }
7706                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7707                 return REQ_VIEW_BLAME;
7709         } else if (!strcmp(subcommand, "show")) {
7710                 request = REQ_VIEW_DIFF;
7712         } else {
7713                 subcommand = NULL;
7714         }
7716         if (subcommand) {
7717                 custom_argv[1] = subcommand;
7718                 j = 2;
7719         }
7721         for (i = 1 + !!subcommand; i < argc; i++) {
7722                 const char *opt = argv[i];
7724                 if (seen_dashdash || !strcmp(opt, "--")) {
7725                         seen_dashdash = TRUE;
7727                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7728                         printf("tig version %s\n", TIG_VERSION);
7729                         quit(0);
7731                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7732                         printf("%s\n", usage);
7733                         quit(0);
7734                 }
7736                 custom_argv[j++] = opt;
7737                 if (j >= ARRAY_SIZE(custom_argv))
7738                         die("command too long");
7739         }
7741         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7742                 die("Failed to format arguments");
7744         return request;
7747 int
7748 main(int argc, const char *argv[])
7750         const char *codeset = "UTF-8";
7751         enum request request = parse_options(argc, argv);
7752         struct view *view;
7753         size_t i;
7755         signal(SIGINT, quit);
7756         signal(SIGPIPE, SIG_IGN);
7758         if (setlocale(LC_ALL, "")) {
7759                 codeset = nl_langinfo(CODESET);
7760         }
7762         if (load_repo_info() == ERR)
7763                 die("Failed to load repo info.");
7765         if (load_options() == ERR)
7766                 die("Failed to load user config.");
7768         if (load_git_config() == ERR)
7769                 die("Failed to load repo config.");
7771         /* Require a git repository unless when running in pager mode. */
7772         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7773                 die("Not a git repository");
7775         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7776                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7777                 if (opt_iconv_in == ICONV_NONE)
7778                         die("Failed to initialize character set conversion");
7779         }
7781         if (codeset && strcmp(codeset, "UTF-8")) {
7782                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7783                 if (opt_iconv_out == ICONV_NONE)
7784                         die("Failed to initialize character set conversion");
7785         }
7787         if (load_refs() == ERR)
7788                 die("Failed to load refs.");
7790         foreach_view (view, i)
7791                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7792                         die("Too many arguments in the `%s` environment variable",
7793                             view->cmd_env);
7795         init_display();
7797         if (request != REQ_NONE)
7798                 open_view(NULL, request, OPEN_PREPARED);
7799         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7801         while (view_driver(display[current_view], request)) {
7802                 int key = get_input(0);
7804                 view = display[current_view];
7805                 request = get_keybinding(view->keymap, key);
7807                 /* Some low-level request handling. This keeps access to
7808                  * status_win restricted. */
7809                 switch (request) {
7810                 case REQ_PROMPT:
7811                 {
7812                         char *cmd = read_prompt(":");
7814                         if (cmd && isdigit(*cmd)) {
7815                                 int lineno = view->lineno + 1;
7817                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7818                                         select_view_line(view, lineno - 1);
7819                                         report("");
7820                                 } else {
7821                                         report("Unable to parse '%s' as a line number", cmd);
7822                                 }
7824                         } else if (cmd) {
7825                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7826                                 const char *argv[SIZEOF_ARG] = { "git" };
7827                                 int argc = 1;
7829                                 /* When running random commands, initially show the
7830                                  * command in the title. However, it maybe later be
7831                                  * overwritten if a commit line is selected. */
7832                                 string_ncopy(next->ref, cmd, strlen(cmd));
7834                                 if (!argv_from_string(argv, &argc, cmd)) {
7835                                         report("Too many arguments");
7836                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7837                                         report("Failed to format command");
7838                                 } else {
7839                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7840                                 }
7841                         }
7843                         request = REQ_NONE;
7844                         break;
7845                 }
7846                 case REQ_SEARCH:
7847                 case REQ_SEARCH_BACK:
7848                 {
7849                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7850                         char *search = read_prompt(prompt);
7852                         if (search)
7853                                 string_ncopy(opt_search, search, strlen(search));
7854                         else if (*opt_search)
7855                                 request = request == REQ_SEARCH ?
7856                                         REQ_FIND_NEXT :
7857                                         REQ_FIND_PREV;
7858                         else
7859                                 request = REQ_NONE;
7860                         break;
7861                 }
7862                 default:
7863                         break;
7864                 }
7865         }
7867         quit(0);
7869         return 0;