Code

Fix set_keymap to error when resolving an unknown keymap
[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_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151         INPUT_OK,
152         INPUT_SKIP,
153         INPUT_STOP,
154         INPUT_CANCEL
155 };
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163         int hotkey;
164         const char *text;
165         void *data;
166 };
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
170 /*
171  * Allocation helpers ... Entering macro hell to never be seen again.
172  */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
175 static type *                                                                   \
176 name(type **mem, size_t size, size_t increase)                                  \
177 {                                                                               \
178         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
179         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180         type *tmp = *mem;                                                       \
181                                                                                 \
182         if (mem == NULL || num_chunks != num_chunks_new) {                      \
183                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184                 if (tmp)                                                        \
185                         *mem = tmp;                                             \
186         }                                                                       \
187                                                                                 \
188         return tmp;                                                             \
191 /*
192  * String helpers
193  */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198         if (srclen > dstlen - 1)
199                 srclen = dstlen - 1;
201         strncpy(dst, src, srclen);
202         dst[srclen] = 0;
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211         string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
222         size_t size, pos;
224         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225                 if (src[pos] == '\t') {
226                         size_t expanded = tabsize - (size % tabsize);
228                         if (expanded + size >= dstlen - 1)
229                                 expanded = dstlen - size - 1;
230                         memcpy(dst + size, "        ", expanded);
231                         size += expanded;
232                 } else {
233                         dst[size++] = src[pos];
234                 }
235         }
237         dst[size] = 0;
240 static char *
241 chomp_string(char *name)
243         int namelen;
245         while (isspace(*name))
246                 name++;
248         namelen = strlen(name) - 1;
249         while (namelen > 0 && isspace(name[namelen]))
250                 name[namelen--] = 0;
252         return name;
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
258         va_list args;
259         size_t pos = bufpos ? *bufpos : 0;
261         va_start(args, fmt);
262         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263         va_end(args);
265         if (bufpos)
266                 *bufpos = pos;
268         return pos >= bufsize ? FALSE : TRUE;
271 #define string_format(buf, fmt, args...) \
272         string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275         string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
280         size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284         /* Diff-Header == DIFF_HEADER */
285         for (i = 0; i < len; i++) {
286                 if (toupper(str1[i]) == toupper(str2[i]))
287                         continue;
289                 if (string_enum_sep(str1[i]) &&
290                     string_enum_sep(str2[i]))
291                         continue;
293                 return str1[i] - str2[i];
294         }
296         return 0;
299 #define enum_equals(entry, str, len) \
300         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303         const char *name;
304         int namelen;
305         int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
313         static char buf[SIZEOF_STR];
314         int bufpos;
316         for (bufpos = 0; bufpos <= namelen; bufpos++) {
317                 buf[bufpos] = tolower(name[bufpos]);
318                 if (buf[bufpos] == '_')
319                         buf[bufpos] = '-';
320         }
322         buf[bufpos] = 0;
323         return buf;
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
331         size_t namelen = strlen(name);
332         int i;
334         for (i = 0; i < map_size; i++)
335                 if (enum_equals(map[i], name, namelen)) {
336                         *value = map[i].value;
337                         return TRUE;
338                 }
340         return FALSE;
343 #define map_enum(attr, map, name) \
344         map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347         strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
352         size_t len = slen >= 0 ? slen : strlen(str);
353         size_t suffixlen = strlen(suffix);
355         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
359 /*
360  * Unicode / UTF-8 handling
361  *
362  * NOTE: Much of the following code for dealing with Unicode is derived from
363  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
365  */
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
370         if (c >= 0x1100 &&
371            (c <= 0x115f                         /* Hangul Jamo */
372             || c == 0x2329
373             || c == 0x232a
374             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
375                                                 /* CJK ... Yi */
376             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
377             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
378             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
379             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
380             || (c >= 0xffe0  && c <= 0xffe6)
381             || (c >= 0x20000 && c <= 0x2fffd)
382             || (c >= 0x30000 && c <= 0x3fffd)))
383                 return 2;
385         if (c == '\t')
386                 return tab_size;
388         return 1;
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392  * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394         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,
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         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,
401         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,
402 };
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
407         int c = *(unsigned char *) string;
409         return utf8_bytes[c];
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
416         unsigned long unicode;
418         switch (length) {
419         case 1:
420                 unicode  =   string[0];
421                 break;
422         case 2:
423                 unicode  =  (string[0] & 0x1f) << 6;
424                 unicode +=  (string[1] & 0x3f);
425                 break;
426         case 3:
427                 unicode  =  (string[0] & 0x0f) << 12;
428                 unicode += ((string[1] & 0x3f) << 6);
429                 unicode +=  (string[2] & 0x3f);
430                 break;
431         case 4:
432                 unicode  =  (string[0] & 0x0f) << 18;
433                 unicode += ((string[1] & 0x3f) << 12);
434                 unicode += ((string[2] & 0x3f) << 6);
435                 unicode +=  (string[3] & 0x3f);
436                 break;
437         case 5:
438                 unicode  =  (string[0] & 0x0f) << 24;
439                 unicode += ((string[1] & 0x3f) << 18);
440                 unicode += ((string[2] & 0x3f) << 12);
441                 unicode += ((string[3] & 0x3f) << 6);
442                 unicode +=  (string[4] & 0x3f);
443                 break;
444         case 6:
445                 unicode  =  (string[0] & 0x01) << 30;
446                 unicode += ((string[1] & 0x3f) << 24);
447                 unicode += ((string[2] & 0x3f) << 18);
448                 unicode += ((string[3] & 0x3f) << 12);
449                 unicode += ((string[4] & 0x3f) << 6);
450                 unicode +=  (string[5] & 0x3f);
451                 break;
452         default:
453                 return 0;
454         }
456         /* Invalid characters could return the special 0xfffd value but NUL
457          * should be just as good. */
458         return unicode > 0xffff ? 0 : unicode;
461 /* Calculates how much of string can be shown within the given maximum width
462  * and sets trimmed parameter to non-zero value if all of string could not be
463  * shown. If the reserve flag is TRUE, it will reserve at least one
464  * trailing character, which can be useful when drawing a delimiter.
465  *
466  * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
470         const char *string = *start;
471         const char *end = strchr(string, '\0');
472         unsigned char last_bytes = 0;
473         size_t last_ucwidth = 0;
475         *width = 0;
476         *trimmed = 0;
478         while (string < end) {
479                 unsigned char bytes = utf8_char_length(string, end);
480                 size_t ucwidth;
481                 unsigned long unicode;
483                 if (string + bytes > end)
484                         break;
486                 /* Change representation to figure out whether
487                  * it is a single- or double-width character. */
489                 unicode = utf8_to_unicode(string, bytes);
490                 /* FIXME: Graceful handling of invalid Unicode character. */
491                 if (!unicode)
492                         break;
494                 ucwidth = unicode_width(unicode, tab_size);
495                 if (skip > 0) {
496                         skip -= ucwidth <= skip ? ucwidth : skip;
497                         *start += bytes;
498                 }
499                 *width  += ucwidth;
500                 if (*width > max_width) {
501                         *trimmed = 1;
502                         *width -= ucwidth;
503                         if (reserve && *width == max_width) {
504                                 string -= last_bytes;
505                                 *width -= last_ucwidth;
506                         }
507                         break;
508                 }
510                 string  += bytes;
511                 last_bytes = ucwidth ? bytes : 0;
512                 last_ucwidth = ucwidth;
513         }
515         return string - *start;
519 #define DATE_INFO \
520         DATE_(NO), \
521         DATE_(DEFAULT), \
522         DATE_(LOCAL), \
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         if (date == DATE_LOCAL) {
588                 time_t date = time->sec + time->tz;
589                 localtime_r(&date, &tm);
590         }
591         else {
592                 gmtime_r(&time->sec, &tm);
593         }
594         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
598 #define AUTHOR_VALUES \
599         AUTHOR_(NO), \
600         AUTHOR_(FULL), \
601         AUTHOR_(ABBREVIATED)
603 enum author {
604 #define AUTHOR_(name) AUTHOR_##name
605         AUTHOR_VALUES,
606 #undef  AUTHOR_
607         AUTHOR_DEFAULT = AUTHOR_FULL
608 };
610 static const struct enum_map author_map[] = {
611 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
612         AUTHOR_VALUES
613 #undef  AUTHOR_
614 };
616 static const char *
617 get_author_initials(const char *author)
619         static char initials[AUTHOR_COLS * 6 + 1];
620         size_t pos = 0;
621         const char *end = strchr(author, '\0');
623 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
625         memset(initials, 0, sizeof(initials));
626         while (author < end) {
627                 unsigned char bytes;
628                 size_t i;
630                 while (is_initial_sep(*author))
631                         author++;
633                 bytes = utf8_char_length(author, end);
634                 if (bytes < sizeof(initials) - 1 - pos) {
635                         while (bytes--) {
636                                 initials[pos++] = *author++;
637                         }
638                 }
640                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
641                         if (i < sizeof(initials) - 1)
642                                 initials[i++] = *author;
643                 }
645                 initials[i++] = 0;
646         }
648         return initials;
652 static bool
653 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
655         int valuelen;
657         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
658                 bool advance = cmd[valuelen] != 0;
660                 cmd[valuelen] = 0;
661                 argv[(*argc)++] = chomp_string(cmd);
662                 cmd = chomp_string(cmd + valuelen + advance);
663         }
665         if (*argc < SIZEOF_ARG)
666                 argv[*argc] = NULL;
667         return *argc < SIZEOF_ARG;
670 static bool
671 argv_from_env(const char **argv, const char *name)
673         char *env = argv ? getenv(name) : NULL;
674         int argc = 0;
676         if (env && *env)
677                 env = strdup(env);
678         return !env || argv_from_string(argv, &argc, env);
682 /*
683  * Executing external commands.
684  */
686 enum io_type {
687         IO_FD,                  /* File descriptor based IO. */
688         IO_BG,                  /* Execute command in the background. */
689         IO_FG,                  /* Execute command with same std{in,out,err}. */
690         IO_RD,                  /* Read only fork+exec IO. */
691         IO_WR,                  /* Write only fork+exec IO. */
692         IO_AP,                  /* Append fork+exec output to file. */
693 };
695 struct io {
696         enum io_type type;      /* The requested type of pipe. */
697         const char *dir;        /* Directory from which to execute. */
698         pid_t pid;              /* PID of spawned process. */
699         int pipe;               /* Pipe end for reading or writing. */
700         int error;              /* Error status. */
701         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
702         char *buf;              /* Read buffer. */
703         size_t bufalloc;        /* Allocated buffer size. */
704         size_t bufsize;         /* Buffer content size. */
705         char *bufpos;           /* Current buffer position. */
706         unsigned int eof:1;     /* Has end of file been reached. */
707 };
709 static void
710 io_reset(struct io *io)
712         io->pipe = -1;
713         io->pid = 0;
714         io->buf = io->bufpos = NULL;
715         io->bufalloc = io->bufsize = 0;
716         io->error = 0;
717         io->eof = 0;
720 static void
721 io_init(struct io *io, const char *dir, enum io_type type)
723         io_reset(io);
724         io->type = type;
725         io->dir = dir;
728 static bool
729 io_format(struct io *io, const char *dir, enum io_type type,
730           const char *argv[], enum format_flags flags)
732         io_init(io, dir, type);
733         return format_argv(io->argv, argv, flags);
736 static bool
737 io_open(struct io *io, const char *fmt, ...)
739         char name[SIZEOF_STR] = "";
740         bool fits;
741         va_list args;
743         io_init(io, NULL, IO_FD);
745         va_start(args, fmt);
746         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
747         va_end(args);
749         if (!fits) {
750                 io->error = ENAMETOOLONG;
751                 return FALSE;
752         }
753         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
754         if (io->pipe == -1)
755                 io->error = errno;
756         return io->pipe != -1;
759 static bool
760 io_kill(struct io *io)
762         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
765 static bool
766 io_done(struct io *io)
768         pid_t pid = io->pid;
770         if (io->pipe != -1)
771                 close(io->pipe);
772         free(io->buf);
773         io_reset(io);
775         while (pid > 0) {
776                 int status;
777                 pid_t waiting = waitpid(pid, &status, 0);
779                 if (waiting < 0) {
780                         if (errno == EINTR)
781                                 continue;
782                         io->error = errno;
783                         return FALSE;
784                 }
786                 return waiting == pid &&
787                        !WIFSIGNALED(status) &&
788                        WIFEXITED(status) &&
789                        !WEXITSTATUS(status);
790         }
792         return TRUE;
795 static bool
796 io_start(struct io *io)
798         int pipefds[2] = { -1, -1 };
800         if (io->type == IO_FD)
801                 return TRUE;
803         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
804                 io->error = errno;
805                 return FALSE;
806         } else if (io->type == IO_AP) {
807                 pipefds[1] = io->pipe;
808         }
810         if ((io->pid = fork())) {
811                 if (io->pid == -1)
812                         io->error = errno;
813                 if (pipefds[!(io->type == IO_WR)] != -1)
814                         close(pipefds[!(io->type == IO_WR)]);
815                 if (io->pid != -1) {
816                         io->pipe = pipefds[!!(io->type == IO_WR)];
817                         return TRUE;
818                 }
820         } else {
821                 if (io->type != IO_FG) {
822                         int devnull = open("/dev/null", O_RDWR);
823                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
824                         int writefd = (io->type == IO_RD || io->type == IO_AP)
825                                                         ? pipefds[1] : devnull;
827                         dup2(readfd,  STDIN_FILENO);
828                         dup2(writefd, STDOUT_FILENO);
829                         dup2(devnull, STDERR_FILENO);
831                         close(devnull);
832                         if (pipefds[0] != -1)
833                                 close(pipefds[0]);
834                         if (pipefds[1] != -1)
835                                 close(pipefds[1]);
836                 }
838                 if (io->dir && *io->dir && chdir(io->dir) == -1)
839                         exit(errno);
841                 execvp(io->argv[0], (char *const*) io->argv);
842                 exit(errno);
843         }
845         if (pipefds[!!(io->type == IO_WR)] != -1)
846                 close(pipefds[!!(io->type == IO_WR)]);
847         return FALSE;
850 static bool
851 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
853         io_init(io, dir, type);
854         if (!format_argv(io->argv, argv, FORMAT_NONE))
855                 return FALSE;
856         return io_start(io);
859 static int
860 io_complete(struct io *io)
862         return io_start(io) && io_done(io);
865 static int
866 io_run_bg(const char **argv)
868         struct io io = {};
870         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
871                 return FALSE;
872         return io_complete(&io);
875 static bool
876 io_run_fg(const char **argv, const char *dir)
878         struct io io = {};
880         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
881                 return FALSE;
882         return io_complete(&io);
885 static bool
886 io_run_append(const char **argv, enum format_flags flags, int fd)
888         struct io io = {};
890         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
891                 close(fd);
892                 return FALSE;
893         }
895         io.pipe = fd;
896         return io_complete(&io);
899 static bool
900 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
902         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
905 static bool
906 io_eof(struct io *io)
908         return io->eof;
911 static int
912 io_error(struct io *io)
914         return io->error;
917 static char *
918 io_strerror(struct io *io)
920         return strerror(io->error);
923 static bool
924 io_can_read(struct io *io)
926         struct timeval tv = { 0, 500 };
927         fd_set fds;
929         FD_ZERO(&fds);
930         FD_SET(io->pipe, &fds);
932         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
935 static ssize_t
936 io_read(struct io *io, void *buf, size_t bufsize)
938         do {
939                 ssize_t readsize = read(io->pipe, buf, bufsize);
941                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
942                         continue;
943                 else if (readsize == -1)
944                         io->error = errno;
945                 else if (readsize == 0)
946                         io->eof = 1;
947                 return readsize;
948         } while (1);
951 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
953 static char *
954 io_get(struct io *io, int c, bool can_read)
956         char *eol;
957         ssize_t readsize;
959         while (TRUE) {
960                 if (io->bufsize > 0) {
961                         eol = memchr(io->bufpos, c, io->bufsize);
962                         if (eol) {
963                                 char *line = io->bufpos;
965                                 *eol = 0;
966                                 io->bufpos = eol + 1;
967                                 io->bufsize -= io->bufpos - line;
968                                 return line;
969                         }
970                 }
972                 if (io_eof(io)) {
973                         if (io->bufsize) {
974                                 io->bufpos[io->bufsize] = 0;
975                                 io->bufsize = 0;
976                                 return io->bufpos;
977                         }
978                         return NULL;
979                 }
981                 if (!can_read)
982                         return NULL;
984                 if (io->bufsize > 0 && io->bufpos > io->buf)
985                         memmove(io->buf, io->bufpos, io->bufsize);
987                 if (io->bufalloc == io->bufsize) {
988                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
989                                 return NULL;
990                         io->bufalloc += BUFSIZ;
991                 }
993                 io->bufpos = io->buf;
994                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
995                 if (io_error(io))
996                         return NULL;
997                 io->bufsize += readsize;
998         }
1001 static bool
1002 io_write(struct io *io, const void *buf, size_t bufsize)
1004         size_t written = 0;
1006         while (!io_error(io) && written < bufsize) {
1007                 ssize_t size;
1009                 size = write(io->pipe, buf + written, bufsize - written);
1010                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1011                         continue;
1012                 else if (size == -1)
1013                         io->error = errno;
1014                 else
1015                         written += size;
1016         }
1018         return written == bufsize;
1021 static bool
1022 io_read_buf(struct io *io, char buf[], size_t bufsize)
1024         char *result = io_get(io, '\n', TRUE);
1026         if (result) {
1027                 result = chomp_string(result);
1028                 string_ncopy_do(buf, bufsize, result, strlen(result));
1029         }
1031         return io_done(io) && result;
1034 static bool
1035 io_run_buf(const char **argv, char buf[], size_t bufsize)
1037         struct io io = {};
1039         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1040             && io_read_buf(&io, buf, bufsize);
1043 static int
1044 io_load(struct io *io, const char *separators,
1045         int (*read_property)(char *, size_t, char *, size_t))
1047         char *name;
1048         int state = OK;
1050         if (!io_start(io))
1051                 return ERR;
1053         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054                 char *value;
1055                 size_t namelen;
1056                 size_t valuelen;
1058                 name = chomp_string(name);
1059                 namelen = strcspn(name, separators);
1061                 if (name[namelen]) {
1062                         name[namelen] = 0;
1063                         value = chomp_string(name + namelen + 1);
1064                         valuelen = strlen(value);
1066                 } else {
1067                         value = "";
1068                         valuelen = 0;
1069                 }
1071                 state = read_property(name, namelen, value, valuelen);
1072         }
1074         if (state != ERR && io_error(io))
1075                 state = ERR;
1076         io_done(io);
1078         return state;
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083             int (*read_property)(char *, size_t, char *, size_t))
1085         struct io io = {};
1087         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1088                 ? io_load(&io, separators, read_property) : ERR;
1092 /*
1093  * User requests
1094  */
1096 #define REQ_INFO \
1097         /* XXX: Keep the view request first and in sync with views[]. */ \
1098         REQ_GROUP("View switching") \
1099         REQ_(VIEW_MAIN,         "Show main view"), \
1100         REQ_(VIEW_DIFF,         "Show diff view"), \
1101         REQ_(VIEW_LOG,          "Show log view"), \
1102         REQ_(VIEW_TREE,         "Show tree view"), \
1103         REQ_(VIEW_BLOB,         "Show blob view"), \
1104         REQ_(VIEW_BLAME,        "Show blame view"), \
1105         REQ_(VIEW_BRANCH,       "Show branch view"), \
1106         REQ_(VIEW_HELP,         "Show help page"), \
1107         REQ_(VIEW_PAGER,        "Show pager view"), \
1108         REQ_(VIEW_STATUS,       "Show status view"), \
1109         REQ_(VIEW_STAGE,        "Show stage view"), \
1110         \
1111         REQ_GROUP("View manipulation") \
1112         REQ_(ENTER,             "Enter current line and scroll"), \
1113         REQ_(NEXT,              "Move to next"), \
1114         REQ_(PREVIOUS,          "Move to previous"), \
1115         REQ_(PARENT,            "Move to parent"), \
1116         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1117         REQ_(REFRESH,           "Reload and refresh"), \
1118         REQ_(MAXIMIZE,          "Maximize the current view"), \
1119         REQ_(VIEW_CLOSE,        "Close the current view"), \
1120         REQ_(QUIT,              "Close all views and quit"), \
1121         \
1122         REQ_GROUP("View specific requests") \
1123         REQ_(STATUS_UPDATE,     "Update file status"), \
1124         REQ_(STATUS_REVERT,     "Revert file changes"), \
1125         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1126         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1127         \
1128         REQ_GROUP("Cursor navigation") \
1129         REQ_(MOVE_UP,           "Move cursor one line up"), \
1130         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1131         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1132         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1133         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1134         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1135         \
1136         REQ_GROUP("Scrolling") \
1137         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1138         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1139         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1140         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1141         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1142         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1143         \
1144         REQ_GROUP("Searching") \
1145         REQ_(SEARCH,            "Search the view"), \
1146         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1147         REQ_(FIND_NEXT,         "Find next search match"), \
1148         REQ_(FIND_PREV,         "Find previous search match"), \
1149         \
1150         REQ_GROUP("Option manipulation") \
1151         REQ_(OPTIONS,           "Open option menu"), \
1152         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1153         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1154         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1155         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1156         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1157         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1158         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1159         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1160         \
1161         REQ_GROUP("Misc") \
1162         REQ_(PROMPT,            "Bring up the prompt"), \
1163         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1164         REQ_(SHOW_VERSION,      "Show version information"), \
1165         REQ_(STOP_LOADING,      "Stop all loading views"), \
1166         REQ_(EDIT,              "Open in editor"), \
1167         REQ_(NONE,              "Do nothing")
1170 /* User action requests. */
1171 enum request {
1172 #define REQ_GROUP(help)
1173 #define REQ_(req, help) REQ_##req
1175         /* Offset all requests to avoid conflicts with ncurses getch values. */
1176         REQ_UNKNOWN = KEY_MAX + 1,
1177         REQ_OFFSET,
1178         REQ_INFO
1180 #undef  REQ_GROUP
1181 #undef  REQ_
1182 };
1184 struct request_info {
1185         enum request request;
1186         const char *name;
1187         int namelen;
1188         const char *help;
1189 };
1191 static const struct request_info req_info[] = {
1192 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1193 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1194         REQ_INFO
1195 #undef  REQ_GROUP
1196 #undef  REQ_
1197 };
1199 static enum request
1200 get_request(const char *name)
1202         int namelen = strlen(name);
1203         int i;
1205         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1206                 if (enum_equals(req_info[i], name, namelen))
1207                         return req_info[i].request;
1209         return REQ_UNKNOWN;
1213 /*
1214  * Options
1215  */
1217 /* Option and state variables. */
1218 static enum date opt_date               = DATE_DEFAULT;
1219 static enum author opt_author           = AUTHOR_DEFAULT;
1220 static bool opt_line_number             = FALSE;
1221 static bool opt_line_graphics           = TRUE;
1222 static bool opt_rev_graph               = FALSE;
1223 static bool opt_show_refs               = TRUE;
1224 static int opt_num_interval             = 5;
1225 static double opt_hscroll               = 0.50;
1226 static double opt_scale_split_view      = 2.0 / 3.0;
1227 static int opt_tab_size                 = 8;
1228 static int opt_author_cols              = AUTHOR_COLS;
1229 static char opt_path[SIZEOF_STR]        = "";
1230 static char opt_file[SIZEOF_STR]        = "";
1231 static char opt_ref[SIZEOF_REF]         = "";
1232 static char opt_head[SIZEOF_REF]        = "";
1233 static char opt_remote[SIZEOF_REF]      = "";
1234 static char opt_encoding[20]            = "UTF-8";
1235 static iconv_t opt_iconv_in             = ICONV_NONE;
1236 static iconv_t opt_iconv_out            = ICONV_NONE;
1237 static char opt_search[SIZEOF_STR]      = "";
1238 static char opt_cdup[SIZEOF_STR]        = "";
1239 static char opt_prefix[SIZEOF_STR]      = "";
1240 static char opt_git_dir[SIZEOF_STR]     = "";
1241 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1242 static char opt_editor[SIZEOF_STR]      = "";
1243 static FILE *opt_tty                    = NULL;
1245 #define is_initial_commit()     (!get_ref_head())
1246 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1249 /*
1250  * Line-oriented content detection.
1251  */
1253 #define LINE_INFO \
1254 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1255 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1256 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1257 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1258 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1259 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1261 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1262 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1263 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1264 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1265 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1266 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1267 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1268 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1269 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1270 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1271 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1272 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1273 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1275 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1276 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1277 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1278 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1279 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1280 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1281 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1282 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1283 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1284 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1285 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1286 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1287 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1288 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1289 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1290 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1291 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1292 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1293 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1294 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1295 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1296 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1297 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1298 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1299 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1300 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1301 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1302 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1303 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1304 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1305 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1306 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1307 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1308 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1309 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1310 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1312 enum line_type {
1313 #define LINE(type, line, fg, bg, attr) \
1314         LINE_##type
1315         LINE_INFO,
1316         LINE_NONE
1317 #undef  LINE
1318 };
1320 struct line_info {
1321         const char *name;       /* Option name. */
1322         int namelen;            /* Size of option name. */
1323         const char *line;       /* The start of line to match. */
1324         int linelen;            /* Size of string to match. */
1325         int fg, bg, attr;       /* Color and text attributes for the lines. */
1326 };
1328 static struct line_info line_info[] = {
1329 #define LINE(type, line, fg, bg, attr) \
1330         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1331         LINE_INFO
1332 #undef  LINE
1333 };
1335 static enum line_type
1336 get_line_type(const char *line)
1338         int linelen = strlen(line);
1339         enum line_type type;
1341         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1342                 /* Case insensitive search matches Signed-off-by lines better. */
1343                 if (linelen >= line_info[type].linelen &&
1344                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1345                         return type;
1347         return LINE_DEFAULT;
1350 static inline int
1351 get_line_attr(enum line_type type)
1353         assert(type < ARRAY_SIZE(line_info));
1354         return COLOR_PAIR(type) | line_info[type].attr;
1357 static struct line_info *
1358 get_line_info(const char *name)
1360         size_t namelen = strlen(name);
1361         enum line_type type;
1363         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1364                 if (enum_equals(line_info[type], name, namelen))
1365                         return &line_info[type];
1367         return NULL;
1370 static void
1371 init_colors(void)
1373         int default_bg = line_info[LINE_DEFAULT].bg;
1374         int default_fg = line_info[LINE_DEFAULT].fg;
1375         enum line_type type;
1377         start_color();
1379         if (assume_default_colors(default_fg, default_bg) == ERR) {
1380                 default_bg = COLOR_BLACK;
1381                 default_fg = COLOR_WHITE;
1382         }
1384         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1385                 struct line_info *info = &line_info[type];
1386                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1387                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1389                 init_pair(type, fg, bg);
1390         }
1393 struct line {
1394         enum line_type type;
1396         /* State flags */
1397         unsigned int selected:1;
1398         unsigned int dirty:1;
1399         unsigned int cleareol:1;
1400         unsigned int other:16;
1402         void *data;             /* User data */
1403 };
1406 /*
1407  * Keys
1408  */
1410 struct keybinding {
1411         int alias;
1412         enum request request;
1413 };
1415 static struct keybinding default_keybindings[] = {
1416         /* View switching */
1417         { 'm',          REQ_VIEW_MAIN },
1418         { 'd',          REQ_VIEW_DIFF },
1419         { 'l',          REQ_VIEW_LOG },
1420         { 't',          REQ_VIEW_TREE },
1421         { 'f',          REQ_VIEW_BLOB },
1422         { 'B',          REQ_VIEW_BLAME },
1423         { 'H',          REQ_VIEW_BRANCH },
1424         { 'p',          REQ_VIEW_PAGER },
1425         { 'h',          REQ_VIEW_HELP },
1426         { 'S',          REQ_VIEW_STATUS },
1427         { 'c',          REQ_VIEW_STAGE },
1429         /* View manipulation */
1430         { 'q',          REQ_VIEW_CLOSE },
1431         { KEY_TAB,      REQ_VIEW_NEXT },
1432         { KEY_RETURN,   REQ_ENTER },
1433         { KEY_UP,       REQ_PREVIOUS },
1434         { KEY_DOWN,     REQ_NEXT },
1435         { 'R',          REQ_REFRESH },
1436         { KEY_F(5),     REQ_REFRESH },
1437         { 'O',          REQ_MAXIMIZE },
1439         /* Cursor navigation */
1440         { 'k',          REQ_MOVE_UP },
1441         { 'j',          REQ_MOVE_DOWN },
1442         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1443         { KEY_END,      REQ_MOVE_LAST_LINE },
1444         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1445         { ' ',          REQ_MOVE_PAGE_DOWN },
1446         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1447         { 'b',          REQ_MOVE_PAGE_UP },
1448         { '-',          REQ_MOVE_PAGE_UP },
1450         /* Scrolling */
1451         { KEY_LEFT,     REQ_SCROLL_LEFT },
1452         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1453         { KEY_IC,       REQ_SCROLL_LINE_UP },
1454         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1455         { 'w',          REQ_SCROLL_PAGE_UP },
1456         { 's',          REQ_SCROLL_PAGE_DOWN },
1458         /* Searching */
1459         { '/',          REQ_SEARCH },
1460         { '?',          REQ_SEARCH_BACK },
1461         { 'n',          REQ_FIND_NEXT },
1462         { 'N',          REQ_FIND_PREV },
1464         /* Misc */
1465         { 'Q',          REQ_QUIT },
1466         { 'z',          REQ_STOP_LOADING },
1467         { 'v',          REQ_SHOW_VERSION },
1468         { 'r',          REQ_SCREEN_REDRAW },
1469         { 'o',          REQ_OPTIONS },
1470         { '.',          REQ_TOGGLE_LINENO },
1471         { 'D',          REQ_TOGGLE_DATE },
1472         { 'A',          REQ_TOGGLE_AUTHOR },
1473         { 'g',          REQ_TOGGLE_REV_GRAPH },
1474         { 'F',          REQ_TOGGLE_REFS },
1475         { 'I',          REQ_TOGGLE_SORT_ORDER },
1476         { 'i',          REQ_TOGGLE_SORT_FIELD },
1477         { ':',          REQ_PROMPT },
1478         { 'u',          REQ_STATUS_UPDATE },
1479         { '!',          REQ_STATUS_REVERT },
1480         { 'M',          REQ_STATUS_MERGE },
1481         { '@',          REQ_STAGE_NEXT },
1482         { ',',          REQ_PARENT },
1483         { 'e',          REQ_EDIT },
1484 };
1486 #define KEYMAP_INFO \
1487         KEYMAP_(GENERIC), \
1488         KEYMAP_(MAIN), \
1489         KEYMAP_(DIFF), \
1490         KEYMAP_(LOG), \
1491         KEYMAP_(TREE), \
1492         KEYMAP_(BLOB), \
1493         KEYMAP_(BLAME), \
1494         KEYMAP_(BRANCH), \
1495         KEYMAP_(PAGER), \
1496         KEYMAP_(HELP), \
1497         KEYMAP_(STATUS), \
1498         KEYMAP_(STAGE)
1500 enum keymap {
1501 #define KEYMAP_(name) KEYMAP_##name
1502         KEYMAP_INFO
1503 #undef  KEYMAP_
1504 };
1506 static const struct enum_map keymap_table[] = {
1507 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1508         KEYMAP_INFO
1509 #undef  KEYMAP_
1510 };
1512 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1514 struct keybinding_table {
1515         struct keybinding *data;
1516         size_t size;
1517 };
1519 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1521 static void
1522 add_keybinding(enum keymap keymap, enum request request, int key)
1524         struct keybinding_table *table = &keybindings[keymap];
1526         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1527         if (!table->data)
1528                 die("Failed to allocate keybinding");
1529         table->data[table->size].alias = key;
1530         table->data[table->size++].request = request;
1532         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1533                 int i;
1535                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1536                         if (default_keybindings[i].alias == key)
1537                                 default_keybindings[i].request = REQ_NONE;
1538         }
1541 /* Looks for a key binding first in the given map, then in the generic map, and
1542  * lastly in the default keybindings. */
1543 static enum request
1544 get_keybinding(enum keymap keymap, int key)
1546         size_t i;
1548         for (i = 0; i < keybindings[keymap].size; i++)
1549                 if (keybindings[keymap].data[i].alias == key)
1550                         return keybindings[keymap].data[i].request;
1552         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1553                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1554                         return keybindings[KEYMAP_GENERIC].data[i].request;
1556         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1557                 if (default_keybindings[i].alias == key)
1558                         return default_keybindings[i].request;
1560         return (enum request) key;
1564 struct key {
1565         const char *name;
1566         int value;
1567 };
1569 static const struct key key_table[] = {
1570         { "Enter",      KEY_RETURN },
1571         { "Space",      ' ' },
1572         { "Backspace",  KEY_BACKSPACE },
1573         { "Tab",        KEY_TAB },
1574         { "Escape",     KEY_ESC },
1575         { "Left",       KEY_LEFT },
1576         { "Right",      KEY_RIGHT },
1577         { "Up",         KEY_UP },
1578         { "Down",       KEY_DOWN },
1579         { "Insert",     KEY_IC },
1580         { "Delete",     KEY_DC },
1581         { "Hash",       '#' },
1582         { "Home",       KEY_HOME },
1583         { "End",        KEY_END },
1584         { "PageUp",     KEY_PPAGE },
1585         { "PageDown",   KEY_NPAGE },
1586         { "F1",         KEY_F(1) },
1587         { "F2",         KEY_F(2) },
1588         { "F3",         KEY_F(3) },
1589         { "F4",         KEY_F(4) },
1590         { "F5",         KEY_F(5) },
1591         { "F6",         KEY_F(6) },
1592         { "F7",         KEY_F(7) },
1593         { "F8",         KEY_F(8) },
1594         { "F9",         KEY_F(9) },
1595         { "F10",        KEY_F(10) },
1596         { "F11",        KEY_F(11) },
1597         { "F12",        KEY_F(12) },
1598 };
1600 static int
1601 get_key_value(const char *name)
1603         int i;
1605         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1606                 if (!strcasecmp(key_table[i].name, name))
1607                         return key_table[i].value;
1609         if (strlen(name) == 1 && isprint(*name))
1610                 return (int) *name;
1612         return ERR;
1615 static const char *
1616 get_key_name(int key_value)
1618         static char key_char[] = "'X'";
1619         const char *seq = NULL;
1620         int key;
1622         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1623                 if (key_table[key].value == key_value)
1624                         seq = key_table[key].name;
1626         if (seq == NULL &&
1627             key_value < 127 &&
1628             isprint(key_value)) {
1629                 key_char[1] = (char) key_value;
1630                 seq = key_char;
1631         }
1633         return seq ? seq : "(no key)";
1636 static bool
1637 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1639         const char *sep = *pos > 0 ? ", " : "";
1640         const char *keyname = get_key_name(keybinding->alias);
1642         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1645 static bool
1646 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1647                            enum keymap keymap, bool all)
1649         int i;
1651         for (i = 0; i < keybindings[keymap].size; i++) {
1652                 if (keybindings[keymap].data[i].request == request) {
1653                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1654                                 return FALSE;
1655                         if (!all)
1656                                 break;
1657                 }
1658         }
1660         return TRUE;
1663 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1665 static const char *
1666 get_keys(enum keymap keymap, enum request request, bool all)
1668         static char buf[BUFSIZ];
1669         size_t pos = 0;
1670         int i;
1672         buf[pos] = 0;
1674         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1675                 return "Too many keybindings!";
1676         if (pos > 0 && !all)
1677                 return buf;
1679         if (keymap != KEYMAP_GENERIC) {
1680                 /* Only the generic keymap includes the default keybindings when
1681                  * listing all keys. */
1682                 if (all)
1683                         return buf;
1685                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1686                         return "Too many keybindings!";
1687                 if (pos)
1688                         return buf;
1689         }
1691         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1692                 if (default_keybindings[i].request == request) {
1693                         if (!append_key(buf, &pos, &default_keybindings[i]))
1694                                 return "Too many keybindings!";
1695                         if (!all)
1696                                 return buf;
1697                 }
1698         }
1700         return buf;
1703 struct run_request {
1704         enum keymap keymap;
1705         int key;
1706         const char *argv[SIZEOF_ARG];
1707 };
1709 static struct run_request *run_request;
1710 static size_t run_requests;
1712 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1714 static enum request
1715 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1717         struct run_request *req;
1719         if (argc >= ARRAY_SIZE(req->argv) - 1)
1720                 return REQ_NONE;
1722         if (!realloc_run_requests(&run_request, run_requests, 1))
1723                 return REQ_NONE;
1725         req = &run_request[run_requests];
1726         req->keymap = keymap;
1727         req->key = key;
1728         req->argv[0] = NULL;
1730         if (!format_argv(req->argv, argv, FORMAT_NONE))
1731                 return REQ_NONE;
1733         return REQ_NONE + ++run_requests;
1736 static struct run_request *
1737 get_run_request(enum request request)
1739         if (request <= REQ_NONE)
1740                 return NULL;
1741         return &run_request[request - REQ_NONE - 1];
1744 static void
1745 add_builtin_run_requests(void)
1747         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1748         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1749         const char *commit[] = { "git", "commit", NULL };
1750         const char *gc[] = { "git", "gc", NULL };
1751         struct {
1752                 enum keymap keymap;
1753                 int key;
1754                 int argc;
1755                 const char **argv;
1756         } reqs[] = {
1757                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1758                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1759                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1760                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1761         };
1762         int i;
1764         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1765                 enum request req;
1767                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1768                 if (req != REQ_NONE)
1769                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1770         }
1773 /*
1774  * User config file handling.
1775  */
1777 static int   config_lineno;
1778 static bool  config_errors;
1779 static const char *config_msg;
1781 static const struct enum_map color_map[] = {
1782 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1783         COLOR_MAP(DEFAULT),
1784         COLOR_MAP(BLACK),
1785         COLOR_MAP(BLUE),
1786         COLOR_MAP(CYAN),
1787         COLOR_MAP(GREEN),
1788         COLOR_MAP(MAGENTA),
1789         COLOR_MAP(RED),
1790         COLOR_MAP(WHITE),
1791         COLOR_MAP(YELLOW),
1792 };
1794 static const struct enum_map attr_map[] = {
1795 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1796         ATTR_MAP(NORMAL),
1797         ATTR_MAP(BLINK),
1798         ATTR_MAP(BOLD),
1799         ATTR_MAP(DIM),
1800         ATTR_MAP(REVERSE),
1801         ATTR_MAP(STANDOUT),
1802         ATTR_MAP(UNDERLINE),
1803 };
1805 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1807 static int parse_step(double *opt, const char *arg)
1809         *opt = atoi(arg);
1810         if (!strchr(arg, '%'))
1811                 return OK;
1813         /* "Shift down" so 100% and 1 does not conflict. */
1814         *opt = (*opt - 1) / 100;
1815         if (*opt >= 1.0) {
1816                 *opt = 0.99;
1817                 config_msg = "Step value larger than 100%";
1818                 return ERR;
1819         }
1820         if (*opt < 0.0) {
1821                 *opt = 1;
1822                 config_msg = "Invalid step value";
1823                 return ERR;
1824         }
1825         return OK;
1828 static int
1829 parse_int(int *opt, const char *arg, int min, int max)
1831         int value = atoi(arg);
1833         if (min <= value && value <= max) {
1834                 *opt = value;
1835                 return OK;
1836         }
1838         config_msg = "Integer value out of bound";
1839         return ERR;
1842 static bool
1843 set_color(int *color, const char *name)
1845         if (map_enum(color, color_map, name))
1846                 return TRUE;
1847         if (!prefixcmp(name, "color"))
1848                 return parse_int(color, name + 5, 0, 255) == OK;
1849         return FALSE;
1852 /* Wants: object fgcolor bgcolor [attribute] */
1853 static int
1854 option_color_command(int argc, const char *argv[])
1856         struct line_info *info;
1858         if (argc < 3) {
1859                 config_msg = "Wrong number of arguments given to color command";
1860                 return ERR;
1861         }
1863         info = get_line_info(argv[0]);
1864         if (!info) {
1865                 static const struct enum_map obsolete[] = {
1866                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1867                         ENUM_MAP("main-date",   LINE_DATE),
1868                         ENUM_MAP("main-author", LINE_AUTHOR),
1869                 };
1870                 int index;
1872                 if (!map_enum(&index, obsolete, argv[0])) {
1873                         config_msg = "Unknown color name";
1874                         return ERR;
1875                 }
1876                 info = &line_info[index];
1877         }
1879         if (!set_color(&info->fg, argv[1]) ||
1880             !set_color(&info->bg, argv[2])) {
1881                 config_msg = "Unknown color";
1882                 return ERR;
1883         }
1885         info->attr = 0;
1886         while (argc-- > 3) {
1887                 int attr;
1889                 if (!set_attribute(&attr, argv[argc])) {
1890                         config_msg = "Unknown attribute";
1891                         return ERR;
1892                 }
1893                 info->attr |= attr;
1894         }
1896         return OK;
1899 static int parse_bool(bool *opt, const char *arg)
1901         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1902                 ? TRUE : FALSE;
1903         return OK;
1906 static int parse_enum_do(unsigned int *opt, const char *arg,
1907                          const struct enum_map *map, size_t map_size)
1909         bool is_true;
1911         assert(map_size > 1);
1913         if (map_enum_do(map, map_size, (int *) opt, arg))
1914                 return OK;
1916         if (parse_bool(&is_true, arg) != OK)
1917                 return ERR;
1919         *opt = is_true ? map[1].value : map[0].value;
1920         return OK;
1923 #define parse_enum(opt, arg, map) \
1924         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1926 static int
1927 parse_string(char *opt, const char *arg, size_t optsize)
1929         int arglen = strlen(arg);
1931         switch (arg[0]) {
1932         case '\"':
1933         case '\'':
1934                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1935                         config_msg = "Unmatched quotation";
1936                         return ERR;
1937                 }
1938                 arg += 1; arglen -= 2;
1939         default:
1940                 string_ncopy_do(opt, optsize, arg, arglen);
1941                 return OK;
1942         }
1945 /* Wants: name = value */
1946 static int
1947 option_set_command(int argc, const char *argv[])
1949         if (argc != 3) {
1950                 config_msg = "Wrong number of arguments given to set command";
1951                 return ERR;
1952         }
1954         if (strcmp(argv[1], "=")) {
1955                 config_msg = "No value assigned";
1956                 return ERR;
1957         }
1959         if (!strcmp(argv[0], "show-author"))
1960                 return parse_enum(&opt_author, argv[2], author_map);
1962         if (!strcmp(argv[0], "show-date"))
1963                 return parse_enum(&opt_date, argv[2], date_map);
1965         if (!strcmp(argv[0], "show-rev-graph"))
1966                 return parse_bool(&opt_rev_graph, argv[2]);
1968         if (!strcmp(argv[0], "show-refs"))
1969                 return parse_bool(&opt_show_refs, argv[2]);
1971         if (!strcmp(argv[0], "show-line-numbers"))
1972                 return parse_bool(&opt_line_number, argv[2]);
1974         if (!strcmp(argv[0], "line-graphics"))
1975                 return parse_bool(&opt_line_graphics, argv[2]);
1977         if (!strcmp(argv[0], "line-number-interval"))
1978                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1980         if (!strcmp(argv[0], "author-width"))
1981                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1983         if (!strcmp(argv[0], "horizontal-scroll"))
1984                 return parse_step(&opt_hscroll, argv[2]);
1986         if (!strcmp(argv[0], "split-view-height"))
1987                 return parse_step(&opt_scale_split_view, argv[2]);
1989         if (!strcmp(argv[0], "tab-size"))
1990                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1992         if (!strcmp(argv[0], "commit-encoding"))
1993                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1995         config_msg = "Unknown variable name";
1996         return ERR;
1999 /* Wants: mode request key */
2000 static int
2001 option_bind_command(int argc, const char *argv[])
2003         enum request request;
2004         int keymap = -1;
2005         int key;
2007         if (argc < 3) {
2008                 config_msg = "Wrong number of arguments given to bind command";
2009                 return ERR;
2010         }
2012         if (!set_keymap(&keymap, argv[0])) {
2013                 config_msg = "Unknown key map";
2014                 return ERR;
2015         }
2017         key = get_key_value(argv[1]);
2018         if (key == ERR) {
2019                 config_msg = "Unknown key";
2020                 return ERR;
2021         }
2023         request = get_request(argv[2]);
2024         if (request == REQ_UNKNOWN) {
2025                 static const struct enum_map obsolete[] = {
2026                         ENUM_MAP("cherry-pick",         REQ_NONE),
2027                         ENUM_MAP("screen-resize",       REQ_NONE),
2028                         ENUM_MAP("tree-parent",         REQ_PARENT),
2029                 };
2030                 int alias;
2032                 if (map_enum(&alias, obsolete, argv[2])) {
2033                         if (alias != REQ_NONE)
2034                                 add_keybinding(keymap, alias, key);
2035                         config_msg = "Obsolete request name";
2036                         return ERR;
2037                 }
2038         }
2039         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2040                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2041         if (request == REQ_UNKNOWN) {
2042                 config_msg = "Unknown request name";
2043                 return ERR;
2044         }
2046         add_keybinding(keymap, request, key);
2048         return OK;
2051 static int
2052 set_option(const char *opt, char *value)
2054         const char *argv[SIZEOF_ARG];
2055         int argc = 0;
2057         if (!argv_from_string(argv, &argc, value)) {
2058                 config_msg = "Too many option arguments";
2059                 return ERR;
2060         }
2062         if (!strcmp(opt, "color"))
2063                 return option_color_command(argc, argv);
2065         if (!strcmp(opt, "set"))
2066                 return option_set_command(argc, argv);
2068         if (!strcmp(opt, "bind"))
2069                 return option_bind_command(argc, argv);
2071         config_msg = "Unknown option command";
2072         return ERR;
2075 static int
2076 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2078         int status = OK;
2080         config_lineno++;
2081         config_msg = "Internal error";
2083         /* Check for comment markers, since read_properties() will
2084          * only ensure opt and value are split at first " \t". */
2085         optlen = strcspn(opt, "#");
2086         if (optlen == 0)
2087                 return OK;
2089         if (opt[optlen] != 0) {
2090                 config_msg = "No option value";
2091                 status = ERR;
2093         }  else {
2094                 /* Look for comment endings in the value. */
2095                 size_t len = strcspn(value, "#");
2097                 if (len < valuelen) {
2098                         valuelen = len;
2099                         value[valuelen] = 0;
2100                 }
2102                 status = set_option(opt, value);
2103         }
2105         if (status == ERR) {
2106                 warn("Error on line %d, near '%.*s': %s",
2107                      config_lineno, (int) optlen, opt, config_msg);
2108                 config_errors = TRUE;
2109         }
2111         /* Always keep going if errors are encountered. */
2112         return OK;
2115 static void
2116 load_option_file(const char *path)
2118         struct io io = {};
2120         /* It's OK that the file doesn't exist. */
2121         if (!io_open(&io, "%s", path))
2122                 return;
2124         config_lineno = 0;
2125         config_errors = FALSE;
2127         if (io_load(&io, " \t", read_option) == ERR ||
2128             config_errors == TRUE)
2129                 warn("Errors while loading %s.", path);
2132 static int
2133 load_options(void)
2135         const char *home = getenv("HOME");
2136         const char *tigrc_user = getenv("TIGRC_USER");
2137         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2138         char buf[SIZEOF_STR];
2140         add_builtin_run_requests();
2142         if (!tigrc_system)
2143                 tigrc_system = SYSCONFDIR "/tigrc";
2144         load_option_file(tigrc_system);
2146         if (!tigrc_user) {
2147                 if (!home || !string_format(buf, "%s/.tigrc", home))
2148                         return ERR;
2149                 tigrc_user = buf;
2150         }
2151         load_option_file(tigrc_user);
2153         return OK;
2157 /*
2158  * The viewer
2159  */
2161 struct view;
2162 struct view_ops;
2164 /* The display array of active views and the index of the current view. */
2165 static struct view *display[2];
2166 static unsigned int current_view;
2168 #define foreach_displayed_view(view, i) \
2169         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2171 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2173 /* Current head and commit ID */
2174 static char ref_blob[SIZEOF_REF]        = "";
2175 static char ref_commit[SIZEOF_REF]      = "HEAD";
2176 static char ref_head[SIZEOF_REF]        = "HEAD";
2177 static char ref_branch[SIZEOF_REF]      = "";
2179 enum view_type {
2180         VIEW_MAIN,
2181         VIEW_DIFF,
2182         VIEW_LOG,
2183         VIEW_TREE,
2184         VIEW_BLOB,
2185         VIEW_BLAME,
2186         VIEW_BRANCH,
2187         VIEW_HELP,
2188         VIEW_PAGER,
2189         VIEW_STATUS,
2190         VIEW_STAGE,
2191 };
2193 struct view {
2194         enum view_type type;    /* View type */
2195         const char *name;       /* View name */
2196         const char *cmd_env;    /* Command line set via environment */
2197         const char *id;         /* Points to either of ref_{head,commit,blob} */
2199         struct view_ops *ops;   /* View operations */
2201         enum keymap keymap;     /* What keymap does this view have */
2202         bool git_dir;           /* Whether the view requires a git directory. */
2203         bool refresh;           /* Whether the view supports refreshing. */
2205         char ref[SIZEOF_REF];   /* Hovered commit reference */
2206         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2208         int height, width;      /* The width and height of the main window */
2209         WINDOW *win;            /* The main window */
2210         WINDOW *title;          /* The title window living below the main window */
2212         /* Navigation */
2213         unsigned long offset;   /* Offset of the window top */
2214         unsigned long yoffset;  /* Offset from the window side. */
2215         unsigned long lineno;   /* Current line number */
2216         unsigned long p_offset; /* Previous offset of the window top */
2217         unsigned long p_yoffset;/* Previous offset from the window side */
2218         unsigned long p_lineno; /* Previous current line number */
2219         bool p_restore;         /* Should the previous position be restored. */
2221         /* Searching */
2222         char grep[SIZEOF_STR];  /* Search string */
2223         regex_t *regex;         /* Pre-compiled regexp */
2225         /* If non-NULL, points to the view that opened this view. If this view
2226          * is closed tig will switch back to the parent view. */
2227         struct view *parent;
2229         /* Buffering */
2230         size_t lines;           /* Total number of lines */
2231         struct line *line;      /* Line index */
2232         unsigned int digits;    /* Number of digits in the lines member. */
2234         /* Drawing */
2235         struct line *curline;   /* Line currently being drawn. */
2236         enum line_type curtype; /* Attribute currently used for drawing. */
2237         unsigned long col;      /* Column when drawing. */
2238         bool has_scrolled;      /* View was scrolled. */
2240         /* Loading */
2241         struct io io;
2242         struct io *pipe;
2243         time_t start_time;
2244         time_t update_secs;
2245 };
2247 struct view_ops {
2248         /* What type of content being displayed. Used in the title bar. */
2249         const char *type;
2250         /* Default command arguments. */
2251         const char **argv;
2252         /* Open and reads in all view content. */
2253         bool (*open)(struct view *view);
2254         /* Read one line; updates view->line. */
2255         bool (*read)(struct view *view, char *data);
2256         /* Draw one line; @lineno must be < view->height. */
2257         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2258         /* Depending on view handle a special requests. */
2259         enum request (*request)(struct view *view, enum request request, struct line *line);
2260         /* Search for regexp in a line. */
2261         bool (*grep)(struct view *view, struct line *line);
2262         /* Select line */
2263         void (*select)(struct view *view, struct line *line);
2264         /* Prepare view for loading */
2265         bool (*prepare)(struct view *view);
2266 };
2268 static struct view_ops blame_ops;
2269 static struct view_ops blob_ops;
2270 static struct view_ops diff_ops;
2271 static struct view_ops help_ops;
2272 static struct view_ops log_ops;
2273 static struct view_ops main_ops;
2274 static struct view_ops pager_ops;
2275 static struct view_ops stage_ops;
2276 static struct view_ops status_ops;
2277 static struct view_ops tree_ops;
2278 static struct view_ops branch_ops;
2280 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2281         { type, name, #env, ref, ops, map, git, refresh }
2283 #define VIEW_(id, name, ops, git, refresh, ref) \
2284         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2286 static struct view views[] = {
2287         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  TRUE,  ref_head),
2288         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  FALSE, ref_commit),
2289         VIEW_(LOG,    "log",    &log_ops,    TRUE,  TRUE,  ref_head),
2290         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  FALSE, ref_commit),
2291         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  FALSE, ref_blob),
2292         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  FALSE, ref_commit),
2293         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  TRUE,  ref_head),
2294         VIEW_(HELP,   "help",   &help_ops,   FALSE, FALSE, ""),
2295         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, FALSE, "stdin"),
2296         VIEW_(STATUS, "status", &status_ops, TRUE,  TRUE,  ""),
2297         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  TRUE,  ""),
2298 };
2300 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2302 #define foreach_view(view, i) \
2303         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2305 #define view_is_displayed(view) \
2306         (view == display[0] || view == display[1])
2308 #define view_has_parent(view, child_type, parent_type) \
2309         (view->type == child_type && view->parent && view->parent->type == parent_type)
2311 static inline void
2312 set_view_attr(struct view *view, enum line_type type)
2314         if (!view->curline->selected && view->curtype != type) {
2315                 (void) wattrset(view->win, get_line_attr(type));
2316                 wchgat(view->win, -1, 0, type, NULL);
2317                 view->curtype = type;
2318         }
2321 static int
2322 draw_chars(struct view *view, enum line_type type, const char *string,
2323            int max_len, bool use_tilde)
2325         static char out_buffer[BUFSIZ * 2];
2326         int len = 0;
2327         int col = 0;
2328         int trimmed = FALSE;
2329         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2331         if (max_len <= 0)
2332                 return 0;
2334         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2336         set_view_attr(view, type);
2337         if (len > 0) {
2338                 if (opt_iconv_out != ICONV_NONE) {
2339                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2340                         size_t inlen = len + 1;
2342                         char *outbuf = out_buffer;
2343                         size_t outlen = sizeof(out_buffer);
2345                         size_t ret;
2347                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2348                         if (ret != (size_t) -1) {
2349                                 string = out_buffer;
2350                                 len = sizeof(out_buffer) - outlen;
2351                         }
2352                 }
2354                 waddnstr(view->win, string, len);
2355         }
2356         if (trimmed && use_tilde) {
2357                 set_view_attr(view, LINE_DELIMITER);
2358                 waddch(view->win, '~');
2359                 col++;
2360         }
2362         return col;
2365 static int
2366 draw_space(struct view *view, enum line_type type, int max, int spaces)
2368         static char space[] = "                    ";
2369         int col = 0;
2371         spaces = MIN(max, spaces);
2373         while (spaces > 0) {
2374                 int len = MIN(spaces, sizeof(space) - 1);
2376                 col += draw_chars(view, type, space, len, FALSE);
2377                 spaces -= len;
2378         }
2380         return col;
2383 static bool
2384 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2386         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2387         return view->width + view->yoffset <= view->col;
2390 static bool
2391 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2393         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2394         int max = view->width + view->yoffset - view->col;
2395         int i;
2397         if (max < size)
2398                 size = max;
2400         set_view_attr(view, type);
2401         /* Using waddch() instead of waddnstr() ensures that
2402          * they'll be rendered correctly for the cursor line. */
2403         for (i = skip; i < size; i++)
2404                 waddch(view->win, graphic[i]);
2406         view->col += size;
2407         if (size < max && skip <= size)
2408                 waddch(view->win, ' ');
2409         view->col++;
2411         return view->width + view->yoffset <= view->col;
2414 static bool
2415 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2417         int max = MIN(view->width + view->yoffset - view->col, len);
2418         int col;
2420         if (text)
2421                 col = draw_chars(view, type, text, max - 1, trim);
2422         else
2423                 col = draw_space(view, type, max - 1, max - 1);
2425         view->col += col;
2426         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2427         return view->width + view->yoffset <= view->col;
2430 static bool
2431 draw_date(struct view *view, struct time *time)
2433         const char *date = mkdate(time, opt_date);
2434         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2436         return draw_field(view, LINE_DATE, date, cols, FALSE);
2439 static bool
2440 draw_author(struct view *view, const char *author)
2442         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2443         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2445         if (abbreviate && author)
2446                 author = get_author_initials(author);
2448         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2451 static bool
2452 draw_mode(struct view *view, mode_t mode)
2454         const char *str;
2456         if (S_ISDIR(mode))
2457                 str = "drwxr-xr-x";
2458         else if (S_ISLNK(mode))
2459                 str = "lrwxrwxrwx";
2460         else if (S_ISGITLINK(mode))
2461                 str = "m---------";
2462         else if (S_ISREG(mode) && mode & S_IXUSR)
2463                 str = "-rwxr-xr-x";
2464         else if (S_ISREG(mode))
2465                 str = "-rw-r--r--";
2466         else
2467                 str = "----------";
2469         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2472 static bool
2473 draw_lineno(struct view *view, unsigned int lineno)
2475         char number[10];
2476         int digits3 = view->digits < 3 ? 3 : view->digits;
2477         int max = MIN(view->width + view->yoffset - view->col, digits3);
2478         char *text = NULL;
2479         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2481         lineno += view->offset + 1;
2482         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2483                 static char fmt[] = "%1ld";
2485                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2486                 if (string_format(number, fmt, lineno))
2487                         text = number;
2488         }
2489         if (text)
2490                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2491         else
2492                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2493         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2496 static bool
2497 draw_view_line(struct view *view, unsigned int lineno)
2499         struct line *line;
2500         bool selected = (view->offset + lineno == view->lineno);
2502         assert(view_is_displayed(view));
2504         if (view->offset + lineno >= view->lines)
2505                 return FALSE;
2507         line = &view->line[view->offset + lineno];
2509         wmove(view->win, lineno, 0);
2510         if (line->cleareol)
2511                 wclrtoeol(view->win);
2512         view->col = 0;
2513         view->curline = line;
2514         view->curtype = LINE_NONE;
2515         line->selected = FALSE;
2516         line->dirty = line->cleareol = 0;
2518         if (selected) {
2519                 set_view_attr(view, LINE_CURSOR);
2520                 line->selected = TRUE;
2521                 view->ops->select(view, line);
2522         }
2524         return view->ops->draw(view, line, lineno);
2527 static void
2528 redraw_view_dirty(struct view *view)
2530         bool dirty = FALSE;
2531         int lineno;
2533         for (lineno = 0; lineno < view->height; lineno++) {
2534                 if (view->offset + lineno >= view->lines)
2535                         break;
2536                 if (!view->line[view->offset + lineno].dirty)
2537                         continue;
2538                 dirty = TRUE;
2539                 if (!draw_view_line(view, lineno))
2540                         break;
2541         }
2543         if (!dirty)
2544                 return;
2545         wnoutrefresh(view->win);
2548 static void
2549 redraw_view_from(struct view *view, int lineno)
2551         assert(0 <= lineno && lineno < view->height);
2553         for (; lineno < view->height; lineno++) {
2554                 if (!draw_view_line(view, lineno))
2555                         break;
2556         }
2558         wnoutrefresh(view->win);
2561 static void
2562 redraw_view(struct view *view)
2564         werase(view->win);
2565         redraw_view_from(view, 0);
2569 static void
2570 update_view_title(struct view *view)
2572         char buf[SIZEOF_STR];
2573         char state[SIZEOF_STR];
2574         size_t bufpos = 0, statelen = 0;
2576         assert(view_is_displayed(view));
2578         if (view->type != VIEW_STATUS && view->lines) {
2579                 unsigned int view_lines = view->offset + view->height;
2580                 unsigned int lines = view->lines
2581                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2582                                    : 0;
2584                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2585                                    view->ops->type,
2586                                    view->lineno + 1,
2587                                    view->lines,
2588                                    lines);
2590         }
2592         if (view->pipe) {
2593                 time_t secs = time(NULL) - view->start_time;
2595                 /* Three git seconds are a long time ... */
2596                 if (secs > 2)
2597                         string_format_from(state, &statelen, " loading %lds", secs);
2598         }
2600         string_format_from(buf, &bufpos, "[%s]", view->name);
2601         if (*view->ref && bufpos < view->width) {
2602                 size_t refsize = strlen(view->ref);
2603                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2605                 if (minsize < view->width)
2606                         refsize = view->width - minsize + 7;
2607                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2608         }
2610         if (statelen && bufpos < view->width) {
2611                 string_format_from(buf, &bufpos, "%s", state);
2612         }
2614         if (view == display[current_view])
2615                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2616         else
2617                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2619         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2620         wclrtoeol(view->title);
2621         wnoutrefresh(view->title);
2624 static int
2625 apply_step(double step, int value)
2627         if (step >= 1)
2628                 return (int) step;
2629         value *= step + 0.01;
2630         return value ? value : 1;
2633 static void
2634 resize_display(void)
2636         int offset, i;
2637         struct view *base = display[0];
2638         struct view *view = display[1] ? display[1] : display[0];
2640         /* Setup window dimensions */
2642         getmaxyx(stdscr, base->height, base->width);
2644         /* Make room for the status window. */
2645         base->height -= 1;
2647         if (view != base) {
2648                 /* Horizontal split. */
2649                 view->width   = base->width;
2650                 view->height  = apply_step(opt_scale_split_view, base->height);
2651                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2652                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2653                 base->height -= view->height;
2655                 /* Make room for the title bar. */
2656                 view->height -= 1;
2657         }
2659         /* Make room for the title bar. */
2660         base->height -= 1;
2662         offset = 0;
2664         foreach_displayed_view (view, i) {
2665                 if (!view->win) {
2666                         view->win = newwin(view->height, 0, offset, 0);
2667                         if (!view->win)
2668                                 die("Failed to create %s view", view->name);
2670                         scrollok(view->win, FALSE);
2672                         view->title = newwin(1, 0, offset + view->height, 0);
2673                         if (!view->title)
2674                                 die("Failed to create title window");
2676                 } else {
2677                         wresize(view->win, view->height, view->width);
2678                         mvwin(view->win,   offset, 0);
2679                         mvwin(view->title, offset + view->height, 0);
2680                 }
2682                 offset += view->height + 1;
2683         }
2686 static void
2687 redraw_display(bool clear)
2689         struct view *view;
2690         int i;
2692         foreach_displayed_view (view, i) {
2693                 if (clear)
2694                         wclear(view->win);
2695                 redraw_view(view);
2696                 update_view_title(view);
2697         }
2700 static void
2701 toggle_enum_option_do(unsigned int *opt, const char *help,
2702                       const struct enum_map *map, size_t size)
2704         *opt = (*opt + 1) % size;
2705         redraw_display(FALSE);
2706         report("Displaying %s %s", enum_name(map[*opt]), help);
2709 #define toggle_enum_option(opt, help, map) \
2710         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2712 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2713 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2715 static void
2716 toggle_view_option(bool *option, const char *help)
2718         *option = !*option;
2719         redraw_display(FALSE);
2720         report("%sabling %s", *option ? "En" : "Dis", help);
2723 static void
2724 open_option_menu(void)
2726         const struct menu_item menu[] = {
2727                 { '.', "line numbers", &opt_line_number },
2728                 { 'D', "date display", &opt_date },
2729                 { 'A', "author display", &opt_author },
2730                 { 'g', "revision graph display", &opt_rev_graph },
2731                 { 'F', "reference display", &opt_show_refs },
2732                 { 0 }
2733         };
2734         int selected = 0;
2736         if (prompt_menu("Toggle option", menu, &selected)) {
2737                 if (menu[selected].data == &opt_date)
2738                         toggle_date();
2739                 else if (menu[selected].data == &opt_author)
2740                         toggle_author();
2741                 else
2742                         toggle_view_option(menu[selected].data, menu[selected].text);
2743         }
2746 static void
2747 maximize_view(struct view *view)
2749         memset(display, 0, sizeof(display));
2750         current_view = 0;
2751         display[current_view] = view;
2752         resize_display();
2753         redraw_display(FALSE);
2754         report("");
2758 /*
2759  * Navigation
2760  */
2762 static bool
2763 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2765         if (lineno >= view->lines)
2766                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2768         if (offset > lineno || offset + view->height <= lineno) {
2769                 unsigned long half = view->height / 2;
2771                 if (lineno > half)
2772                         offset = lineno - half;
2773                 else
2774                         offset = 0;
2775         }
2777         if (offset != view->offset || lineno != view->lineno) {
2778                 view->offset = offset;
2779                 view->lineno = lineno;
2780                 return TRUE;
2781         }
2783         return FALSE;
2786 /* Scrolling backend */
2787 static void
2788 do_scroll_view(struct view *view, int lines)
2790         bool redraw_current_line = FALSE;
2792         /* The rendering expects the new offset. */
2793         view->offset += lines;
2795         assert(0 <= view->offset && view->offset < view->lines);
2796         assert(lines);
2798         /* Move current line into the view. */
2799         if (view->lineno < view->offset) {
2800                 view->lineno = view->offset;
2801                 redraw_current_line = TRUE;
2802         } else if (view->lineno >= view->offset + view->height) {
2803                 view->lineno = view->offset + view->height - 1;
2804                 redraw_current_line = TRUE;
2805         }
2807         assert(view->offset <= view->lineno && view->lineno < view->lines);
2809         /* Redraw the whole screen if scrolling is pointless. */
2810         if (view->height < ABS(lines)) {
2811                 redraw_view(view);
2813         } else {
2814                 int line = lines > 0 ? view->height - lines : 0;
2815                 int end = line + ABS(lines);
2817                 scrollok(view->win, TRUE);
2818                 wscrl(view->win, lines);
2819                 scrollok(view->win, FALSE);
2821                 while (line < end && draw_view_line(view, line))
2822                         line++;
2824                 if (redraw_current_line)
2825                         draw_view_line(view, view->lineno - view->offset);
2826                 wnoutrefresh(view->win);
2827         }
2829         view->has_scrolled = TRUE;
2830         report("");
2833 /* Scroll frontend */
2834 static void
2835 scroll_view(struct view *view, enum request request)
2837         int lines = 1;
2839         assert(view_is_displayed(view));
2841         switch (request) {
2842         case REQ_SCROLL_LEFT:
2843                 if (view->yoffset == 0) {
2844                         report("Cannot scroll beyond the first column");
2845                         return;
2846                 }
2847                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2848                         view->yoffset = 0;
2849                 else
2850                         view->yoffset -= apply_step(opt_hscroll, view->width);
2851                 redraw_view_from(view, 0);
2852                 report("");
2853                 return;
2854         case REQ_SCROLL_RIGHT:
2855                 view->yoffset += apply_step(opt_hscroll, view->width);
2856                 redraw_view(view);
2857                 report("");
2858                 return;
2859         case REQ_SCROLL_PAGE_DOWN:
2860                 lines = view->height;
2861         case REQ_SCROLL_LINE_DOWN:
2862                 if (view->offset + lines > view->lines)
2863                         lines = view->lines - view->offset;
2865                 if (lines == 0 || view->offset + view->height >= view->lines) {
2866                         report("Cannot scroll beyond the last line");
2867                         return;
2868                 }
2869                 break;
2871         case REQ_SCROLL_PAGE_UP:
2872                 lines = view->height;
2873         case REQ_SCROLL_LINE_UP:
2874                 if (lines > view->offset)
2875                         lines = view->offset;
2877                 if (lines == 0) {
2878                         report("Cannot scroll beyond the first line");
2879                         return;
2880                 }
2882                 lines = -lines;
2883                 break;
2885         default:
2886                 die("request %d not handled in switch", request);
2887         }
2889         do_scroll_view(view, lines);
2892 /* Cursor moving */
2893 static void
2894 move_view(struct view *view, enum request request)
2896         int scroll_steps = 0;
2897         int steps;
2899         switch (request) {
2900         case REQ_MOVE_FIRST_LINE:
2901                 steps = -view->lineno;
2902                 break;
2904         case REQ_MOVE_LAST_LINE:
2905                 steps = view->lines - view->lineno - 1;
2906                 break;
2908         case REQ_MOVE_PAGE_UP:
2909                 steps = view->height > view->lineno
2910                       ? -view->lineno : -view->height;
2911                 break;
2913         case REQ_MOVE_PAGE_DOWN:
2914                 steps = view->lineno + view->height >= view->lines
2915                       ? view->lines - view->lineno - 1 : view->height;
2916                 break;
2918         case REQ_MOVE_UP:
2919                 steps = -1;
2920                 break;
2922         case REQ_MOVE_DOWN:
2923                 steps = 1;
2924                 break;
2926         default:
2927                 die("request %d not handled in switch", request);
2928         }
2930         if (steps <= 0 && view->lineno == 0) {
2931                 report("Cannot move beyond the first line");
2932                 return;
2934         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2935                 report("Cannot move beyond the last line");
2936                 return;
2937         }
2939         /* Move the current line */
2940         view->lineno += steps;
2941         assert(0 <= view->lineno && view->lineno < view->lines);
2943         /* Check whether the view needs to be scrolled */
2944         if (view->lineno < view->offset ||
2945             view->lineno >= view->offset + view->height) {
2946                 scroll_steps = steps;
2947                 if (steps < 0 && -steps > view->offset) {
2948                         scroll_steps = -view->offset;
2950                 } else if (steps > 0) {
2951                         if (view->lineno == view->lines - 1 &&
2952                             view->lines > view->height) {
2953                                 scroll_steps = view->lines - view->offset - 1;
2954                                 if (scroll_steps >= view->height)
2955                                         scroll_steps -= view->height - 1;
2956                         }
2957                 }
2958         }
2960         if (!view_is_displayed(view)) {
2961                 view->offset += scroll_steps;
2962                 assert(0 <= view->offset && view->offset < view->lines);
2963                 view->ops->select(view, &view->line[view->lineno]);
2964                 return;
2965         }
2967         /* Repaint the old "current" line if we be scrolling */
2968         if (ABS(steps) < view->height)
2969                 draw_view_line(view, view->lineno - steps - view->offset);
2971         if (scroll_steps) {
2972                 do_scroll_view(view, scroll_steps);
2973                 return;
2974         }
2976         /* Draw the current line */
2977         draw_view_line(view, view->lineno - view->offset);
2979         wnoutrefresh(view->win);
2980         report("");
2984 /*
2985  * Searching
2986  */
2988 static void search_view(struct view *view, enum request request);
2990 static bool
2991 grep_text(struct view *view, const char *text[])
2993         regmatch_t pmatch;
2994         size_t i;
2996         for (i = 0; text[i]; i++)
2997                 if (*text[i] &&
2998                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2999                         return TRUE;
3000         return FALSE;
3003 static void
3004 select_view_line(struct view *view, unsigned long lineno)
3006         unsigned long old_lineno = view->lineno;
3007         unsigned long old_offset = view->offset;
3009         if (goto_view_line(view, view->offset, lineno)) {
3010                 if (view_is_displayed(view)) {
3011                         if (old_offset != view->offset) {
3012                                 redraw_view(view);
3013                         } else {
3014                                 draw_view_line(view, old_lineno - view->offset);
3015                                 draw_view_line(view, view->lineno - view->offset);
3016                                 wnoutrefresh(view->win);
3017                         }
3018                 } else {
3019                         view->ops->select(view, &view->line[view->lineno]);
3020                 }
3021         }
3024 static void
3025 find_next(struct view *view, enum request request)
3027         unsigned long lineno = view->lineno;
3028         int direction;
3030         if (!*view->grep) {
3031                 if (!*opt_search)
3032                         report("No previous search");
3033                 else
3034                         search_view(view, request);
3035                 return;
3036         }
3038         switch (request) {
3039         case REQ_SEARCH:
3040         case REQ_FIND_NEXT:
3041                 direction = 1;
3042                 break;
3044         case REQ_SEARCH_BACK:
3045         case REQ_FIND_PREV:
3046                 direction = -1;
3047                 break;
3049         default:
3050                 return;
3051         }
3053         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3054                 lineno += direction;
3056         /* Note, lineno is unsigned long so will wrap around in which case it
3057          * will become bigger than view->lines. */
3058         for (; lineno < view->lines; lineno += direction) {
3059                 if (view->ops->grep(view, &view->line[lineno])) {
3060                         select_view_line(view, lineno);
3061                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3062                         return;
3063                 }
3064         }
3066         report("No match found for '%s'", view->grep);
3069 static void
3070 search_view(struct view *view, enum request request)
3072         int regex_err;
3074         if (view->regex) {
3075                 regfree(view->regex);
3076                 *view->grep = 0;
3077         } else {
3078                 view->regex = calloc(1, sizeof(*view->regex));
3079                 if (!view->regex)
3080                         return;
3081         }
3083         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3084         if (regex_err != 0) {
3085                 char buf[SIZEOF_STR] = "unknown error";
3087                 regerror(regex_err, view->regex, buf, sizeof(buf));
3088                 report("Search failed: %s", buf);
3089                 return;
3090         }
3092         string_copy(view->grep, opt_search);
3094         find_next(view, request);
3097 /*
3098  * Incremental updating
3099  */
3101 static void
3102 reset_view(struct view *view)
3104         int i;
3106         for (i = 0; i < view->lines; i++)
3107                 free(view->line[i].data);
3108         free(view->line);
3110         view->p_offset = view->offset;
3111         view->p_yoffset = view->yoffset;
3112         view->p_lineno = view->lineno;
3114         view->line = NULL;
3115         view->offset = 0;
3116         view->yoffset = 0;
3117         view->lines  = 0;
3118         view->lineno = 0;
3119         view->vid[0] = 0;
3120         view->update_secs = 0;
3123 static void
3124 free_argv(const char *argv[])
3126         int argc;
3128         for (argc = 0; argv[argc]; argc++)
3129                 free((void *) argv[argc]);
3132 static const char *
3133 format_arg(const char *name)
3135         static struct {
3136                 const char *name;
3137                 size_t namelen;
3138                 const char *value;
3139                 const char *value_if_empty;
3140         } vars[] = {
3141 #define FORMAT_VAR(name, value, value_if_empty) \
3142         { name, STRING_SIZE(name), value, value_if_empty }
3143                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3144                 FORMAT_VAR("%(file)",           opt_file,       ""),
3145                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3146                 FORMAT_VAR("%(head)",           ref_head,       ""),
3147                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3148                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3149                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3150         };
3151         int i;
3153         for (i = 0; i < ARRAY_SIZE(vars); i++)
3154                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3155                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3157         report("Unknown replacement: `%s`", name);
3158         return NULL;
3161 static bool
3162 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3164         char buf[SIZEOF_STR];
3165         int argc;
3166         bool noreplace = flags == FORMAT_NONE;
3168         free_argv(dst_argv);
3170         for (argc = 0; src_argv[argc]; argc++) {
3171                 const char *arg = src_argv[argc];
3172                 size_t bufpos = 0;
3174                 while (arg) {
3175                         char *next = strstr(arg, "%(");
3176                         int len = next - arg;
3177                         const char *value;
3179                         if (!next || noreplace) {
3180                                 len = strlen(arg);
3181                                 value = "";
3183                         } else {
3184                                 value = format_arg(next);
3186                                 if (!value) {
3187                                         return FALSE;
3188                                 }
3189                         }
3191                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3192                                 return FALSE;
3194                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3195                 }
3197                 dst_argv[argc] = strdup(buf);
3198                 if (!dst_argv[argc])
3199                         break;
3200         }
3202         dst_argv[argc] = NULL;
3204         return src_argv[argc] == NULL;
3207 static bool
3208 restore_view_position(struct view *view)
3210         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3211                 return FALSE;
3213         /* Changing the view position cancels the restoring. */
3214         /* FIXME: Changing back to the first line is not detected. */
3215         if (view->offset != 0 || view->lineno != 0) {
3216                 view->p_restore = FALSE;
3217                 return FALSE;
3218         }
3220         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3221             view_is_displayed(view))
3222                 werase(view->win);
3224         view->yoffset = view->p_yoffset;
3225         view->p_restore = FALSE;
3227         return TRUE;
3230 static void
3231 end_update(struct view *view, bool force)
3233         if (!view->pipe)
3234                 return;
3235         while (!view->ops->read(view, NULL))
3236                 if (!force)
3237                         return;
3238         if (force)
3239                 io_kill(view->pipe);
3240         io_done(view->pipe);
3241         view->pipe = NULL;
3244 static void
3245 setup_update(struct view *view, const char *vid)
3247         reset_view(view);
3248         string_copy_rev(view->vid, vid);
3249         view->pipe = &view->io;
3250         view->start_time = time(NULL);
3253 static bool
3254 prepare_update(struct view *view, const char *argv[], const char *dir)
3256         if (view->pipe)
3257                 end_update(view, TRUE);
3258         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3261 static bool
3262 prepare_update_file(struct view *view, const char *name)
3264         if (view->pipe)
3265                 end_update(view, TRUE);
3266         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3269 static bool
3270 begin_update(struct view *view, bool refresh)
3272         if (view->pipe)
3273                 end_update(view, TRUE);
3275         if (!refresh) {
3276                 if (view->ops->prepare) {
3277                         if (!view->ops->prepare(view))
3278                                 return FALSE;
3279                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3280                         return FALSE;
3281                 }
3283                 /* Put the current ref_* value to the view title ref
3284                  * member. This is needed by the blob view. Most other
3285                  * views sets it automatically after loading because the
3286                  * first line is a commit line. */
3287                 string_copy_rev(view->ref, view->id);
3288         }
3290         if (!io_start(&view->io))
3291                 return FALSE;
3293         setup_update(view, view->id);
3295         return TRUE;
3298 static bool
3299 update_view(struct view *view)
3301         char out_buffer[BUFSIZ * 2];
3302         char *line;
3303         /* Clear the view and redraw everything since the tree sorting
3304          * might have rearranged things. */
3305         bool redraw = view->lines == 0;
3306         bool can_read = TRUE;
3308         if (!view->pipe)
3309                 return TRUE;
3311         if (!io_can_read(view->pipe)) {
3312                 if (view->lines == 0 && view_is_displayed(view)) {
3313                         time_t secs = time(NULL) - view->start_time;
3315                         if (secs > 1 && secs > view->update_secs) {
3316                                 if (view->update_secs == 0)
3317                                         redraw_view(view);
3318                                 update_view_title(view);
3319                                 view->update_secs = secs;
3320                         }
3321                 }
3322                 return TRUE;
3323         }
3325         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3326                 if (opt_iconv_in != ICONV_NONE) {
3327                         ICONV_CONST char *inbuf = line;
3328                         size_t inlen = strlen(line) + 1;
3330                         char *outbuf = out_buffer;
3331                         size_t outlen = sizeof(out_buffer);
3333                         size_t ret;
3335                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3336                         if (ret != (size_t) -1)
3337                                 line = out_buffer;
3338                 }
3340                 if (!view->ops->read(view, line)) {
3341                         report("Allocation failure");
3342                         end_update(view, TRUE);
3343                         return FALSE;
3344                 }
3345         }
3347         {
3348                 unsigned long lines = view->lines;
3349                 int digits;
3351                 for (digits = 0; lines; digits++)
3352                         lines /= 10;
3354                 /* Keep the displayed view in sync with line number scaling. */
3355                 if (digits != view->digits) {
3356                         view->digits = digits;
3357                         if (opt_line_number || view->type == VIEW_BLAME)
3358                                 redraw = TRUE;
3359                 }
3360         }
3362         if (io_error(view->pipe)) {
3363                 report("Failed to read: %s", io_strerror(view->pipe));
3364                 end_update(view, TRUE);
3366         } else if (io_eof(view->pipe)) {
3367                 report("");
3368                 end_update(view, FALSE);
3369         }
3371         if (restore_view_position(view))
3372                 redraw = TRUE;
3374         if (!view_is_displayed(view))
3375                 return TRUE;
3377         if (redraw)
3378                 redraw_view_from(view, 0);
3379         else
3380                 redraw_view_dirty(view);
3382         /* Update the title _after_ the redraw so that if the redraw picks up a
3383          * commit reference in view->ref it'll be available here. */
3384         update_view_title(view);
3385         return TRUE;
3388 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3390 static struct line *
3391 add_line_data(struct view *view, void *data, enum line_type type)
3393         struct line *line;
3395         if (!realloc_lines(&view->line, view->lines, 1))
3396                 return NULL;
3398         line = &view->line[view->lines++];
3399         memset(line, 0, sizeof(*line));
3400         line->type = type;
3401         line->data = data;
3402         line->dirty = 1;
3404         return line;
3407 static struct line *
3408 add_line_text(struct view *view, const char *text, enum line_type type)
3410         char *data = text ? strdup(text) : NULL;
3412         return data ? add_line_data(view, data, type) : NULL;
3415 static struct line *
3416 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3418         char buf[SIZEOF_STR];
3419         va_list args;
3421         va_start(args, fmt);
3422         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3423                 buf[0] = 0;
3424         va_end(args);
3426         return buf[0] ? add_line_text(view, buf, type) : NULL;
3429 /*
3430  * View opening
3431  */
3433 enum open_flags {
3434         OPEN_DEFAULT = 0,       /* Use default view switching. */
3435         OPEN_SPLIT = 1,         /* Split current view. */
3436         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3437         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3438         OPEN_PREPARED = 32,     /* Open already prepared command. */
3439 };
3441 static void
3442 open_view(struct view *prev, enum request request, enum open_flags flags)
3444         bool split = !!(flags & OPEN_SPLIT);
3445         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3446         bool nomaximize = !!(flags & OPEN_REFRESH);
3447         struct view *view = VIEW(request);
3448         int nviews = displayed_views();
3449         struct view *base_view = display[0];
3451         if (view == prev && nviews == 1 && !reload) {
3452                 report("Already in %s view", view->name);
3453                 return;
3454         }
3456         if (view->git_dir && !opt_git_dir[0]) {
3457                 report("The %s view is disabled in pager view", view->name);
3458                 return;
3459         }
3461         if (split) {
3462                 display[1] = view;
3463                 current_view = 1;
3464         } else if (!nomaximize) {
3465                 /* Maximize the current view. */
3466                 memset(display, 0, sizeof(display));
3467                 current_view = 0;
3468                 display[current_view] = view;
3469         }
3471         /* No parent signals that this is the first loaded view. */
3472         if (prev && view != prev) {
3473                 view->parent = prev;
3474         }
3476         /* Resize the view when switching between split- and full-screen,
3477          * or when switching between two different full-screen views. */
3478         if (nviews != displayed_views() ||
3479             (nviews == 1 && base_view != display[0]))
3480                 resize_display();
3482         if (view->ops->open) {
3483                 if (view->pipe)
3484                         end_update(view, TRUE);
3485                 if (!view->ops->open(view)) {
3486                         report("Failed to load %s view", view->name);
3487                         return;
3488                 }
3489                 restore_view_position(view);
3491         } else if ((reload || strcmp(view->vid, view->id)) &&
3492                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3493                 report("Failed to load %s view", view->name);
3494                 return;
3495         }
3497         if (split && prev->lineno - prev->offset >= prev->height) {
3498                 /* Take the title line into account. */
3499                 int lines = prev->lineno - prev->offset - prev->height + 1;
3501                 /* Scroll the view that was split if the current line is
3502                  * outside the new limited view. */
3503                 do_scroll_view(prev, lines);
3504         }
3506         if (prev && view != prev && split && view_is_displayed(prev)) {
3507                 /* "Blur" the previous view. */
3508                 update_view_title(prev);
3509         }
3511         if (view->pipe && view->lines == 0) {
3512                 /* Clear the old view and let the incremental updating refill
3513                  * the screen. */
3514                 werase(view->win);
3515                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3516                 report("");
3517         } else if (view_is_displayed(view)) {
3518                 redraw_view(view);
3519                 report("");
3520         }
3523 static void
3524 open_external_viewer(const char *argv[], const char *dir)
3526         def_prog_mode();           /* save current tty modes */
3527         endwin();                  /* restore original tty modes */
3528         io_run_fg(argv, dir);
3529         fprintf(stderr, "Press Enter to continue");
3530         getc(opt_tty);
3531         reset_prog_mode();
3532         redraw_display(TRUE);
3535 static void
3536 open_mergetool(const char *file)
3538         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3540         open_external_viewer(mergetool_argv, opt_cdup);
3543 static void
3544 open_editor(const char *file)
3546         const char *editor_argv[] = { "vi", file, NULL };
3547         const char *editor;
3549         editor = getenv("GIT_EDITOR");
3550         if (!editor && *opt_editor)
3551                 editor = opt_editor;
3552         if (!editor)
3553                 editor = getenv("VISUAL");
3554         if (!editor)
3555                 editor = getenv("EDITOR");
3556         if (!editor)
3557                 editor = "vi";
3559         editor_argv[0] = editor;
3560         open_external_viewer(editor_argv, opt_cdup);
3563 static void
3564 open_run_request(enum request request)
3566         struct run_request *req = get_run_request(request);
3567         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3569         if (!req) {
3570                 report("Unknown run request");
3571                 return;
3572         }
3574         if (format_argv(argv, req->argv, FORMAT_ALL))
3575                 open_external_viewer(argv, NULL);
3576         free_argv(argv);
3579 /*
3580  * User request switch noodle
3581  */
3583 static int
3584 view_driver(struct view *view, enum request request)
3586         int i;
3588         if (request == REQ_NONE)
3589                 return TRUE;
3591         if (request > REQ_NONE) {
3592                 open_run_request(request);
3593                 /* FIXME: When all views can refresh always do this. */
3594                 if (view->refresh)
3595                         request = REQ_REFRESH;
3596                 else
3597                         return TRUE;
3598         }
3600         if (view && view->lines) {
3601                 request = view->ops->request(view, request, &view->line[view->lineno]);
3602                 if (request == REQ_NONE)
3603                         return TRUE;
3604         }
3606         switch (request) {
3607         case REQ_MOVE_UP:
3608         case REQ_MOVE_DOWN:
3609         case REQ_MOVE_PAGE_UP:
3610         case REQ_MOVE_PAGE_DOWN:
3611         case REQ_MOVE_FIRST_LINE:
3612         case REQ_MOVE_LAST_LINE:
3613                 move_view(view, request);
3614                 break;
3616         case REQ_SCROLL_LEFT:
3617         case REQ_SCROLL_RIGHT:
3618         case REQ_SCROLL_LINE_DOWN:
3619         case REQ_SCROLL_LINE_UP:
3620         case REQ_SCROLL_PAGE_DOWN:
3621         case REQ_SCROLL_PAGE_UP:
3622                 scroll_view(view, request);
3623                 break;
3625         case REQ_VIEW_BLAME:
3626                 if (!opt_file[0]) {
3627                         report("No file chosen, press %s to open tree view",
3628                                get_key(view->keymap, REQ_VIEW_TREE));
3629                         break;
3630                 }
3631                 open_view(view, request, OPEN_DEFAULT);
3632                 break;
3634         case REQ_VIEW_BLOB:
3635                 if (!ref_blob[0]) {
3636                         report("No file chosen, press %s to open tree view",
3637                                get_key(view->keymap, REQ_VIEW_TREE));
3638                         break;
3639                 }
3640                 open_view(view, request, OPEN_DEFAULT);
3641                 break;
3643         case REQ_VIEW_PAGER:
3644                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3645                         report("No pager content, press %s to run command from prompt",
3646                                get_key(view->keymap, REQ_PROMPT));
3647                         break;
3648                 }
3649                 open_view(view, request, OPEN_DEFAULT);
3650                 break;
3652         case REQ_VIEW_STAGE:
3653                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3654                         report("No stage content, press %s to open the status view and choose file",
3655                                get_key(view->keymap, REQ_VIEW_STATUS));
3656                         break;
3657                 }
3658                 open_view(view, request, OPEN_DEFAULT);
3659                 break;
3661         case REQ_VIEW_STATUS:
3662                 if (opt_is_inside_work_tree == FALSE) {
3663                         report("The status view requires a working tree");
3664                         break;
3665                 }
3666                 open_view(view, request, OPEN_DEFAULT);
3667                 break;
3669         case REQ_VIEW_MAIN:
3670         case REQ_VIEW_DIFF:
3671         case REQ_VIEW_LOG:
3672         case REQ_VIEW_TREE:
3673         case REQ_VIEW_HELP:
3674         case REQ_VIEW_BRANCH:
3675                 open_view(view, request, OPEN_DEFAULT);
3676                 break;
3678         case REQ_NEXT:
3679         case REQ_PREVIOUS:
3680                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3682                 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3683                     view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3684                     view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3685                     view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3686                     view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3687                         int line;
3689                         view = view->parent;
3690                         line = view->lineno;
3691                         move_view(view, request);
3692                         if (view_is_displayed(view))
3693                                 update_view_title(view);
3694                         if (line != view->lineno)
3695                                 view->ops->request(view, REQ_ENTER,
3696                                                    &view->line[view->lineno]);
3698                 } else {
3699                         move_view(view, request);
3700                 }
3701                 break;
3703         case REQ_VIEW_NEXT:
3704         {
3705                 int nviews = displayed_views();
3706                 int next_view = (current_view + 1) % nviews;
3708                 if (next_view == current_view) {
3709                         report("Only one view is displayed");
3710                         break;
3711                 }
3713                 current_view = next_view;
3714                 /* Blur out the title of the previous view. */
3715                 update_view_title(view);
3716                 report("");
3717                 break;
3718         }
3719         case REQ_REFRESH:
3720                 report("Refreshing is not yet supported for the %s view", view->name);
3721                 break;
3723         case REQ_MAXIMIZE:
3724                 if (displayed_views() == 2)
3725                         maximize_view(view);
3726                 break;
3728         case REQ_OPTIONS:
3729                 open_option_menu();
3730                 break;
3732         case REQ_TOGGLE_LINENO:
3733                 toggle_view_option(&opt_line_number, "line numbers");
3734                 break;
3736         case REQ_TOGGLE_DATE:
3737                 toggle_date();
3738                 break;
3740         case REQ_TOGGLE_AUTHOR:
3741                 toggle_author();
3742                 break;
3744         case REQ_TOGGLE_REV_GRAPH:
3745                 toggle_view_option(&opt_rev_graph, "revision graph display");
3746                 break;
3748         case REQ_TOGGLE_REFS:
3749                 toggle_view_option(&opt_show_refs, "reference display");
3750                 break;
3752         case REQ_TOGGLE_SORT_FIELD:
3753         case REQ_TOGGLE_SORT_ORDER:
3754                 report("Sorting is not yet supported for the %s view", view->name);
3755                 break;
3757         case REQ_SEARCH:
3758         case REQ_SEARCH_BACK:
3759                 search_view(view, request);
3760                 break;
3762         case REQ_FIND_NEXT:
3763         case REQ_FIND_PREV:
3764                 find_next(view, request);
3765                 break;
3767         case REQ_STOP_LOADING:
3768                 foreach_view(view, i) {
3769                         if (view->pipe)
3770                                 report("Stopped loading the %s view", view->name),
3771                         end_update(view, TRUE);
3772                 }
3773                 break;
3775         case REQ_SHOW_VERSION:
3776                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3777                 return TRUE;
3779         case REQ_SCREEN_REDRAW:
3780                 redraw_display(TRUE);
3781                 break;
3783         case REQ_EDIT:
3784                 report("Nothing to edit");
3785                 break;
3787         case REQ_ENTER:
3788                 report("Nothing to enter");
3789                 break;
3791         case REQ_VIEW_CLOSE:
3792                 /* XXX: Mark closed views by letting view->parent point to the
3793                  * view itself. Parents to closed view should never be
3794                  * followed. */
3795                 if (view->parent &&
3796                     view->parent->parent != view->parent) {
3797                         maximize_view(view->parent);
3798                         view->parent = view;
3799                         break;
3800                 }
3801                 /* Fall-through */
3802         case REQ_QUIT:
3803                 return FALSE;
3805         default:
3806                 report("Unknown key, press %s for help",
3807                        get_key(view->keymap, REQ_VIEW_HELP));
3808                 return TRUE;
3809         }
3811         return TRUE;
3815 /*
3816  * View backend utilities
3817  */
3819 enum sort_field {
3820         ORDERBY_NAME,
3821         ORDERBY_DATE,
3822         ORDERBY_AUTHOR,
3823 };
3825 struct sort_state {
3826         const enum sort_field *fields;
3827         size_t size, current;
3828         bool reverse;
3829 };
3831 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3832 #define get_sort_field(state) ((state).fields[(state).current])
3833 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3835 static void
3836 sort_view(struct view *view, enum request request, struct sort_state *state,
3837           int (*compare)(const void *, const void *))
3839         switch (request) {
3840         case REQ_TOGGLE_SORT_FIELD:
3841                 state->current = (state->current + 1) % state->size;
3842                 break;
3844         case REQ_TOGGLE_SORT_ORDER:
3845                 state->reverse = !state->reverse;
3846                 break;
3847         default:
3848                 die("Not a sort request");
3849         }
3851         qsort(view->line, view->lines, sizeof(*view->line), compare);
3852         redraw_view(view);
3855 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3857 /* Small author cache to reduce memory consumption. It uses binary
3858  * search to lookup or find place to position new entries. No entries
3859  * are ever freed. */
3860 static const char *
3861 get_author(const char *name)
3863         static const char **authors;
3864         static size_t authors_size;
3865         int from = 0, to = authors_size - 1;
3867         while (from <= to) {
3868                 size_t pos = (to + from) / 2;
3869                 int cmp = strcmp(name, authors[pos]);
3871                 if (!cmp)
3872                         return authors[pos];
3874                 if (cmp < 0)
3875                         to = pos - 1;
3876                 else
3877                         from = pos + 1;
3878         }
3880         if (!realloc_authors(&authors, authors_size, 1))
3881                 return NULL;
3882         name = strdup(name);
3883         if (!name)
3884                 return NULL;
3886         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3887         authors[from] = name;
3888         authors_size++;
3890         return name;
3893 static void
3894 parse_timesec(struct time *time, const char *sec)
3896         time->sec = (time_t) atol(sec);
3899 static void
3900 parse_timezone(struct time *time, const char *zone)
3902         long tz;
3904         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3905         tz += ('0' - zone[2]) * 60 * 60;
3906         tz += ('0' - zone[3]) * 60 * 10;
3907         tz += ('0' - zone[4]) * 60;
3909         if (zone[0] == '-')
3910                 tz = -tz;
3912         time->tz = tz;
3913         time->sec -= tz;
3916 /* Parse author lines where the name may be empty:
3917  *      author  <email@address.tld> 1138474660 +0100
3918  */
3919 static void
3920 parse_author_line(char *ident, const char **author, struct time *time)
3922         char *nameend = strchr(ident, '<');
3923         char *emailend = strchr(ident, '>');
3925         if (nameend && emailend)
3926                 *nameend = *emailend = 0;
3927         ident = chomp_string(ident);
3928         if (!*ident) {
3929                 if (nameend)
3930                         ident = chomp_string(nameend + 1);
3931                 if (!*ident)
3932                         ident = "Unknown";
3933         }
3935         *author = get_author(ident);
3937         /* Parse epoch and timezone */
3938         if (emailend && emailend[1] == ' ') {
3939                 char *secs = emailend + 2;
3940                 char *zone = strchr(secs, ' ');
3942                 parse_timesec(time, secs);
3944                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3945                         parse_timezone(time, zone + 1);
3946         }
3949 static bool
3950 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3952         char rev[SIZEOF_REV];
3953         const char *revlist_argv[] = {
3954                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3955         };
3956         struct menu_item *items;
3957         char text[SIZEOF_STR];
3958         bool ok = TRUE;
3959         int i;
3961         items = calloc(*parents + 1, sizeof(*items));
3962         if (!items)
3963                 return FALSE;
3965         for (i = 0; i < *parents; i++) {
3966                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3967                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3968                     !(items[i].text = strdup(text))) {
3969                         ok = FALSE;
3970                         break;
3971                 }
3972         }
3974         if (ok) {
3975                 *parents = 0;
3976                 ok = prompt_menu("Select parent", items, parents);
3977         }
3978         for (i = 0; items[i].text; i++)
3979                 free((char *) items[i].text);
3980         free(items);
3981         return ok;
3984 static bool
3985 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3987         char buf[SIZEOF_STR * 4];
3988         const char *revlist_argv[] = {
3989                 "git", "log", "--no-color", "-1",
3990                         "--pretty=format:%P", id, "--", path, NULL
3991         };
3992         int parents;
3994         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3995             (parents = strlen(buf) / 40) < 0) {
3996                 report("Failed to get parent information");
3997                 return FALSE;
3999         } else if (parents == 0) {
4000                 if (path)
4001                         report("Path '%s' does not exist in the parent", path);
4002                 else
4003                         report("The selected commit has no parents");
4004                 return FALSE;
4005         }
4007         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
4008                 return FALSE;
4010         string_copy_rev(rev, &buf[41 * parents]);
4011         return TRUE;
4014 /*
4015  * Pager backend
4016  */
4018 static bool
4019 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4021         char text[SIZEOF_STR];
4023         if (opt_line_number && draw_lineno(view, lineno))
4024                 return TRUE;
4026         string_expand(text, sizeof(text), line->data, opt_tab_size);
4027         draw_text(view, line->type, text, TRUE);
4028         return TRUE;
4031 static bool
4032 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4034         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4035         char ref[SIZEOF_STR];
4037         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4038                 return TRUE;
4040         /* This is the only fatal call, since it can "corrupt" the buffer. */
4041         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4042                 return FALSE;
4044         return TRUE;
4047 static void
4048 add_pager_refs(struct view *view, struct line *line)
4050         char buf[SIZEOF_STR];
4051         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4052         struct ref_list *list;
4053         size_t bufpos = 0, i;
4054         const char *sep = "Refs: ";
4055         bool is_tag = FALSE;
4057         assert(line->type == LINE_COMMIT);
4059         list = get_ref_list(commit_id);
4060         if (!list) {
4061                 if (view->type == VIEW_DIFF)
4062                         goto try_add_describe_ref;
4063                 return;
4064         }
4066         for (i = 0; i < list->size; i++) {
4067                 struct ref *ref = list->refs[i];
4068                 const char *fmt = ref->tag    ? "%s[%s]" :
4069                                   ref->remote ? "%s<%s>" : "%s%s";
4071                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4072                         return;
4073                 sep = ", ";
4074                 if (ref->tag)
4075                         is_tag = TRUE;
4076         }
4078         if (!is_tag && view->type == VIEW_DIFF) {
4079 try_add_describe_ref:
4080                 /* Add <tag>-g<commit_id> "fake" reference. */
4081                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4082                         return;
4083         }
4085         if (bufpos == 0)
4086                 return;
4088         add_line_text(view, buf, LINE_PP_REFS);
4091 static bool
4092 pager_read(struct view *view, char *data)
4094         struct line *line;
4096         if (!data)
4097                 return TRUE;
4099         line = add_line_text(view, data, get_line_type(data));
4100         if (!line)
4101                 return FALSE;
4103         if (line->type == LINE_COMMIT &&
4104             (view->type == VIEW_DIFF ||
4105              view->type == VIEW_LOG))
4106                 add_pager_refs(view, line);
4108         return TRUE;
4111 static enum request
4112 pager_request(struct view *view, enum request request, struct line *line)
4114         int split = 0;
4116         if (request != REQ_ENTER)
4117                 return request;
4119         if (line->type == LINE_COMMIT &&
4120            (view->type == VIEW_LOG ||
4121             view->type == VIEW_PAGER)) {
4122                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4123                 split = 1;
4124         }
4126         /* Always scroll the view even if it was split. That way
4127          * you can use Enter to scroll through the log view and
4128          * split open each commit diff. */
4129         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4131         /* FIXME: A minor workaround. Scrolling the view will call report("")
4132          * but if we are scrolling a non-current view this won't properly
4133          * update the view title. */
4134         if (split)
4135                 update_view_title(view);
4137         return REQ_NONE;
4140 static bool
4141 pager_grep(struct view *view, struct line *line)
4143         const char *text[] = { line->data, NULL };
4145         return grep_text(view, text);
4148 static void
4149 pager_select(struct view *view, struct line *line)
4151         if (line->type == LINE_COMMIT) {
4152                 char *text = (char *)line->data + STRING_SIZE("commit ");
4154                 if (view->type != VIEW_PAGER)
4155                         string_copy_rev(view->ref, text);
4156                 string_copy_rev(ref_commit, text);
4157         }
4160 static struct view_ops pager_ops = {
4161         "line",
4162         NULL,
4163         NULL,
4164         pager_read,
4165         pager_draw,
4166         pager_request,
4167         pager_grep,
4168         pager_select,
4169 };
4171 static const char *log_argv[SIZEOF_ARG] = {
4172         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4173 };
4175 static enum request
4176 log_request(struct view *view, enum request request, struct line *line)
4178         switch (request) {
4179         case REQ_REFRESH:
4180                 load_refs();
4181                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4182                 return REQ_NONE;
4183         default:
4184                 return pager_request(view, request, line);
4185         }
4188 static struct view_ops log_ops = {
4189         "line",
4190         log_argv,
4191         NULL,
4192         pager_read,
4193         pager_draw,
4194         log_request,
4195         pager_grep,
4196         pager_select,
4197 };
4199 static const char *diff_argv[SIZEOF_ARG] = {
4200         "git", "show", "--pretty=fuller", "--no-color", "--root",
4201                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4202 };
4204 static struct view_ops diff_ops = {
4205         "line",
4206         diff_argv,
4207         NULL,
4208         pager_read,
4209         pager_draw,
4210         pager_request,
4211         pager_grep,
4212         pager_select,
4213 };
4215 /*
4216  * Help backend
4217  */
4219 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4221 static bool
4222 help_open_keymap_title(struct view *view, enum keymap keymap)
4224         struct line *line;
4226         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4227                                help_keymap_hidden[keymap] ? '+' : '-',
4228                                enum_name(keymap_table[keymap]));
4229         if (line)
4230                 line->other = keymap;
4232         return help_keymap_hidden[keymap];
4235 static void
4236 help_open_keymap(struct view *view, enum keymap keymap)
4238         const char *group = NULL;
4239         char buf[SIZEOF_STR];
4240         size_t bufpos;
4241         bool add_title = TRUE;
4242         int i;
4244         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4245                 const char *key = NULL;
4247                 if (req_info[i].request == REQ_NONE)
4248                         continue;
4250                 if (!req_info[i].request) {
4251                         group = req_info[i].help;
4252                         continue;
4253                 }
4255                 key = get_keys(keymap, req_info[i].request, TRUE);
4256                 if (!key || !*key)
4257                         continue;
4259                 if (add_title && help_open_keymap_title(view, keymap))
4260                         return;
4261                 add_title = FALSE;
4263                 if (group) {
4264                         add_line_text(view, group, LINE_HELP_GROUP);
4265                         group = NULL;
4266                 }
4268                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4269                                 enum_name(req_info[i]), req_info[i].help);
4270         }
4272         group = "External commands:";
4274         for (i = 0; i < run_requests; i++) {
4275                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4276                 const char *key;
4277                 int argc;
4279                 if (!req || req->keymap != keymap)
4280                         continue;
4282                 key = get_key_name(req->key);
4283                 if (!*key)
4284                         key = "(no key defined)";
4286                 if (add_title && help_open_keymap_title(view, keymap))
4287                         return;
4288                 if (group) {
4289                         add_line_text(view, group, LINE_HELP_GROUP);
4290                         group = NULL;
4291                 }
4293                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4294                         if (!string_format_from(buf, &bufpos, "%s%s",
4295                                                 argc ? " " : "", req->argv[argc]))
4296                                 return;
4298                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4299         }
4302 static bool
4303 help_open(struct view *view)
4305         enum keymap keymap;
4307         reset_view(view);
4308         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4309         add_line_text(view, "", LINE_DEFAULT);
4311         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4312                 help_open_keymap(view, keymap);
4314         return TRUE;
4317 static enum request
4318 help_request(struct view *view, enum request request, struct line *line)
4320         switch (request) {
4321         case REQ_ENTER:
4322                 if (line->type == LINE_HELP_KEYMAP) {
4323                         help_keymap_hidden[line->other] =
4324                                 !help_keymap_hidden[line->other];
4325                         view->p_restore = TRUE;
4326                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4327                 }
4329                 return REQ_NONE;
4330         default:
4331                 return pager_request(view, request, line);
4332         }
4335 static struct view_ops help_ops = {
4336         "line",
4337         NULL,
4338         help_open,
4339         NULL,
4340         pager_draw,
4341         help_request,
4342         pager_grep,
4343         pager_select,
4344 };
4347 /*
4348  * Tree backend
4349  */
4351 struct tree_stack_entry {
4352         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4353         unsigned long lineno;           /* Line number to restore */
4354         char *name;                     /* Position of name in opt_path */
4355 };
4357 /* The top of the path stack. */
4358 static struct tree_stack_entry *tree_stack = NULL;
4359 unsigned long tree_lineno = 0;
4361 static void
4362 pop_tree_stack_entry(void)
4364         struct tree_stack_entry *entry = tree_stack;
4366         tree_lineno = entry->lineno;
4367         entry->name[0] = 0;
4368         tree_stack = entry->prev;
4369         free(entry);
4372 static void
4373 push_tree_stack_entry(const char *name, unsigned long lineno)
4375         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4376         size_t pathlen = strlen(opt_path);
4378         if (!entry)
4379                 return;
4381         entry->prev = tree_stack;
4382         entry->name = opt_path + pathlen;
4383         tree_stack = entry;
4385         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4386                 pop_tree_stack_entry();
4387                 return;
4388         }
4390         /* Move the current line to the first tree entry. */
4391         tree_lineno = 1;
4392         entry->lineno = lineno;
4395 /* Parse output from git-ls-tree(1):
4396  *
4397  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4398  */
4400 #define SIZEOF_TREE_ATTR \
4401         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4403 #define SIZEOF_TREE_MODE \
4404         STRING_SIZE("100644 ")
4406 #define TREE_ID_OFFSET \
4407         STRING_SIZE("100644 blob ")
4409 struct tree_entry {
4410         char id[SIZEOF_REV];
4411         mode_t mode;
4412         struct time time;               /* Date from the author ident. */
4413         const char *author;             /* Author of the commit. */
4414         char name[1];
4415 };
4417 static const char *
4418 tree_path(const struct line *line)
4420         return ((struct tree_entry *) line->data)->name;
4423 static int
4424 tree_compare_entry(const struct line *line1, const struct line *line2)
4426         if (line1->type != line2->type)
4427                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4428         return strcmp(tree_path(line1), tree_path(line2));
4431 static const enum sort_field tree_sort_fields[] = {
4432         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4433 };
4434 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4436 static int
4437 tree_compare(const void *l1, const void *l2)
4439         const struct line *line1 = (const struct line *) l1;
4440         const struct line *line2 = (const struct line *) l2;
4441         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4442         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4444         if (line1->type == LINE_TREE_HEAD)
4445                 return -1;
4446         if (line2->type == LINE_TREE_HEAD)
4447                 return 1;
4449         switch (get_sort_field(tree_sort_state)) {
4450         case ORDERBY_DATE:
4451                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4453         case ORDERBY_AUTHOR:
4454                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4456         case ORDERBY_NAME:
4457         default:
4458                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4459         }
4463 static struct line *
4464 tree_entry(struct view *view, enum line_type type, const char *path,
4465            const char *mode, const char *id)
4467         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4468         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4470         if (!entry || !line) {
4471                 free(entry);
4472                 return NULL;
4473         }
4475         strncpy(entry->name, path, strlen(path));
4476         if (mode)
4477                 entry->mode = strtoul(mode, NULL, 8);
4478         if (id)
4479                 string_copy_rev(entry->id, id);
4481         return line;
4484 static bool
4485 tree_read_date(struct view *view, char *text, bool *read_date)
4487         static const char *author_name;
4488         static struct time author_time;
4490         if (!text && *read_date) {
4491                 *read_date = FALSE;
4492                 return TRUE;
4494         } else if (!text) {
4495                 char *path = *opt_path ? opt_path : ".";
4496                 /* Find next entry to process */
4497                 const char *log_file[] = {
4498                         "git", "log", "--no-color", "--pretty=raw",
4499                                 "--cc", "--raw", view->id, "--", path, NULL
4500                 };
4501                 struct io io = {};
4503                 if (!view->lines) {
4504                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4505                         report("Tree is empty");
4506                         return TRUE;
4507                 }
4509                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4510                         report("Failed to load tree data");
4511                         return TRUE;
4512                 }
4514                 io_done(view->pipe);
4515                 view->io = io;
4516                 *read_date = TRUE;
4517                 return FALSE;
4519         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4520                 parse_author_line(text + STRING_SIZE("author "),
4521                                   &author_name, &author_time);
4523         } else if (*text == ':') {
4524                 char *pos;
4525                 size_t annotated = 1;
4526                 size_t i;
4528                 pos = strchr(text, '\t');
4529                 if (!pos)
4530                         return TRUE;
4531                 text = pos + 1;
4532                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4533                         text += strlen(opt_path);
4534                 pos = strchr(text, '/');
4535                 if (pos)
4536                         *pos = 0;
4538                 for (i = 1; i < view->lines; i++) {
4539                         struct line *line = &view->line[i];
4540                         struct tree_entry *entry = line->data;
4542                         annotated += !!entry->author;
4543                         if (entry->author || strcmp(entry->name, text))
4544                                 continue;
4546                         entry->author = author_name;
4547                         entry->time = author_time;
4548                         line->dirty = 1;
4549                         break;
4550                 }
4552                 if (annotated == view->lines)
4553                         io_kill(view->pipe);
4554         }
4555         return TRUE;
4558 static bool
4559 tree_read(struct view *view, char *text)
4561         static bool read_date = FALSE;
4562         struct tree_entry *data;
4563         struct line *entry, *line;
4564         enum line_type type;
4565         size_t textlen = text ? strlen(text) : 0;
4566         char *path = text + SIZEOF_TREE_ATTR;
4568         if (read_date || !text)
4569                 return tree_read_date(view, text, &read_date);
4571         if (textlen <= SIZEOF_TREE_ATTR)
4572                 return FALSE;
4573         if (view->lines == 0 &&
4574             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4575                 return FALSE;
4577         /* Strip the path part ... */
4578         if (*opt_path) {
4579                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4580                 size_t striplen = strlen(opt_path);
4582                 if (pathlen > striplen)
4583                         memmove(path, path + striplen,
4584                                 pathlen - striplen + 1);
4586                 /* Insert "link" to parent directory. */
4587                 if (view->lines == 1 &&
4588                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4589                         return FALSE;
4590         }
4592         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4593         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4594         if (!entry)
4595                 return FALSE;
4596         data = entry->data;
4598         /* Skip "Directory ..." and ".." line. */
4599         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4600                 if (tree_compare_entry(line, entry) <= 0)
4601                         continue;
4603                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4605                 line->data = data;
4606                 line->type = type;
4607                 for (; line <= entry; line++)
4608                         line->dirty = line->cleareol = 1;
4609                 return TRUE;
4610         }
4612         if (tree_lineno > view->lineno) {
4613                 view->lineno = tree_lineno;
4614                 tree_lineno = 0;
4615         }
4617         return TRUE;
4620 static bool
4621 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4623         struct tree_entry *entry = line->data;
4625         if (line->type == LINE_TREE_HEAD) {
4626                 if (draw_text(view, line->type, "Directory path /", TRUE))
4627                         return TRUE;
4628         } else {
4629                 if (draw_mode(view, entry->mode))
4630                         return TRUE;
4632                 if (opt_author && draw_author(view, entry->author))
4633                         return TRUE;
4635                 if (opt_date && draw_date(view, &entry->time))
4636                         return TRUE;
4637         }
4638         if (draw_text(view, line->type, entry->name, TRUE))
4639                 return TRUE;
4640         return TRUE;
4643 static void
4644 open_blob_editor()
4646         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4647         int fd = mkstemp(file);
4649         if (fd == -1)
4650                 report("Failed to create temporary file");
4651         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4652                 report("Failed to save blob data to file");
4653         else
4654                 open_editor(file);
4655         if (fd != -1)
4656                 unlink(file);
4659 static enum request
4660 tree_request(struct view *view, enum request request, struct line *line)
4662         enum open_flags flags;
4664         switch (request) {
4665         case REQ_VIEW_BLAME:
4666                 if (line->type != LINE_TREE_FILE) {
4667                         report("Blame only supported for files");
4668                         return REQ_NONE;
4669                 }
4671                 string_copy(opt_ref, view->vid);
4672                 return request;
4674         case REQ_EDIT:
4675                 if (line->type != LINE_TREE_FILE) {
4676                         report("Edit only supported for files");
4677                 } else if (!is_head_commit(view->vid)) {
4678                         open_blob_editor();
4679                 } else {
4680                         open_editor(opt_file);
4681                 }
4682                 return REQ_NONE;
4684         case REQ_TOGGLE_SORT_FIELD:
4685         case REQ_TOGGLE_SORT_ORDER:
4686                 sort_view(view, request, &tree_sort_state, tree_compare);
4687                 return REQ_NONE;
4689         case REQ_PARENT:
4690                 if (!*opt_path) {
4691                         /* quit view if at top of tree */
4692                         return REQ_VIEW_CLOSE;
4693                 }
4694                 /* fake 'cd  ..' */
4695                 line = &view->line[1];
4696                 break;
4698         case REQ_ENTER:
4699                 break;
4701         default:
4702                 return request;
4703         }
4705         /* Cleanup the stack if the tree view is at a different tree. */
4706         while (!*opt_path && tree_stack)
4707                 pop_tree_stack_entry();
4709         switch (line->type) {
4710         case LINE_TREE_DIR:
4711                 /* Depending on whether it is a subdirectory or parent link
4712                  * mangle the path buffer. */
4713                 if (line == &view->line[1] && *opt_path) {
4714                         pop_tree_stack_entry();
4716                 } else {
4717                         const char *basename = tree_path(line);
4719                         push_tree_stack_entry(basename, view->lineno);
4720                 }
4722                 /* Trees and subtrees share the same ID, so they are not not
4723                  * unique like blobs. */
4724                 flags = OPEN_RELOAD;
4725                 request = REQ_VIEW_TREE;
4726                 break;
4728         case LINE_TREE_FILE:
4729                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4730                 request = REQ_VIEW_BLOB;
4731                 break;
4733         default:
4734                 return REQ_NONE;
4735         }
4737         open_view(view, request, flags);
4738         if (request == REQ_VIEW_TREE)
4739                 view->lineno = tree_lineno;
4741         return REQ_NONE;
4744 static bool
4745 tree_grep(struct view *view, struct line *line)
4747         struct tree_entry *entry = line->data;
4748         const char *text[] = {
4749                 entry->name,
4750                 opt_author ? entry->author : "",
4751                 mkdate(&entry->time, opt_date),
4752                 NULL
4753         };
4755         return grep_text(view, text);
4758 static void
4759 tree_select(struct view *view, struct line *line)
4761         struct tree_entry *entry = line->data;
4763         if (line->type == LINE_TREE_FILE) {
4764                 string_copy_rev(ref_blob, entry->id);
4765                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4767         } else if (line->type != LINE_TREE_DIR) {
4768                 return;
4769         }
4771         string_copy_rev(view->ref, entry->id);
4774 static bool
4775 tree_prepare(struct view *view)
4777         if (view->lines == 0 && opt_prefix[0]) {
4778                 char *pos = opt_prefix;
4780                 while (pos && *pos) {
4781                         char *end = strchr(pos, '/');
4783                         if (end)
4784                                 *end = 0;
4785                         push_tree_stack_entry(pos, 0);
4786                         pos = end;
4787                         if (end) {
4788                                 *end = '/';
4789                                 pos++;
4790                         }
4791                 }
4793         } else if (strcmp(view->vid, view->id)) {
4794                 opt_path[0] = 0;
4795         }
4797         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4800 static const char *tree_argv[SIZEOF_ARG] = {
4801         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4802 };
4804 static struct view_ops tree_ops = {
4805         "file",
4806         tree_argv,
4807         NULL,
4808         tree_read,
4809         tree_draw,
4810         tree_request,
4811         tree_grep,
4812         tree_select,
4813         tree_prepare,
4814 };
4816 static bool
4817 blob_read(struct view *view, char *line)
4819         if (!line)
4820                 return TRUE;
4821         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4824 static enum request
4825 blob_request(struct view *view, enum request request, struct line *line)
4827         switch (request) {
4828         case REQ_EDIT:
4829                 open_blob_editor();
4830                 return REQ_NONE;
4831         default:
4832                 return pager_request(view, request, line);
4833         }
4836 static const char *blob_argv[SIZEOF_ARG] = {
4837         "git", "cat-file", "blob", "%(blob)", NULL
4838 };
4840 static struct view_ops blob_ops = {
4841         "line",
4842         blob_argv,
4843         NULL,
4844         blob_read,
4845         pager_draw,
4846         blob_request,
4847         pager_grep,
4848         pager_select,
4849 };
4851 /*
4852  * Blame backend
4853  *
4854  * Loading the blame view is a two phase job:
4855  *
4856  *  1. File content is read either using opt_file from the
4857  *     filesystem or using git-cat-file.
4858  *  2. Then blame information is incrementally added by
4859  *     reading output from git-blame.
4860  */
4862 static const char *blame_head_argv[] = {
4863         "git", "blame", "--incremental", "--", "%(file)", NULL
4864 };
4866 static const char *blame_ref_argv[] = {
4867         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4868 };
4870 static const char *blame_cat_file_argv[] = {
4871         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4872 };
4874 struct blame_commit {
4875         char id[SIZEOF_REV];            /* SHA1 ID. */
4876         char title[128];                /* First line of the commit message. */
4877         const char *author;             /* Author of the commit. */
4878         struct time time;               /* Date from the author ident. */
4879         char filename[128];             /* Name of file. */
4880         bool has_previous;              /* Was a "previous" line detected. */
4881 };
4883 struct blame {
4884         struct blame_commit *commit;
4885         unsigned long lineno;
4886         char text[1];
4887 };
4889 static bool
4890 blame_open(struct view *view)
4892         char path[SIZEOF_STR];
4894         if (!view->parent && *opt_prefix) {
4895                 string_copy(path, opt_file);
4896                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4897                         return FALSE;
4898         }
4900         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4901                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4902                         return FALSE;
4903         }
4905         setup_update(view, opt_file);
4906         string_format(view->ref, "%s ...", opt_file);
4908         return TRUE;
4911 static struct blame_commit *
4912 get_blame_commit(struct view *view, const char *id)
4914         size_t i;
4916         for (i = 0; i < view->lines; i++) {
4917                 struct blame *blame = view->line[i].data;
4919                 if (!blame->commit)
4920                         continue;
4922                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4923                         return blame->commit;
4924         }
4926         {
4927                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4929                 if (commit)
4930                         string_ncopy(commit->id, id, SIZEOF_REV);
4931                 return commit;
4932         }
4935 static bool
4936 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4938         const char *pos = *posref;
4940         *posref = NULL;
4941         pos = strchr(pos + 1, ' ');
4942         if (!pos || !isdigit(pos[1]))
4943                 return FALSE;
4944         *number = atoi(pos + 1);
4945         if (*number < min || *number > max)
4946                 return FALSE;
4948         *posref = pos;
4949         return TRUE;
4952 static struct blame_commit *
4953 parse_blame_commit(struct view *view, const char *text, int *blamed)
4955         struct blame_commit *commit;
4956         struct blame *blame;
4957         const char *pos = text + SIZEOF_REV - 2;
4958         size_t orig_lineno = 0;
4959         size_t lineno;
4960         size_t group;
4962         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4963                 return NULL;
4965         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4966             !parse_number(&pos, &lineno, 1, view->lines) ||
4967             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4968                 return NULL;
4970         commit = get_blame_commit(view, text);
4971         if (!commit)
4972                 return NULL;
4974         *blamed += group;
4975         while (group--) {
4976                 struct line *line = &view->line[lineno + group - 1];
4978                 blame = line->data;
4979                 blame->commit = commit;
4980                 blame->lineno = orig_lineno + group - 1;
4981                 line->dirty = 1;
4982         }
4984         return commit;
4987 static bool
4988 blame_read_file(struct view *view, const char *line, bool *read_file)
4990         if (!line) {
4991                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4992                 struct io io = {};
4994                 if (view->lines == 0 && !view->parent)
4995                         die("No blame exist for %s", view->vid);
4997                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4998                         report("Failed to load blame data");
4999                         return TRUE;
5000                 }
5002                 io_done(view->pipe);
5003                 view->io = io;
5004                 *read_file = FALSE;
5005                 return FALSE;
5007         } else {
5008                 size_t linelen = strlen(line);
5009                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5011                 if (!blame)
5012                         return FALSE;
5014                 blame->commit = NULL;
5015                 strncpy(blame->text, line, linelen);
5016                 blame->text[linelen] = 0;
5017                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5018         }
5021 static bool
5022 match_blame_header(const char *name, char **line)
5024         size_t namelen = strlen(name);
5025         bool matched = !strncmp(name, *line, namelen);
5027         if (matched)
5028                 *line += namelen;
5030         return matched;
5033 static bool
5034 blame_read(struct view *view, char *line)
5036         static struct blame_commit *commit = NULL;
5037         static int blamed = 0;
5038         static bool read_file = TRUE;
5040         if (read_file)
5041                 return blame_read_file(view, line, &read_file);
5043         if (!line) {
5044                 /* Reset all! */
5045                 commit = NULL;
5046                 blamed = 0;
5047                 read_file = TRUE;
5048                 string_format(view->ref, "%s", view->vid);
5049                 if (view_is_displayed(view)) {
5050                         update_view_title(view);
5051                         redraw_view_from(view, 0);
5052                 }
5053                 return TRUE;
5054         }
5056         if (!commit) {
5057                 commit = parse_blame_commit(view, line, &blamed);
5058                 string_format(view->ref, "%s %2d%%", view->vid,
5059                               view->lines ? blamed * 100 / view->lines : 0);
5061         } else if (match_blame_header("author ", &line)) {
5062                 commit->author = get_author(line);
5064         } else if (match_blame_header("author-time ", &line)) {
5065                 parse_timesec(&commit->time, line);
5067         } else if (match_blame_header("author-tz ", &line)) {
5068                 parse_timezone(&commit->time, line);
5070         } else if (match_blame_header("summary ", &line)) {
5071                 string_ncopy(commit->title, line, strlen(line));
5073         } else if (match_blame_header("previous ", &line)) {
5074                 commit->has_previous = TRUE;
5076         } else if (match_blame_header("filename ", &line)) {
5077                 string_ncopy(commit->filename, line, strlen(line));
5078                 commit = NULL;
5079         }
5081         return TRUE;
5084 static bool
5085 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5087         struct blame *blame = line->data;
5088         struct time *time = NULL;
5089         const char *id = NULL, *author = NULL;
5090         char text[SIZEOF_STR];
5092         if (blame->commit && *blame->commit->filename) {
5093                 id = blame->commit->id;
5094                 author = blame->commit->author;
5095                 time = &blame->commit->time;
5096         }
5098         if (opt_date && draw_date(view, time))
5099                 return TRUE;
5101         if (opt_author && draw_author(view, author))
5102                 return TRUE;
5104         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5105                 return TRUE;
5107         if (draw_lineno(view, lineno))
5108                 return TRUE;
5110         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5111         draw_text(view, LINE_DEFAULT, text, TRUE);
5112         return TRUE;
5115 static bool
5116 check_blame_commit(struct blame *blame, bool check_null_id)
5118         if (!blame->commit)
5119                 report("Commit data not loaded yet");
5120         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5121                 report("No commit exist for the selected line");
5122         else
5123                 return TRUE;
5124         return FALSE;
5127 static void
5128 setup_blame_parent_line(struct view *view, struct blame *blame)
5130         const char *diff_tree_argv[] = {
5131                 "git", "diff-tree", "-U0", blame->commit->id,
5132                         "--", blame->commit->filename, NULL
5133         };
5134         struct io io = {};
5135         int parent_lineno = -1;
5136         int blamed_lineno = -1;
5137         char *line;
5139         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5140                 return;
5142         while ((line = io_get(&io, '\n', TRUE))) {
5143                 if (*line == '@') {
5144                         char *pos = strchr(line, '+');
5146                         parent_lineno = atoi(line + 4);
5147                         if (pos)
5148                                 blamed_lineno = atoi(pos + 1);
5150                 } else if (*line == '+' && parent_lineno != -1) {
5151                         if (blame->lineno == blamed_lineno - 1 &&
5152                             !strcmp(blame->text, line + 1)) {
5153                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5154                                 break;
5155                         }
5156                         blamed_lineno++;
5157                 }
5158         }
5160         io_done(&io);
5163 static enum request
5164 blame_request(struct view *view, enum request request, struct line *line)
5166         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5167         struct blame *blame = line->data;
5169         switch (request) {
5170         case REQ_VIEW_BLAME:
5171                 if (check_blame_commit(blame, TRUE)) {
5172                         string_copy(opt_ref, blame->commit->id);
5173                         string_copy(opt_file, blame->commit->filename);
5174                         if (blame->lineno)
5175                                 view->lineno = blame->lineno;
5176                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5177                 }
5178                 break;
5180         case REQ_PARENT:
5181                 if (check_blame_commit(blame, TRUE) &&
5182                     select_commit_parent(blame->commit->id, opt_ref,
5183                                          blame->commit->filename)) {
5184                         string_copy(opt_file, blame->commit->filename);
5185                         setup_blame_parent_line(view, blame);
5186                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5187                 }
5188                 break;
5190         case REQ_ENTER:
5191                 if (!check_blame_commit(blame, FALSE))
5192                         break;
5194                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5195                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5196                         break;
5198                 if (!strcmp(blame->commit->id, NULL_ID)) {
5199                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5200                         const char *diff_index_argv[] = {
5201                                 "git", "diff-index", "--root", "--patch-with-stat",
5202                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5203                         };
5205                         if (!blame->commit->has_previous) {
5206                                 diff_index_argv[1] = "diff";
5207                                 diff_index_argv[2] = "--no-color";
5208                                 diff_index_argv[6] = "--";
5209                                 diff_index_argv[7] = "/dev/null";
5210                         }
5212                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5213                                 report("Failed to allocate diff command");
5214                                 break;
5215                         }
5216                         flags |= OPEN_PREPARED;
5217                 }
5219                 open_view(view, REQ_VIEW_DIFF, flags);
5220                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5221                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5222                 break;
5224         default:
5225                 return request;
5226         }
5228         return REQ_NONE;
5231 static bool
5232 blame_grep(struct view *view, struct line *line)
5234         struct blame *blame = line->data;
5235         struct blame_commit *commit = blame->commit;
5236         const char *text[] = {
5237                 blame->text,
5238                 commit ? commit->title : "",
5239                 commit ? commit->id : "",
5240                 commit && opt_author ? commit->author : "",
5241                 commit ? mkdate(&commit->time, opt_date) : "",
5242                 NULL
5243         };
5245         return grep_text(view, text);
5248 static void
5249 blame_select(struct view *view, struct line *line)
5251         struct blame *blame = line->data;
5252         struct blame_commit *commit = blame->commit;
5254         if (!commit)
5255                 return;
5257         if (!strcmp(commit->id, NULL_ID))
5258                 string_ncopy(ref_commit, "HEAD", 4);
5259         else
5260                 string_copy_rev(ref_commit, commit->id);
5263 static struct view_ops blame_ops = {
5264         "line",
5265         NULL,
5266         blame_open,
5267         blame_read,
5268         blame_draw,
5269         blame_request,
5270         blame_grep,
5271         blame_select,
5272 };
5274 /*
5275  * Branch backend
5276  */
5278 struct branch {
5279         const char *author;             /* Author of the last commit. */
5280         struct time time;               /* Date of the last activity. */
5281         const struct ref *ref;          /* Name and commit ID information. */
5282 };
5284 static const struct ref branch_all;
5286 static const enum sort_field branch_sort_fields[] = {
5287         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5288 };
5289 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5291 static int
5292 branch_compare(const void *l1, const void *l2)
5294         const struct branch *branch1 = ((const struct line *) l1)->data;
5295         const struct branch *branch2 = ((const struct line *) l2)->data;
5297         switch (get_sort_field(branch_sort_state)) {
5298         case ORDERBY_DATE:
5299                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5301         case ORDERBY_AUTHOR:
5302                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5304         case ORDERBY_NAME:
5305         default:
5306                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5307         }
5310 static bool
5311 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5313         struct branch *branch = line->data;
5314         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5316         if (opt_date && draw_date(view, &branch->time))
5317                 return TRUE;
5319         if (opt_author && draw_author(view, branch->author))
5320                 return TRUE;
5322         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5323         return TRUE;
5326 static enum request
5327 branch_request(struct view *view, enum request request, struct line *line)
5329         struct branch *branch = line->data;
5331         switch (request) {
5332         case REQ_REFRESH:
5333                 load_refs();
5334                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5335                 return REQ_NONE;
5337         case REQ_TOGGLE_SORT_FIELD:
5338         case REQ_TOGGLE_SORT_ORDER:
5339                 sort_view(view, request, &branch_sort_state, branch_compare);
5340                 return REQ_NONE;
5342         case REQ_ENTER:
5343                 if (branch->ref == &branch_all) {
5344                         const char *all_branches_argv[] = {
5345                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5346                                       "--topo-order", "--all", NULL
5347                         };
5348                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5350                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5351                                 report("Failed to load view of all branches");
5352                                 return REQ_NONE;
5353                         }
5354                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5355                 } else {
5356                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5357                 }
5358                 return REQ_NONE;
5360         default:
5361                 return request;
5362         }
5365 static bool
5366 branch_read(struct view *view, char *line)
5368         static char id[SIZEOF_REV];
5369         struct branch *reference;
5370         size_t i;
5372         if (!line)
5373                 return TRUE;
5375         switch (get_line_type(line)) {
5376         case LINE_COMMIT:
5377                 string_copy_rev(id, line + STRING_SIZE("commit "));
5378                 return TRUE;
5380         case LINE_AUTHOR:
5381                 for (i = 0, reference = NULL; i < view->lines; i++) {
5382                         struct branch *branch = view->line[i].data;
5384                         if (strcmp(branch->ref->id, id))
5385                                 continue;
5387                         view->line[i].dirty = TRUE;
5388                         if (reference) {
5389                                 branch->author = reference->author;
5390                                 branch->time = reference->time;
5391                                 continue;
5392                         }
5394                         parse_author_line(line + STRING_SIZE("author "),
5395                                           &branch->author, &branch->time);
5396                         reference = branch;
5397                 }
5398                 return TRUE;
5400         default:
5401                 return TRUE;
5402         }
5406 static bool
5407 branch_open_visitor(void *data, const struct ref *ref)
5409         struct view *view = data;
5410         struct branch *branch;
5412         if (ref->tag || ref->ltag || ref->remote)
5413                 return TRUE;
5415         branch = calloc(1, sizeof(*branch));
5416         if (!branch)
5417                 return FALSE;
5419         branch->ref = ref;
5420         return !!add_line_data(view, branch, LINE_DEFAULT);
5423 static bool
5424 branch_open(struct view *view)
5426         const char *branch_log[] = {
5427                 "git", "log", "--no-color", "--pretty=raw",
5428                         "--simplify-by-decoration", "--all", NULL
5429         };
5431         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5432                 report("Failed to load branch data");
5433                 return TRUE;
5434         }
5436         setup_update(view, view->id);
5437         branch_open_visitor(view, &branch_all);
5438         foreach_ref(branch_open_visitor, view);
5439         view->p_restore = TRUE;
5441         return TRUE;
5444 static bool
5445 branch_grep(struct view *view, struct line *line)
5447         struct branch *branch = line->data;
5448         const char *text[] = {
5449                 branch->ref->name,
5450                 branch->author,
5451                 NULL
5452         };
5454         return grep_text(view, text);
5457 static void
5458 branch_select(struct view *view, struct line *line)
5460         struct branch *branch = line->data;
5462         string_copy_rev(view->ref, branch->ref->id);
5463         string_copy_rev(ref_commit, branch->ref->id);
5464         string_copy_rev(ref_head, branch->ref->id);
5465         string_copy_rev(ref_branch, branch->ref->name);
5468 static struct view_ops branch_ops = {
5469         "branch",
5470         NULL,
5471         branch_open,
5472         branch_read,
5473         branch_draw,
5474         branch_request,
5475         branch_grep,
5476         branch_select,
5477 };
5479 /*
5480  * Status backend
5481  */
5483 struct status {
5484         char status;
5485         struct {
5486                 mode_t mode;
5487                 char rev[SIZEOF_REV];
5488                 char name[SIZEOF_STR];
5489         } old;
5490         struct {
5491                 mode_t mode;
5492                 char rev[SIZEOF_REV];
5493                 char name[SIZEOF_STR];
5494         } new;
5495 };
5497 static char status_onbranch[SIZEOF_STR];
5498 static struct status stage_status;
5499 static enum line_type stage_line_type;
5500 static size_t stage_chunks;
5501 static int *stage_chunk;
5503 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5505 /* This should work even for the "On branch" line. */
5506 static inline bool
5507 status_has_none(struct view *view, struct line *line)
5509         return line < view->line + view->lines && !line[1].data;
5512 /* Get fields from the diff line:
5513  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5514  */
5515 static inline bool
5516 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5518         const char *old_mode = buf +  1;
5519         const char *new_mode = buf +  8;
5520         const char *old_rev  = buf + 15;
5521         const char *new_rev  = buf + 56;
5522         const char *status   = buf + 97;
5524         if (bufsize < 98 ||
5525             old_mode[-1] != ':' ||
5526             new_mode[-1] != ' ' ||
5527             old_rev[-1]  != ' ' ||
5528             new_rev[-1]  != ' ' ||
5529             status[-1]   != ' ')
5530                 return FALSE;
5532         file->status = *status;
5534         string_copy_rev(file->old.rev, old_rev);
5535         string_copy_rev(file->new.rev, new_rev);
5537         file->old.mode = strtoul(old_mode, NULL, 8);
5538         file->new.mode = strtoul(new_mode, NULL, 8);
5540         file->old.name[0] = file->new.name[0] = 0;
5542         return TRUE;
5545 static bool
5546 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5548         struct status *unmerged = NULL;
5549         char *buf;
5550         struct io io = {};
5552         if (!io_run(&io, argv, opt_cdup, IO_RD))
5553                 return FALSE;
5555         add_line_data(view, NULL, type);
5557         while ((buf = io_get(&io, 0, TRUE))) {
5558                 struct status *file = unmerged;
5560                 if (!file) {
5561                         file = calloc(1, sizeof(*file));
5562                         if (!file || !add_line_data(view, file, type))
5563                                 goto error_out;
5564                 }
5566                 /* Parse diff info part. */
5567                 if (status) {
5568                         file->status = status;
5569                         if (status == 'A')
5570                                 string_copy(file->old.rev, NULL_ID);
5572                 } else if (!file->status || file == unmerged) {
5573                         if (!status_get_diff(file, buf, strlen(buf)))
5574                                 goto error_out;
5576                         buf = io_get(&io, 0, TRUE);
5577                         if (!buf)
5578                                 break;
5580                         /* Collapse all modified entries that follow an
5581                          * associated unmerged entry. */
5582                         if (unmerged == file) {
5583                                 unmerged->status = 'U';
5584                                 unmerged = NULL;
5585                         } else if (file->status == 'U') {
5586                                 unmerged = file;
5587                         }
5588                 }
5590                 /* Grab the old name for rename/copy. */
5591                 if (!*file->old.name &&
5592                     (file->status == 'R' || file->status == 'C')) {
5593                         string_ncopy(file->old.name, buf, strlen(buf));
5595                         buf = io_get(&io, 0, TRUE);
5596                         if (!buf)
5597                                 break;
5598                 }
5600                 /* git-ls-files just delivers a NUL separated list of
5601                  * file names similar to the second half of the
5602                  * git-diff-* output. */
5603                 string_ncopy(file->new.name, buf, strlen(buf));
5604                 if (!*file->old.name)
5605                         string_copy(file->old.name, file->new.name);
5606                 file = NULL;
5607         }
5609         if (io_error(&io)) {
5610 error_out:
5611                 io_done(&io);
5612                 return FALSE;
5613         }
5615         if (!view->line[view->lines - 1].data)
5616                 add_line_data(view, NULL, LINE_STAT_NONE);
5618         io_done(&io);
5619         return TRUE;
5622 /* Don't show unmerged entries in the staged section. */
5623 static const char *status_diff_index_argv[] = {
5624         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5625                              "--cached", "-M", "HEAD", NULL
5626 };
5628 static const char *status_diff_files_argv[] = {
5629         "git", "diff-files", "-z", NULL
5630 };
5632 static const char *status_list_other_argv[] = {
5633         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5634 };
5636 static const char *status_list_no_head_argv[] = {
5637         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5638 };
5640 static const char *update_index_argv[] = {
5641         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5642 };
5644 /* Restore the previous line number to stay in the context or select a
5645  * line with something that can be updated. */
5646 static void
5647 status_restore(struct view *view)
5649         if (view->p_lineno >= view->lines)
5650                 view->p_lineno = view->lines - 1;
5651         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5652                 view->p_lineno++;
5653         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5654                 view->p_lineno--;
5656         /* If the above fails, always skip the "On branch" line. */
5657         if (view->p_lineno < view->lines)
5658                 view->lineno = view->p_lineno;
5659         else
5660                 view->lineno = 1;
5662         if (view->lineno < view->offset)
5663                 view->offset = view->lineno;
5664         else if (view->offset + view->height <= view->lineno)
5665                 view->offset = view->lineno - view->height + 1;
5667         view->p_restore = FALSE;
5670 static void
5671 status_update_onbranch(void)
5673         static const char *paths[][2] = {
5674                 { "rebase-apply/rebasing",      "Rebasing" },
5675                 { "rebase-apply/applying",      "Applying mailbox" },
5676                 { "rebase-apply/",              "Rebasing mailbox" },
5677                 { "rebase-merge/interactive",   "Interactive rebase" },
5678                 { "rebase-merge/",              "Rebase merge" },
5679                 { "MERGE_HEAD",                 "Merging" },
5680                 { "BISECT_LOG",                 "Bisecting" },
5681                 { "HEAD",                       "On branch" },
5682         };
5683         char buf[SIZEOF_STR];
5684         struct stat stat;
5685         int i;
5687         if (is_initial_commit()) {
5688                 string_copy(status_onbranch, "Initial commit");
5689                 return;
5690         }
5692         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5693                 char *head = opt_head;
5695                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5696                     lstat(buf, &stat) < 0)
5697                         continue;
5699                 if (!*opt_head) {
5700                         struct io io = {};
5702                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5703                             io_read_buf(&io, buf, sizeof(buf))) {
5704                                 head = buf;
5705                                 if (!prefixcmp(head, "refs/heads/"))
5706                                         head += STRING_SIZE("refs/heads/");
5707                         }
5708                 }
5710                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5711                         string_copy(status_onbranch, opt_head);
5712                 return;
5713         }
5715         string_copy(status_onbranch, "Not currently on any branch");
5718 /* First parse staged info using git-diff-index(1), then parse unstaged
5719  * info using git-diff-files(1), and finally untracked files using
5720  * git-ls-files(1). */
5721 static bool
5722 status_open(struct view *view)
5724         reset_view(view);
5726         add_line_data(view, NULL, LINE_STAT_HEAD);
5727         status_update_onbranch();
5729         io_run_bg(update_index_argv);
5731         if (is_initial_commit()) {
5732                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5733                         return FALSE;
5734         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5735                 return FALSE;
5736         }
5738         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5739             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5740                 return FALSE;
5742         /* Restore the exact position or use the specialized restore
5743          * mode? */
5744         if (!view->p_restore)
5745                 status_restore(view);
5746         return TRUE;
5749 static bool
5750 status_draw(struct view *view, struct line *line, unsigned int lineno)
5752         struct status *status = line->data;
5753         enum line_type type;
5754         const char *text;
5756         if (!status) {
5757                 switch (line->type) {
5758                 case LINE_STAT_STAGED:
5759                         type = LINE_STAT_SECTION;
5760                         text = "Changes to be committed:";
5761                         break;
5763                 case LINE_STAT_UNSTAGED:
5764                         type = LINE_STAT_SECTION;
5765                         text = "Changed but not updated:";
5766                         break;
5768                 case LINE_STAT_UNTRACKED:
5769                         type = LINE_STAT_SECTION;
5770                         text = "Untracked files:";
5771                         break;
5773                 case LINE_STAT_NONE:
5774                         type = LINE_DEFAULT;
5775                         text = "  (no files)";
5776                         break;
5778                 case LINE_STAT_HEAD:
5779                         type = LINE_STAT_HEAD;
5780                         text = status_onbranch;
5781                         break;
5783                 default:
5784                         return FALSE;
5785                 }
5786         } else {
5787                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5789                 buf[0] = status->status;
5790                 if (draw_text(view, line->type, buf, TRUE))
5791                         return TRUE;
5792                 type = LINE_DEFAULT;
5793                 text = status->new.name;
5794         }
5796         draw_text(view, type, text, TRUE);
5797         return TRUE;
5800 static enum request
5801 status_load_error(struct view *view, struct view *stage, const char *path)
5803         if (displayed_views() == 2 || display[current_view] != view)
5804                 maximize_view(view);
5805         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5806         return REQ_NONE;
5809 static enum request
5810 status_enter(struct view *view, struct line *line)
5812         struct status *status = line->data;
5813         const char *oldpath = status ? status->old.name : NULL;
5814         /* Diffs for unmerged entries are empty when passing the new
5815          * path, so leave it empty. */
5816         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5817         const char *info;
5818         enum open_flags split;
5819         struct view *stage = VIEW(REQ_VIEW_STAGE);
5821         if (line->type == LINE_STAT_NONE ||
5822             (!status && line[1].type == LINE_STAT_NONE)) {
5823                 report("No file to diff");
5824                 return REQ_NONE;
5825         }
5827         switch (line->type) {
5828         case LINE_STAT_STAGED:
5829                 if (is_initial_commit()) {
5830                         const char *no_head_diff_argv[] = {
5831                                 "git", "diff", "--no-color", "--patch-with-stat",
5832                                         "--", "/dev/null", newpath, NULL
5833                         };
5835                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5836                                 return status_load_error(view, stage, newpath);
5837                 } else {
5838                         const char *index_show_argv[] = {
5839                                 "git", "diff-index", "--root", "--patch-with-stat",
5840                                         "-C", "-M", "--cached", "HEAD", "--",
5841                                         oldpath, newpath, NULL
5842                         };
5844                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5845                                 return status_load_error(view, stage, newpath);
5846                 }
5848                 if (status)
5849                         info = "Staged changes to %s";
5850                 else
5851                         info = "Staged changes";
5852                 break;
5854         case LINE_STAT_UNSTAGED:
5855         {
5856                 const char *files_show_argv[] = {
5857                         "git", "diff-files", "--root", "--patch-with-stat",
5858                                 "-C", "-M", "--", oldpath, newpath, NULL
5859                 };
5861                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5862                         return status_load_error(view, stage, newpath);
5863                 if (status)
5864                         info = "Unstaged changes to %s";
5865                 else
5866                         info = "Unstaged changes";
5867                 break;
5868         }
5869         case LINE_STAT_UNTRACKED:
5870                 if (!newpath) {
5871                         report("No file to show");
5872                         return REQ_NONE;
5873                 }
5875                 if (!suffixcmp(status->new.name, -1, "/")) {
5876                         report("Cannot display a directory");
5877                         return REQ_NONE;
5878                 }
5880                 if (!prepare_update_file(stage, newpath))
5881                         return status_load_error(view, stage, newpath);
5882                 info = "Untracked file %s";
5883                 break;
5885         case LINE_STAT_HEAD:
5886                 return REQ_NONE;
5888         default:
5889                 die("line type %d not handled in switch", line->type);
5890         }
5892         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5893         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5894         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5895                 if (status) {
5896                         stage_status = *status;
5897                 } else {
5898                         memset(&stage_status, 0, sizeof(stage_status));
5899                 }
5901                 stage_line_type = line->type;
5902                 stage_chunks = 0;
5903                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5904         }
5906         return REQ_NONE;
5909 static bool
5910 status_exists(struct status *status, enum line_type type)
5912         struct view *view = VIEW(REQ_VIEW_STATUS);
5913         unsigned long lineno;
5915         for (lineno = 0; lineno < view->lines; lineno++) {
5916                 struct line *line = &view->line[lineno];
5917                 struct status *pos = line->data;
5919                 if (line->type != type)
5920                         continue;
5921                 if (!pos && (!status || !status->status) && line[1].data) {
5922                         select_view_line(view, lineno);
5923                         return TRUE;
5924                 }
5925                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5926                         select_view_line(view, lineno);
5927                         return TRUE;
5928                 }
5929         }
5931         return FALSE;
5935 static bool
5936 status_update_prepare(struct io *io, enum line_type type)
5938         const char *staged_argv[] = {
5939                 "git", "update-index", "-z", "--index-info", NULL
5940         };
5941         const char *others_argv[] = {
5942                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5943         };
5945         switch (type) {
5946         case LINE_STAT_STAGED:
5947                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5949         case LINE_STAT_UNSTAGED:
5950         case LINE_STAT_UNTRACKED:
5951                 return io_run(io, others_argv, opt_cdup, IO_WR);
5953         default:
5954                 die("line type %d not handled in switch", type);
5955                 return FALSE;
5956         }
5959 static bool
5960 status_update_write(struct io *io, struct status *status, enum line_type type)
5962         char buf[SIZEOF_STR];
5963         size_t bufsize = 0;
5965         switch (type) {
5966         case LINE_STAT_STAGED:
5967                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5968                                         status->old.mode,
5969                                         status->old.rev,
5970                                         status->old.name, 0))
5971                         return FALSE;
5972                 break;
5974         case LINE_STAT_UNSTAGED:
5975         case LINE_STAT_UNTRACKED:
5976                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5977                         return FALSE;
5978                 break;
5980         default:
5981                 die("line type %d not handled in switch", type);
5982         }
5984         return io_write(io, buf, bufsize);
5987 static bool
5988 status_update_file(struct status *status, enum line_type type)
5990         struct io io = {};
5991         bool result;
5993         if (!status_update_prepare(&io, type))
5994                 return FALSE;
5996         result = status_update_write(&io, status, type);
5997         return io_done(&io) && result;
6000 static bool
6001 status_update_files(struct view *view, struct line *line)
6003         char buf[sizeof(view->ref)];
6004         struct io io = {};
6005         bool result = TRUE;
6006         struct line *pos = view->line + view->lines;
6007         int files = 0;
6008         int file, done;
6009         int cursor_y = -1, cursor_x = -1;
6011         if (!status_update_prepare(&io, line->type))
6012                 return FALSE;
6014         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6015                 files++;
6017         string_copy(buf, view->ref);
6018         getsyx(cursor_y, cursor_x);
6019         for (file = 0, done = 5; result && file < files; line++, file++) {
6020                 int almost_done = file * 100 / files;
6022                 if (almost_done > done) {
6023                         done = almost_done;
6024                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6025                                       file, files, done);
6026                         update_view_title(view);
6027                         setsyx(cursor_y, cursor_x);
6028                         doupdate();
6029                 }
6030                 result = status_update_write(&io, line->data, line->type);
6031         }
6032         string_copy(view->ref, buf);
6034         return io_done(&io) && result;
6037 static bool
6038 status_update(struct view *view)
6040         struct line *line = &view->line[view->lineno];
6042         assert(view->lines);
6044         if (!line->data) {
6045                 /* This should work even for the "On branch" line. */
6046                 if (line < view->line + view->lines && !line[1].data) {
6047                         report("Nothing to update");
6048                         return FALSE;
6049                 }
6051                 if (!status_update_files(view, line + 1)) {
6052                         report("Failed to update file status");
6053                         return FALSE;
6054                 }
6056         } else if (!status_update_file(line->data, line->type)) {
6057                 report("Failed to update file status");
6058                 return FALSE;
6059         }
6061         return TRUE;
6064 static bool
6065 status_revert(struct status *status, enum line_type type, bool has_none)
6067         if (!status || type != LINE_STAT_UNSTAGED) {
6068                 if (type == LINE_STAT_STAGED) {
6069                         report("Cannot revert changes to staged files");
6070                 } else if (type == LINE_STAT_UNTRACKED) {
6071                         report("Cannot revert changes to untracked files");
6072                 } else if (has_none) {
6073                         report("Nothing to revert");
6074                 } else {
6075                         report("Cannot revert changes to multiple files");
6076                 }
6078         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6079                 char mode[10] = "100644";
6080                 const char *reset_argv[] = {
6081                         "git", "update-index", "--cacheinfo", mode,
6082                                 status->old.rev, status->old.name, NULL
6083                 };
6084                 const char *checkout_argv[] = {
6085                         "git", "checkout", "--", status->old.name, NULL
6086                 };
6088                 if (status->status == 'U') {
6089                         string_format(mode, "%5o", status->old.mode);
6091                         if (status->old.mode == 0 && status->new.mode == 0) {
6092                                 reset_argv[2] = "--force-remove";
6093                                 reset_argv[3] = status->old.name;
6094                                 reset_argv[4] = NULL;
6095                         }
6097                         if (!io_run_fg(reset_argv, opt_cdup))
6098                                 return FALSE;
6099                         if (status->old.mode == 0 && status->new.mode == 0)
6100                                 return TRUE;
6101                 }
6103                 return io_run_fg(checkout_argv, opt_cdup);
6104         }
6106         return FALSE;
6109 static enum request
6110 status_request(struct view *view, enum request request, struct line *line)
6112         struct status *status = line->data;
6114         switch (request) {
6115         case REQ_STATUS_UPDATE:
6116                 if (!status_update(view))
6117                         return REQ_NONE;
6118                 break;
6120         case REQ_STATUS_REVERT:
6121                 if (!status_revert(status, line->type, status_has_none(view, line)))
6122                         return REQ_NONE;
6123                 break;
6125         case REQ_STATUS_MERGE:
6126                 if (!status || status->status != 'U') {
6127                         report("Merging only possible for files with unmerged status ('U').");
6128                         return REQ_NONE;
6129                 }
6130                 open_mergetool(status->new.name);
6131                 break;
6133         case REQ_EDIT:
6134                 if (!status)
6135                         return request;
6136                 if (status->status == 'D') {
6137                         report("File has been deleted.");
6138                         return REQ_NONE;
6139                 }
6141                 open_editor(status->new.name);
6142                 break;
6144         case REQ_VIEW_BLAME:
6145                 if (status)
6146                         opt_ref[0] = 0;
6147                 return request;
6149         case REQ_ENTER:
6150                 /* After returning the status view has been split to
6151                  * show the stage view. No further reloading is
6152                  * necessary. */
6153                 return status_enter(view, line);
6155         case REQ_REFRESH:
6156                 /* Simply reload the view. */
6157                 break;
6159         default:
6160                 return request;
6161         }
6163         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6165         return REQ_NONE;
6168 static void
6169 status_select(struct view *view, struct line *line)
6171         struct status *status = line->data;
6172         char file[SIZEOF_STR] = "all files";
6173         const char *text;
6174         const char *key;
6176         if (status && !string_format(file, "'%s'", status->new.name))
6177                 return;
6179         if (!status && line[1].type == LINE_STAT_NONE)
6180                 line++;
6182         switch (line->type) {
6183         case LINE_STAT_STAGED:
6184                 text = "Press %s to unstage %s for commit";
6185                 break;
6187         case LINE_STAT_UNSTAGED:
6188                 text = "Press %s to stage %s for commit";
6189                 break;
6191         case LINE_STAT_UNTRACKED:
6192                 text = "Press %s to stage %s for addition";
6193                 break;
6195         case LINE_STAT_HEAD:
6196         case LINE_STAT_NONE:
6197                 text = "Nothing to update";
6198                 break;
6200         default:
6201                 die("line type %d not handled in switch", line->type);
6202         }
6204         if (status && status->status == 'U') {
6205                 text = "Press %s to resolve conflict in %s";
6206                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6208         } else {
6209                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6210         }
6212         string_format(view->ref, text, key, file);
6213         if (status)
6214                 string_copy(opt_file, status->new.name);
6217 static bool
6218 status_grep(struct view *view, struct line *line)
6220         struct status *status = line->data;
6222         if (status) {
6223                 const char buf[2] = { status->status, 0 };
6224                 const char *text[] = { status->new.name, buf, NULL };
6226                 return grep_text(view, text);
6227         }
6229         return FALSE;
6232 static struct view_ops status_ops = {
6233         "file",
6234         NULL,
6235         status_open,
6236         NULL,
6237         status_draw,
6238         status_request,
6239         status_grep,
6240         status_select,
6241 };
6244 static bool
6245 stage_diff_write(struct io *io, struct line *line, struct line *end)
6247         while (line < end) {
6248                 if (!io_write(io, line->data, strlen(line->data)) ||
6249                     !io_write(io, "\n", 1))
6250                         return FALSE;
6251                 line++;
6252                 if (line->type == LINE_DIFF_CHUNK ||
6253                     line->type == LINE_DIFF_HEADER)
6254                         break;
6255         }
6257         return TRUE;
6260 static struct line *
6261 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6263         for (; view->line < line; line--)
6264                 if (line->type == type)
6265                         return line;
6267         return NULL;
6270 static bool
6271 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6273         const char *apply_argv[SIZEOF_ARG] = {
6274                 "git", "apply", "--whitespace=nowarn", NULL
6275         };
6276         struct line *diff_hdr;
6277         struct io io = {};
6278         int argc = 3;
6280         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6281         if (!diff_hdr)
6282                 return FALSE;
6284         if (!revert)
6285                 apply_argv[argc++] = "--cached";
6286         if (revert || stage_line_type == LINE_STAT_STAGED)
6287                 apply_argv[argc++] = "-R";
6288         apply_argv[argc++] = "-";
6289         apply_argv[argc++] = NULL;
6290         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6291                 return FALSE;
6293         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6294             !stage_diff_write(&io, chunk, view->line + view->lines))
6295                 chunk = NULL;
6297         io_done(&io);
6298         io_run_bg(update_index_argv);
6300         return chunk ? TRUE : FALSE;
6303 static bool
6304 stage_update(struct view *view, struct line *line)
6306         struct line *chunk = NULL;
6308         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6309                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6311         if (chunk) {
6312                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6313                         report("Failed to apply chunk");
6314                         return FALSE;
6315                 }
6317         } else if (!stage_status.status) {
6318                 view = VIEW(REQ_VIEW_STATUS);
6320                 for (line = view->line; line < view->line + view->lines; line++)
6321                         if (line->type == stage_line_type)
6322                                 break;
6324                 if (!status_update_files(view, line + 1)) {
6325                         report("Failed to update files");
6326                         return FALSE;
6327                 }
6329         } else if (!status_update_file(&stage_status, stage_line_type)) {
6330                 report("Failed to update file");
6331                 return FALSE;
6332         }
6334         return TRUE;
6337 static bool
6338 stage_revert(struct view *view, struct line *line)
6340         struct line *chunk = NULL;
6342         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6343                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6345         if (chunk) {
6346                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6347                         return FALSE;
6349                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6350                         report("Failed to revert chunk");
6351                         return FALSE;
6352                 }
6353                 return TRUE;
6355         } else {
6356                 return status_revert(stage_status.status ? &stage_status : NULL,
6357                                      stage_line_type, FALSE);
6358         }
6362 static void
6363 stage_next(struct view *view, struct line *line)
6365         int i;
6367         if (!stage_chunks) {
6368                 for (line = view->line; line < view->line + view->lines; line++) {
6369                         if (line->type != LINE_DIFF_CHUNK)
6370                                 continue;
6372                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6373                                 report("Allocation failure");
6374                                 return;
6375                         }
6377                         stage_chunk[stage_chunks++] = line - view->line;
6378                 }
6379         }
6381         for (i = 0; i < stage_chunks; i++) {
6382                 if (stage_chunk[i] > view->lineno) {
6383                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6384                         report("Chunk %d of %d", i + 1, stage_chunks);
6385                         return;
6386                 }
6387         }
6389         report("No next chunk found");
6392 static enum request
6393 stage_request(struct view *view, enum request request, struct line *line)
6395         switch (request) {
6396         case REQ_STATUS_UPDATE:
6397                 if (!stage_update(view, line))
6398                         return REQ_NONE;
6399                 break;
6401         case REQ_STATUS_REVERT:
6402                 if (!stage_revert(view, line))
6403                         return REQ_NONE;
6404                 break;
6406         case REQ_STAGE_NEXT:
6407                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6408                         report("File is untracked; press %s to add",
6409                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6410                         return REQ_NONE;
6411                 }
6412                 stage_next(view, line);
6413                 return REQ_NONE;
6415         case REQ_EDIT:
6416                 if (!stage_status.new.name[0])
6417                         return request;
6418                 if (stage_status.status == 'D') {
6419                         report("File has been deleted.");
6420                         return REQ_NONE;
6421                 }
6423                 open_editor(stage_status.new.name);
6424                 break;
6426         case REQ_REFRESH:
6427                 /* Reload everything ... */
6428                 break;
6430         case REQ_VIEW_BLAME:
6431                 if (stage_status.new.name[0]) {
6432                         string_copy(opt_file, stage_status.new.name);
6433                         opt_ref[0] = 0;
6434                 }
6435                 return request;
6437         case REQ_ENTER:
6438                 return pager_request(view, request, line);
6440         default:
6441                 return request;
6442         }
6444         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6445         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6447         /* Check whether the staged entry still exists, and close the
6448          * stage view if it doesn't. */
6449         if (!status_exists(&stage_status, stage_line_type)) {
6450                 status_restore(VIEW(REQ_VIEW_STATUS));
6451                 return REQ_VIEW_CLOSE;
6452         }
6454         if (stage_line_type == LINE_STAT_UNTRACKED) {
6455                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6456                         report("Cannot display a directory");
6457                         return REQ_NONE;
6458                 }
6460                 if (!prepare_update_file(view, stage_status.new.name)) {
6461                         report("Failed to open file: %s", strerror(errno));
6462                         return REQ_NONE;
6463                 }
6464         }
6465         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6467         return REQ_NONE;
6470 static struct view_ops stage_ops = {
6471         "line",
6472         NULL,
6473         NULL,
6474         pager_read,
6475         pager_draw,
6476         stage_request,
6477         pager_grep,
6478         pager_select,
6479 };
6482 /*
6483  * Revision graph
6484  */
6486 struct commit {
6487         char id[SIZEOF_REV];            /* SHA1 ID. */
6488         char title[128];                /* First line of the commit message. */
6489         const char *author;             /* Author of the commit. */
6490         struct time time;               /* Date from the author ident. */
6491         struct ref_list *refs;          /* Repository references. */
6492         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6493         size_t graph_size;              /* The width of the graph array. */
6494         bool has_parents;               /* Rewritten --parents seen. */
6495 };
6497 /* Size of rev graph with no  "padding" columns */
6498 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6500 struct rev_graph {
6501         struct rev_graph *prev, *next, *parents;
6502         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6503         size_t size;
6504         struct commit *commit;
6505         size_t pos;
6506         unsigned int boundary:1;
6507 };
6509 /* Parents of the commit being visualized. */
6510 static struct rev_graph graph_parents[4];
6512 /* The current stack of revisions on the graph. */
6513 static struct rev_graph graph_stacks[4] = {
6514         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6515         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6516         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6517         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6518 };
6520 static inline bool
6521 graph_parent_is_merge(struct rev_graph *graph)
6523         return graph->parents->size > 1;
6526 static inline void
6527 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6529         struct commit *commit = graph->commit;
6531         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6532                 commit->graph[commit->graph_size++] = symbol;
6535 static void
6536 clear_rev_graph(struct rev_graph *graph)
6538         graph->boundary = 0;
6539         graph->size = graph->pos = 0;
6540         graph->commit = NULL;
6541         memset(graph->parents, 0, sizeof(*graph->parents));
6544 static void
6545 done_rev_graph(struct rev_graph *graph)
6547         if (graph_parent_is_merge(graph) &&
6548             graph->pos < graph->size - 1 &&
6549             graph->next->size == graph->size + graph->parents->size - 1) {
6550                 size_t i = graph->pos + graph->parents->size - 1;
6552                 graph->commit->graph_size = i * 2;
6553                 while (i < graph->next->size - 1) {
6554                         append_to_rev_graph(graph, ' ');
6555                         append_to_rev_graph(graph, '\\');
6556                         i++;
6557                 }
6558         }
6560         clear_rev_graph(graph);
6563 static void
6564 push_rev_graph(struct rev_graph *graph, const char *parent)
6566         int i;
6568         /* "Collapse" duplicate parents lines.
6569          *
6570          * FIXME: This needs to also update update the drawn graph but
6571          * for now it just serves as a method for pruning graph lines. */
6572         for (i = 0; i < graph->size; i++)
6573                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6574                         return;
6576         if (graph->size < SIZEOF_REVITEMS) {
6577                 string_copy_rev(graph->rev[graph->size++], parent);
6578         }
6581 static chtype
6582 get_rev_graph_symbol(struct rev_graph *graph)
6584         chtype symbol;
6586         if (graph->boundary)
6587                 symbol = REVGRAPH_BOUND;
6588         else if (graph->parents->size == 0)
6589                 symbol = REVGRAPH_INIT;
6590         else if (graph_parent_is_merge(graph))
6591                 symbol = REVGRAPH_MERGE;
6592         else if (graph->pos >= graph->size)
6593                 symbol = REVGRAPH_BRANCH;
6594         else
6595                 symbol = REVGRAPH_COMMIT;
6597         return symbol;
6600 static void
6601 draw_rev_graph(struct rev_graph *graph)
6603         struct rev_filler {
6604                 chtype separator, line;
6605         };
6606         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6607         static struct rev_filler fillers[] = {
6608                 { ' ',  '|' },
6609                 { '`',  '.' },
6610                 { '\'', ' ' },
6611                 { '/',  ' ' },
6612         };
6613         chtype symbol = get_rev_graph_symbol(graph);
6614         struct rev_filler *filler;
6615         size_t i;
6617         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6618         filler = &fillers[DEFAULT];
6620         for (i = 0; i < graph->pos; i++) {
6621                 append_to_rev_graph(graph, filler->line);
6622                 if (graph_parent_is_merge(graph->prev) &&
6623                     graph->prev->pos == i)
6624                         filler = &fillers[RSHARP];
6626                 append_to_rev_graph(graph, filler->separator);
6627         }
6629         /* Place the symbol for this revision. */
6630         append_to_rev_graph(graph, symbol);
6632         if (graph->prev->size > graph->size)
6633                 filler = &fillers[RDIAG];
6634         else
6635                 filler = &fillers[DEFAULT];
6637         i++;
6639         for (; i < graph->size; i++) {
6640                 append_to_rev_graph(graph, filler->separator);
6641                 append_to_rev_graph(graph, filler->line);
6642                 if (graph_parent_is_merge(graph->prev) &&
6643                     i < graph->prev->pos + graph->parents->size)
6644                         filler = &fillers[RSHARP];
6645                 if (graph->prev->size > graph->size)
6646                         filler = &fillers[LDIAG];
6647         }
6649         if (graph->prev->size > graph->size) {
6650                 append_to_rev_graph(graph, filler->separator);
6651                 if (filler->line != ' ')
6652                         append_to_rev_graph(graph, filler->line);
6653         }
6656 /* Prepare the next rev graph */
6657 static void
6658 prepare_rev_graph(struct rev_graph *graph)
6660         size_t i;
6662         /* First, traverse all lines of revisions up to the active one. */
6663         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6664                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6665                         break;
6667                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6668         }
6670         /* Interleave the new revision parent(s). */
6671         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6672                 push_rev_graph(graph->next, graph->parents->rev[i]);
6674         /* Lastly, put any remaining revisions. */
6675         for (i = graph->pos + 1; i < graph->size; i++)
6676                 push_rev_graph(graph->next, graph->rev[i]);
6679 static void
6680 update_rev_graph(struct view *view, struct rev_graph *graph)
6682         /* If this is the finalizing update ... */
6683         if (graph->commit)
6684                 prepare_rev_graph(graph);
6686         /* Graph visualization needs a one rev look-ahead,
6687          * so the first update doesn't visualize anything. */
6688         if (!graph->prev->commit)
6689                 return;
6691         if (view->lines > 2)
6692                 view->line[view->lines - 3].dirty = 1;
6693         if (view->lines > 1)
6694                 view->line[view->lines - 2].dirty = 1;
6695         draw_rev_graph(graph->prev);
6696         done_rev_graph(graph->prev->prev);
6700 /*
6701  * Main view backend
6702  */
6704 static const char *main_argv[SIZEOF_ARG] = {
6705         "git", "log", "--no-color", "--pretty=raw", "--parents",
6706                       "--topo-order", "%(head)", NULL
6707 };
6709 static bool
6710 main_draw(struct view *view, struct line *line, unsigned int lineno)
6712         struct commit *commit = line->data;
6714         if (!commit->author)
6715                 return FALSE;
6717         if (opt_date && draw_date(view, &commit->time))
6718                 return TRUE;
6720         if (opt_author && draw_author(view, commit->author))
6721                 return TRUE;
6723         if (opt_rev_graph && commit->graph_size &&
6724             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6725                 return TRUE;
6727         if (opt_show_refs && commit->refs) {
6728                 size_t i;
6730                 for (i = 0; i < commit->refs->size; i++) {
6731                         struct ref *ref = commit->refs->refs[i];
6732                         enum line_type type;
6734                         if (ref->head)
6735                                 type = LINE_MAIN_HEAD;
6736                         else if (ref->ltag)
6737                                 type = LINE_MAIN_LOCAL_TAG;
6738                         else if (ref->tag)
6739                                 type = LINE_MAIN_TAG;
6740                         else if (ref->tracked)
6741                                 type = LINE_MAIN_TRACKED;
6742                         else if (ref->remote)
6743                                 type = LINE_MAIN_REMOTE;
6744                         else
6745                                 type = LINE_MAIN_REF;
6747                         if (draw_text(view, type, "[", TRUE) ||
6748                             draw_text(view, type, ref->name, TRUE) ||
6749                             draw_text(view, type, "]", TRUE))
6750                                 return TRUE;
6752                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6753                                 return TRUE;
6754                 }
6755         }
6757         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6758         return TRUE;
6761 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6762 static bool
6763 main_read(struct view *view, char *line)
6765         static struct rev_graph *graph = graph_stacks;
6766         enum line_type type;
6767         struct commit *commit;
6769         if (!line) {
6770                 int i;
6772                 if (!view->lines && !view->parent)
6773                         die("No revisions match the given arguments.");
6774                 if (view->lines > 0) {
6775                         commit = view->line[view->lines - 1].data;
6776                         view->line[view->lines - 1].dirty = 1;
6777                         if (!commit->author) {
6778                                 view->lines--;
6779                                 free(commit);
6780                                 graph->commit = NULL;
6781                         }
6782                 }
6783                 update_rev_graph(view, graph);
6785                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6786                         clear_rev_graph(&graph_stacks[i]);
6787                 return TRUE;
6788         }
6790         type = get_line_type(line);
6791         if (type == LINE_COMMIT) {
6792                 commit = calloc(1, sizeof(struct commit));
6793                 if (!commit)
6794                         return FALSE;
6796                 line += STRING_SIZE("commit ");
6797                 if (*line == '-') {
6798                         graph->boundary = 1;
6799                         line++;
6800                 }
6802                 string_copy_rev(commit->id, line);
6803                 commit->refs = get_ref_list(commit->id);
6804                 graph->commit = commit;
6805                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6807                 while ((line = strchr(line, ' '))) {
6808                         line++;
6809                         push_rev_graph(graph->parents, line);
6810                         commit->has_parents = TRUE;
6811                 }
6812                 return TRUE;
6813         }
6815         if (!view->lines)
6816                 return TRUE;
6817         commit = view->line[view->lines - 1].data;
6819         switch (type) {
6820         case LINE_PARENT:
6821                 if (commit->has_parents)
6822                         break;
6823                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6824                 break;
6826         case LINE_AUTHOR:
6827                 parse_author_line(line + STRING_SIZE("author "),
6828                                   &commit->author, &commit->time);
6829                 update_rev_graph(view, graph);
6830                 graph = graph->next;
6831                 break;
6833         default:
6834                 /* Fill in the commit title if it has not already been set. */
6835                 if (commit->title[0])
6836                         break;
6838                 /* Require titles to start with a non-space character at the
6839                  * offset used by git log. */
6840                 if (strncmp(line, "    ", 4))
6841                         break;
6842                 line += 4;
6843                 /* Well, if the title starts with a whitespace character,
6844                  * try to be forgiving.  Otherwise we end up with no title. */
6845                 while (isspace(*line))
6846                         line++;
6847                 if (*line == '\0')
6848                         break;
6849                 /* FIXME: More graceful handling of titles; append "..." to
6850                  * shortened titles, etc. */
6852                 string_expand(commit->title, sizeof(commit->title), line, 1);
6853                 view->line[view->lines - 1].dirty = 1;
6854         }
6856         return TRUE;
6859 static enum request
6860 main_request(struct view *view, enum request request, struct line *line)
6862         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6864         switch (request) {
6865         case REQ_ENTER:
6866                 open_view(view, REQ_VIEW_DIFF, flags);
6867                 break;
6868         case REQ_REFRESH:
6869                 load_refs();
6870                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6871                 break;
6872         default:
6873                 return request;
6874         }
6876         return REQ_NONE;
6879 static bool
6880 grep_refs(struct ref_list *list, regex_t *regex)
6882         regmatch_t pmatch;
6883         size_t i;
6885         if (!opt_show_refs || !list)
6886                 return FALSE;
6888         for (i = 0; i < list->size; i++) {
6889                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6890                         return TRUE;
6891         }
6893         return FALSE;
6896 static bool
6897 main_grep(struct view *view, struct line *line)
6899         struct commit *commit = line->data;
6900         const char *text[] = {
6901                 commit->title,
6902                 opt_author ? commit->author : "",
6903                 mkdate(&commit->time, opt_date),
6904                 NULL
6905         };
6907         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6910 static void
6911 main_select(struct view *view, struct line *line)
6913         struct commit *commit = line->data;
6915         string_copy_rev(view->ref, commit->id);
6916         string_copy_rev(ref_commit, view->ref);
6919 static struct view_ops main_ops = {
6920         "commit",
6921         main_argv,
6922         NULL,
6923         main_read,
6924         main_draw,
6925         main_request,
6926         main_grep,
6927         main_select,
6928 };
6931 /*
6932  * Status management
6933  */
6935 /* Whether or not the curses interface has been initialized. */
6936 static bool cursed = FALSE;
6938 /* Terminal hacks and workarounds. */
6939 static bool use_scroll_redrawwin;
6940 static bool use_scroll_status_wclear;
6942 /* The status window is used for polling keystrokes. */
6943 static WINDOW *status_win;
6945 /* Reading from the prompt? */
6946 static bool input_mode = FALSE;
6948 static bool status_empty = FALSE;
6950 /* Update status and title window. */
6951 static void
6952 report(const char *msg, ...)
6954         struct view *view = display[current_view];
6956         if (input_mode)
6957                 return;
6959         if (!view) {
6960                 char buf[SIZEOF_STR];
6961                 va_list args;
6963                 va_start(args, msg);
6964                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6965                         buf[sizeof(buf) - 1] = 0;
6966                         buf[sizeof(buf) - 2] = '.';
6967                         buf[sizeof(buf) - 3] = '.';
6968                         buf[sizeof(buf) - 4] = '.';
6969                 }
6970                 va_end(args);
6971                 die("%s", buf);
6972         }
6974         if (!status_empty || *msg) {
6975                 va_list args;
6977                 va_start(args, msg);
6979                 wmove(status_win, 0, 0);
6980                 if (view->has_scrolled && use_scroll_status_wclear)
6981                         wclear(status_win);
6982                 if (*msg) {
6983                         vwprintw(status_win, msg, args);
6984                         status_empty = FALSE;
6985                 } else {
6986                         status_empty = TRUE;
6987                 }
6988                 wclrtoeol(status_win);
6989                 wnoutrefresh(status_win);
6991                 va_end(args);
6992         }
6994         update_view_title(view);
6997 static void
6998 init_display(void)
7000         const char *term;
7001         int x, y;
7003         /* Initialize the curses library */
7004         if (isatty(STDIN_FILENO)) {
7005                 cursed = !!initscr();
7006                 opt_tty = stdin;
7007         } else {
7008                 /* Leave stdin and stdout alone when acting as a pager. */
7009                 opt_tty = fopen("/dev/tty", "r+");
7010                 if (!opt_tty)
7011                         die("Failed to open /dev/tty");
7012                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7013         }
7015         if (!cursed)
7016                 die("Failed to initialize curses");
7018         nonl();         /* Disable conversion and detect newlines from input. */
7019         cbreak();       /* Take input chars one at a time, no wait for \n */
7020         noecho();       /* Don't echo input */
7021         leaveok(stdscr, FALSE);
7023         if (has_colors())
7024                 init_colors();
7026         getmaxyx(stdscr, y, x);
7027         status_win = newwin(1, 0, y - 1, 0);
7028         if (!status_win)
7029                 die("Failed to create status window");
7031         /* Enable keyboard mapping */
7032         keypad(status_win, TRUE);
7033         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7035         TABSIZE = opt_tab_size;
7037         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7038         if (term && !strcmp(term, "gnome-terminal")) {
7039                 /* In the gnome-terminal-emulator, the message from
7040                  * scrolling up one line when impossible followed by
7041                  * scrolling down one line causes corruption of the
7042                  * status line. This is fixed by calling wclear. */
7043                 use_scroll_status_wclear = TRUE;
7044                 use_scroll_redrawwin = FALSE;
7046         } else if (term && !strcmp(term, "xrvt-xpm")) {
7047                 /* No problems with full optimizations in xrvt-(unicode)
7048                  * and aterm. */
7049                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7051         } else {
7052                 /* When scrolling in (u)xterm the last line in the
7053                  * scrolling direction will update slowly. */
7054                 use_scroll_redrawwin = TRUE;
7055                 use_scroll_status_wclear = FALSE;
7056         }
7059 static int
7060 get_input(int prompt_position)
7062         struct view *view;
7063         int i, key, cursor_y, cursor_x;
7064         bool loading = FALSE;
7066         if (prompt_position)
7067                 input_mode = TRUE;
7069         while (TRUE) {
7070                 foreach_view (view, i) {
7071                         update_view(view);
7072                         if (view_is_displayed(view) && view->has_scrolled &&
7073                             use_scroll_redrawwin)
7074                                 redrawwin(view->win);
7075                         view->has_scrolled = FALSE;
7076                         if (view->pipe)
7077                                 loading = TRUE;
7078                 }
7080                 /* Update the cursor position. */
7081                 if (prompt_position) {
7082                         getbegyx(status_win, cursor_y, cursor_x);
7083                         cursor_x = prompt_position;
7084                 } else {
7085                         view = display[current_view];
7086                         getbegyx(view->win, cursor_y, cursor_x);
7087                         cursor_x = view->width - 1;
7088                         cursor_y += view->lineno - view->offset;
7089                 }
7090                 setsyx(cursor_y, cursor_x);
7092                 /* Refresh, accept single keystroke of input */
7093                 doupdate();
7094                 nodelay(status_win, loading);
7095                 key = wgetch(status_win);
7097                 /* wgetch() with nodelay() enabled returns ERR when
7098                  * there's no input. */
7099                 if (key == ERR) {
7101                 } else if (key == KEY_RESIZE) {
7102                         int height, width;
7104                         getmaxyx(stdscr, height, width);
7106                         wresize(status_win, 1, width);
7107                         mvwin(status_win, height - 1, 0);
7108                         wnoutrefresh(status_win);
7109                         resize_display();
7110                         redraw_display(TRUE);
7112                 } else {
7113                         input_mode = FALSE;
7114                         return key;
7115                 }
7116         }
7119 static char *
7120 prompt_input(const char *prompt, input_handler handler, void *data)
7122         enum input_status status = INPUT_OK;
7123         static char buf[SIZEOF_STR];
7124         size_t pos = 0;
7126         buf[pos] = 0;
7128         while (status == INPUT_OK || status == INPUT_SKIP) {
7129                 int key;
7131                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7132                 wclrtoeol(status_win);
7134                 key = get_input(pos + 1);
7135                 switch (key) {
7136                 case KEY_RETURN:
7137                 case KEY_ENTER:
7138                 case '\n':
7139                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7140                         break;
7142                 case KEY_BACKSPACE:
7143                         if (pos > 0)
7144                                 buf[--pos] = 0;
7145                         else
7146                                 status = INPUT_CANCEL;
7147                         break;
7149                 case KEY_ESC:
7150                         status = INPUT_CANCEL;
7151                         break;
7153                 default:
7154                         if (pos >= sizeof(buf)) {
7155                                 report("Input string too long");
7156                                 return NULL;
7157                         }
7159                         status = handler(data, buf, key);
7160                         if (status == INPUT_OK)
7161                                 buf[pos++] = (char) key;
7162                 }
7163         }
7165         /* Clear the status window */
7166         status_empty = FALSE;
7167         report("");
7169         if (status == INPUT_CANCEL)
7170                 return NULL;
7172         buf[pos++] = 0;
7174         return buf;
7177 static enum input_status
7178 prompt_yesno_handler(void *data, char *buf, int c)
7180         if (c == 'y' || c == 'Y')
7181                 return INPUT_STOP;
7182         if (c == 'n' || c == 'N')
7183                 return INPUT_CANCEL;
7184         return INPUT_SKIP;
7187 static bool
7188 prompt_yesno(const char *prompt)
7190         char prompt2[SIZEOF_STR];
7192         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7193                 return FALSE;
7195         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7198 static enum input_status
7199 read_prompt_handler(void *data, char *buf, int c)
7201         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7204 static char *
7205 read_prompt(const char *prompt)
7207         return prompt_input(prompt, read_prompt_handler, NULL);
7210 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7212         enum input_status status = INPUT_OK;
7213         int size = 0;
7215         while (items[size].text)
7216                 size++;
7218         while (status == INPUT_OK) {
7219                 const struct menu_item *item = &items[*selected];
7220                 int key;
7221                 int i;
7223                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7224                           prompt, *selected + 1, size);
7225                 if (item->hotkey)
7226                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7227                 wprintw(status_win, "%s", item->text);
7228                 wclrtoeol(status_win);
7230                 key = get_input(COLS - 1);
7231                 switch (key) {
7232                 case KEY_RETURN:
7233                 case KEY_ENTER:
7234                 case '\n':
7235                         status = INPUT_STOP;
7236                         break;
7238                 case KEY_LEFT:
7239                 case KEY_UP:
7240                         *selected = *selected - 1;
7241                         if (*selected < 0)
7242                                 *selected = size - 1;
7243                         break;
7245                 case KEY_RIGHT:
7246                 case KEY_DOWN:
7247                         *selected = (*selected + 1) % size;
7248                         break;
7250                 case KEY_ESC:
7251                         status = INPUT_CANCEL;
7252                         break;
7254                 default:
7255                         for (i = 0; items[i].text; i++)
7256                                 if (items[i].hotkey == key) {
7257                                         *selected = i;
7258                                         status = INPUT_STOP;
7259                                         break;
7260                                 }
7261                 }
7262         }
7264         /* Clear the status window */
7265         status_empty = FALSE;
7266         report("");
7268         return status != INPUT_CANCEL;
7271 /*
7272  * Repository properties
7273  */
7275 static struct ref **refs = NULL;
7276 static size_t refs_size = 0;
7277 static struct ref *refs_head = NULL;
7279 static struct ref_list **ref_lists = NULL;
7280 static size_t ref_lists_size = 0;
7282 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7283 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7284 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7286 static int
7287 compare_refs(const void *ref1_, const void *ref2_)
7289         const struct ref *ref1 = *(const struct ref **)ref1_;
7290         const struct ref *ref2 = *(const struct ref **)ref2_;
7292         if (ref1->tag != ref2->tag)
7293                 return ref2->tag - ref1->tag;
7294         if (ref1->ltag != ref2->ltag)
7295                 return ref2->ltag - ref2->ltag;
7296         if (ref1->head != ref2->head)
7297                 return ref2->head - ref1->head;
7298         if (ref1->tracked != ref2->tracked)
7299                 return ref2->tracked - ref1->tracked;
7300         if (ref1->remote != ref2->remote)
7301                 return ref2->remote - ref1->remote;
7302         return strcmp(ref1->name, ref2->name);
7305 static void
7306 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7308         size_t i;
7310         for (i = 0; i < refs_size; i++)
7311                 if (!visitor(data, refs[i]))
7312                         break;
7315 static struct ref *
7316 get_ref_head()
7318         return refs_head;
7321 static struct ref_list *
7322 get_ref_list(const char *id)
7324         struct ref_list *list;
7325         size_t i;
7327         for (i = 0; i < ref_lists_size; i++)
7328                 if (!strcmp(id, ref_lists[i]->id))
7329                         return ref_lists[i];
7331         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7332                 return NULL;
7333         list = calloc(1, sizeof(*list));
7334         if (!list)
7335                 return NULL;
7337         for (i = 0; i < refs_size; i++) {
7338                 if (!strcmp(id, refs[i]->id) &&
7339                     realloc_refs_list(&list->refs, list->size, 1))
7340                         list->refs[list->size++] = refs[i];
7341         }
7343         if (!list->refs) {
7344                 free(list);
7345                 return NULL;
7346         }
7348         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7349         ref_lists[ref_lists_size++] = list;
7350         return list;
7353 static int
7354 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7356         struct ref *ref = NULL;
7357         bool tag = FALSE;
7358         bool ltag = FALSE;
7359         bool remote = FALSE;
7360         bool tracked = FALSE;
7361         bool head = FALSE;
7362         int from = 0, to = refs_size - 1;
7364         if (!prefixcmp(name, "refs/tags/")) {
7365                 if (!suffixcmp(name, namelen, "^{}")) {
7366                         namelen -= 3;
7367                         name[namelen] = 0;
7368                 } else {
7369                         ltag = TRUE;
7370                 }
7372                 tag = TRUE;
7373                 namelen -= STRING_SIZE("refs/tags/");
7374                 name    += STRING_SIZE("refs/tags/");
7376         } else if (!prefixcmp(name, "refs/remotes/")) {
7377                 remote = TRUE;
7378                 namelen -= STRING_SIZE("refs/remotes/");
7379                 name    += STRING_SIZE("refs/remotes/");
7380                 tracked  = !strcmp(opt_remote, name);
7382         } else if (!prefixcmp(name, "refs/heads/")) {
7383                 namelen -= STRING_SIZE("refs/heads/");
7384                 name    += STRING_SIZE("refs/heads/");
7385                 if (!strncmp(opt_head, name, namelen))
7386                         return OK;
7388         } else if (!strcmp(name, "HEAD")) {
7389                 head     = TRUE;
7390                 if (*opt_head) {
7391                         namelen  = strlen(opt_head);
7392                         name     = opt_head;
7393                 }
7394         }
7396         /* If we are reloading or it's an annotated tag, replace the
7397          * previous SHA1 with the resolved commit id; relies on the fact
7398          * git-ls-remote lists the commit id of an annotated tag right
7399          * before the commit id it points to. */
7400         while (from <= to) {
7401                 size_t pos = (to + from) / 2;
7402                 int cmp = strcmp(name, refs[pos]->name);
7404                 if (!cmp) {
7405                         ref = refs[pos];
7406                         break;
7407                 }
7409                 if (cmp < 0)
7410                         to = pos - 1;
7411                 else
7412                         from = pos + 1;
7413         }
7415         if (!ref) {
7416                 if (!realloc_refs(&refs, refs_size, 1))
7417                         return ERR;
7418                 ref = calloc(1, sizeof(*ref) + namelen);
7419                 if (!ref)
7420                         return ERR;
7421                 memmove(refs + from + 1, refs + from,
7422                         (refs_size - from) * sizeof(*refs));
7423                 refs[from] = ref;
7424                 strncpy(ref->name, name, namelen);
7425                 refs_size++;
7426         }
7428         ref->head = head;
7429         ref->tag = tag;
7430         ref->ltag = ltag;
7431         ref->remote = remote;
7432         ref->tracked = tracked;
7433         string_copy_rev(ref->id, id);
7435         if (head)
7436                 refs_head = ref;
7437         return OK;
7440 static int
7441 load_refs(void)
7443         const char *head_argv[] = {
7444                 "git", "symbolic-ref", "HEAD", NULL
7445         };
7446         static const char *ls_remote_argv[SIZEOF_ARG] = {
7447                 "git", "ls-remote", opt_git_dir, NULL
7448         };
7449         static bool init = FALSE;
7450         size_t i;
7452         if (!init) {
7453                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7454                         die("TIG_LS_REMOTE contains too many arguments");
7455                 init = TRUE;
7456         }
7458         if (!*opt_git_dir)
7459                 return OK;
7461         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7462             !prefixcmp(opt_head, "refs/heads/")) {
7463                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7465                 memmove(opt_head, offset, strlen(offset) + 1);
7466         }
7468         refs_head = NULL;
7469         for (i = 0; i < refs_size; i++)
7470                 refs[i]->id[0] = 0;
7472         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7473                 return ERR;
7475         /* Update the ref lists to reflect changes. */
7476         for (i = 0; i < ref_lists_size; i++) {
7477                 struct ref_list *list = ref_lists[i];
7478                 size_t old, new;
7480                 for (old = new = 0; old < list->size; old++)
7481                         if (!strcmp(list->id, list->refs[old]->id))
7482                                 list->refs[new++] = list->refs[old];
7483                 list->size = new;
7484         }
7486         return OK;
7489 static void
7490 set_remote_branch(const char *name, const char *value, size_t valuelen)
7492         if (!strcmp(name, ".remote")) {
7493                 string_ncopy(opt_remote, value, valuelen);
7495         } else if (*opt_remote && !strcmp(name, ".merge")) {
7496                 size_t from = strlen(opt_remote);
7498                 if (!prefixcmp(value, "refs/heads/"))
7499                         value += STRING_SIZE("refs/heads/");
7501                 if (!string_format_from(opt_remote, &from, "/%s", value))
7502                         opt_remote[0] = 0;
7503         }
7506 static void
7507 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7509         const char *argv[SIZEOF_ARG] = { name, "=" };
7510         int argc = 1 + (cmd == option_set_command);
7511         int error = ERR;
7513         if (!argv_from_string(argv, &argc, value))
7514                 config_msg = "Too many option arguments";
7515         else
7516                 error = cmd(argc, argv);
7518         if (error == ERR)
7519                 warn("Option 'tig.%s': %s", name, config_msg);
7522 static bool
7523 set_environment_variable(const char *name, const char *value)
7525         size_t len = strlen(name) + 1 + strlen(value) + 1;
7526         char *env = malloc(len);
7528         if (env &&
7529             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7530             putenv(env) == 0)
7531                 return TRUE;
7532         free(env);
7533         return FALSE;
7536 static void
7537 set_work_tree(const char *value)
7539         char cwd[SIZEOF_STR];
7541         if (!getcwd(cwd, sizeof(cwd)))
7542                 die("Failed to get cwd path: %s", strerror(errno));
7543         if (chdir(opt_git_dir) < 0)
7544                 die("Failed to chdir(%s): %s", strerror(errno));
7545         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7546                 die("Failed to get git path: %s", strerror(errno));
7547         if (chdir(cwd) < 0)
7548                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7549         if (chdir(value) < 0)
7550                 die("Failed to chdir(%s): %s", value, strerror(errno));
7551         if (!getcwd(cwd, sizeof(cwd)))
7552                 die("Failed to get cwd path: %s", strerror(errno));
7553         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7554                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7555         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7556                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7557         opt_is_inside_work_tree = TRUE;
7560 static int
7561 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7563         if (!strcmp(name, "i18n.commitencoding"))
7564                 string_ncopy(opt_encoding, value, valuelen);
7566         else if (!strcmp(name, "core.editor"))
7567                 string_ncopy(opt_editor, value, valuelen);
7569         else if (!strcmp(name, "core.worktree"))
7570                 set_work_tree(value);
7572         else if (!prefixcmp(name, "tig.color."))
7573                 set_repo_config_option(name + 10, value, option_color_command);
7575         else if (!prefixcmp(name, "tig.bind."))
7576                 set_repo_config_option(name + 9, value, option_bind_command);
7578         else if (!prefixcmp(name, "tig."))
7579                 set_repo_config_option(name + 4, value, option_set_command);
7581         else if (*opt_head && !prefixcmp(name, "branch.") &&
7582                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7583                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7585         return OK;
7588 static int
7589 load_git_config(void)
7591         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7593         return io_run_load(config_list_argv, "=", read_repo_config_option);
7596 static int
7597 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7599         if (!opt_git_dir[0]) {
7600                 string_ncopy(opt_git_dir, name, namelen);
7602         } else if (opt_is_inside_work_tree == -1) {
7603                 /* This can be 3 different values depending on the
7604                  * version of git being used. If git-rev-parse does not
7605                  * understand --is-inside-work-tree it will simply echo
7606                  * the option else either "true" or "false" is printed.
7607                  * Default to true for the unknown case. */
7608                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7610         } else if (*name == '.') {
7611                 string_ncopy(opt_cdup, name, namelen);
7613         } else {
7614                 string_ncopy(opt_prefix, name, namelen);
7615         }
7617         return OK;
7620 static int
7621 load_repo_info(void)
7623         const char *rev_parse_argv[] = {
7624                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7625                         "--show-cdup", "--show-prefix", NULL
7626         };
7628         return io_run_load(rev_parse_argv, "=", read_repo_info);
7632 /*
7633  * Main
7634  */
7636 static const char usage[] =
7637 "tig " TIG_VERSION " (" __DATE__ ")\n"
7638 "\n"
7639 "Usage: tig        [options] [revs] [--] [paths]\n"
7640 "   or: tig show   [options] [revs] [--] [paths]\n"
7641 "   or: tig blame  [rev] path\n"
7642 "   or: tig status\n"
7643 "   or: tig <      [git command output]\n"
7644 "\n"
7645 "Options:\n"
7646 "  -v, --version   Show version and exit\n"
7647 "  -h, --help      Show help message and exit";
7649 static void __NORETURN
7650 quit(int sig)
7652         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7653         if (cursed)
7654                 endwin();
7655         exit(0);
7658 static void __NORETURN
7659 die(const char *err, ...)
7661         va_list args;
7663         endwin();
7665         va_start(args, err);
7666         fputs("tig: ", stderr);
7667         vfprintf(stderr, err, args);
7668         fputs("\n", stderr);
7669         va_end(args);
7671         exit(1);
7674 static void
7675 warn(const char *msg, ...)
7677         va_list args;
7679         va_start(args, msg);
7680         fputs("tig warning: ", stderr);
7681         vfprintf(stderr, msg, args);
7682         fputs("\n", stderr);
7683         va_end(args);
7686 static enum request
7687 parse_options(int argc, const char *argv[])
7689         enum request request = REQ_VIEW_MAIN;
7690         const char *subcommand;
7691         bool seen_dashdash = FALSE;
7692         /* XXX: This is vulnerable to the user overriding options
7693          * required for the main view parser. */
7694         const char *custom_argv[SIZEOF_ARG] = {
7695                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7696                         "--topo-order", NULL
7697         };
7698         int i, j = 6;
7700         if (!isatty(STDIN_FILENO)) {
7701                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7702                 return REQ_VIEW_PAGER;
7703         }
7705         if (argc <= 1)
7706                 return REQ_NONE;
7708         subcommand = argv[1];
7709         if (!strcmp(subcommand, "status")) {
7710                 if (argc > 2)
7711                         warn("ignoring arguments after `%s'", subcommand);
7712                 return REQ_VIEW_STATUS;
7714         } else if (!strcmp(subcommand, "blame")) {
7715                 if (argc <= 2 || argc > 4)
7716                         die("invalid number of options to blame\n\n%s", usage);
7718                 i = 2;
7719                 if (argc == 4) {
7720                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7721                         i++;
7722                 }
7724                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7725                 return REQ_VIEW_BLAME;
7727         } else if (!strcmp(subcommand, "show")) {
7728                 request = REQ_VIEW_DIFF;
7730         } else {
7731                 subcommand = NULL;
7732         }
7734         if (subcommand) {
7735                 custom_argv[1] = subcommand;
7736                 j = 2;
7737         }
7739         for (i = 1 + !!subcommand; i < argc; i++) {
7740                 const char *opt = argv[i];
7742                 if (seen_dashdash || !strcmp(opt, "--")) {
7743                         seen_dashdash = TRUE;
7745                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7746                         printf("tig version %s\n", TIG_VERSION);
7747                         quit(0);
7749                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7750                         printf("%s\n", usage);
7751                         quit(0);
7752                 }
7754                 custom_argv[j++] = opt;
7755                 if (j >= ARRAY_SIZE(custom_argv))
7756                         die("command too long");
7757         }
7759         if (!prepare_update(VIEW(request), custom_argv, NULL))
7760                 die("Failed to format arguments");
7762         return request;
7765 int
7766 main(int argc, const char *argv[])
7768         const char *codeset = "UTF-8";
7769         enum request request = parse_options(argc, argv);
7770         struct view *view;
7771         size_t i;
7773         signal(SIGINT, quit);
7774         signal(SIGPIPE, SIG_IGN);
7776         if (setlocale(LC_ALL, "")) {
7777                 codeset = nl_langinfo(CODESET);
7778         }
7780         if (load_repo_info() == ERR)
7781                 die("Failed to load repo info.");
7783         if (load_options() == ERR)
7784                 die("Failed to load user config.");
7786         if (load_git_config() == ERR)
7787                 die("Failed to load repo config.");
7789         /* Require a git repository unless when running in pager mode. */
7790         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7791                 die("Not a git repository");
7793         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7794                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7795                 if (opt_iconv_in == ICONV_NONE)
7796                         die("Failed to initialize character set conversion");
7797         }
7799         if (codeset && strcmp(codeset, "UTF-8")) {
7800                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7801                 if (opt_iconv_out == ICONV_NONE)
7802                         die("Failed to initialize character set conversion");
7803         }
7805         if (load_refs() == ERR)
7806                 die("Failed to load refs.");
7808         foreach_view (view, i)
7809                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7810                         die("Too many arguments in the `%s` environment variable",
7811                             view->cmd_env);
7813         init_display();
7815         if (request != REQ_NONE)
7816                 open_view(NULL, request, OPEN_PREPARED);
7817         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7819         while (view_driver(display[current_view], request)) {
7820                 int key = get_input(0);
7822                 view = display[current_view];
7823                 request = get_keybinding(view->keymap, key);
7825                 /* Some low-level request handling. This keeps access to
7826                  * status_win restricted. */
7827                 switch (request) {
7828                 case REQ_NONE:
7829                         report("Unknown key, press %s for help",
7830                                get_key(view->keymap, REQ_VIEW_HELP));
7831                         break;
7832                 case REQ_PROMPT:
7833                 {
7834                         char *cmd = read_prompt(":");
7836                         if (cmd && isdigit(*cmd)) {
7837                                 int lineno = view->lineno + 1;
7839                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7840                                         select_view_line(view, lineno - 1);
7841                                         report("");
7842                                 } else {
7843                                         report("Unable to parse '%s' as a line number", cmd);
7844                                 }
7846                         } else if (cmd) {
7847                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7848                                 const char *argv[SIZEOF_ARG] = { "git" };
7849                                 int argc = 1;
7851                                 /* When running random commands, initially show the
7852                                  * command in the title. However, it maybe later be
7853                                  * overwritten if a commit line is selected. */
7854                                 string_ncopy(next->ref, cmd, strlen(cmd));
7856                                 if (!argv_from_string(argv, &argc, cmd)) {
7857                                         report("Too many arguments");
7858                                 } else if (!prepare_update(next, argv, NULL)) {
7859                                         report("Failed to format command");
7860                                 } else {
7861                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7862                                 }
7863                         }
7865                         request = REQ_NONE;
7866                         break;
7867                 }
7868                 case REQ_SEARCH:
7869                 case REQ_SEARCH_BACK:
7870                 {
7871                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7872                         char *search = read_prompt(prompt);
7874                         if (search)
7875                                 string_ncopy(opt_search, search, strlen(search));
7876                         else if (*opt_search)
7877                                 request = request == REQ_SEARCH ?
7878                                         REQ_FIND_NEXT :
7879                                         REQ_FIND_PREV;
7880                         else
7881                                 request = REQ_NONE;
7882                         break;
7883                 }
7884                 default:
7885                         break;
7886                 }
7887         }
7889         quit(0);
7891         return 0;