Code

Refactor io_complete into a single backend for {back,fore}ground and append IO
[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);
681 static void
682 argv_free(const char *argv[])
684         int argc;
686         for (argc = 0; argv[argc]; argc++)
687                 free((void *) argv[argc]);
690 static void
691 argv_copy(const char *dst[], const char *src[])
693         int argc;
695         for (argc = 0; src[argc]; argc++)
696                 dst[argc] = src[argc];
700 /*
701  * Executing external commands.
702  */
704 enum io_type {
705         IO_FD,                  /* File descriptor based IO. */
706         IO_BG,                  /* Execute command in the background. */
707         IO_FG,                  /* Execute command with same std{in,out,err}. */
708         IO_RD,                  /* Read only fork+exec IO. */
709         IO_WR,                  /* Write only fork+exec IO. */
710         IO_AP,                  /* Append fork+exec output to file. */
711 };
713 struct io {
714         enum io_type type;      /* The requested type of pipe. */
715         const char *dir;        /* Directory from which to execute. */
716         pid_t pid;              /* PID of spawned process. */
717         int pipe;               /* Pipe end for reading or writing. */
718         int error;              /* Error status. */
719         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
720         char *buf;              /* Read buffer. */
721         size_t bufalloc;        /* Allocated buffer size. */
722         size_t bufsize;         /* Buffer content size. */
723         char *bufpos;           /* Current buffer position. */
724         unsigned int eof:1;     /* Has end of file been reached. */
725 };
727 static void
728 io_reset(struct io *io)
730         io->pipe = -1;
731         io->pid = 0;
732         io->buf = io->bufpos = NULL;
733         io->bufalloc = io->bufsize = 0;
734         io->error = 0;
735         io->eof = 0;
738 static void
739 io_init(struct io *io, const char *dir, enum io_type type)
741         io_reset(io);
742         io->type = type;
743         io->dir = dir;
746 static void
747 io_prepare(struct io *io, const char *dir, enum io_type type, const char *argv[])
749         io_init(io, dir, type);
750         argv_copy(io->argv, argv);
753 static bool
754 io_format(struct io *io, const char *dir, enum io_type type,
755           const char *argv[], enum format_flags flags)
757         io_init(io, dir, type);
758         return format_argv(io->argv, argv, flags);
761 static bool
762 io_open(struct io *io, const char *fmt, ...)
764         char name[SIZEOF_STR] = "";
765         bool fits;
766         va_list args;
768         io_init(io, NULL, IO_FD);
770         va_start(args, fmt);
771         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
772         va_end(args);
774         if (!fits) {
775                 io->error = ENAMETOOLONG;
776                 return FALSE;
777         }
778         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
779         if (io->pipe == -1)
780                 io->error = errno;
781         return io->pipe != -1;
784 static bool
785 io_kill(struct io *io)
787         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
790 static bool
791 io_done(struct io *io)
793         pid_t pid = io->pid;
795         if (io->pipe != -1)
796                 close(io->pipe);
797         free(io->buf);
798         io_reset(io);
800         while (pid > 0) {
801                 int status;
802                 pid_t waiting = waitpid(pid, &status, 0);
804                 if (waiting < 0) {
805                         if (errno == EINTR)
806                                 continue;
807                         io->error = errno;
808                         return FALSE;
809                 }
811                 return waiting == pid &&
812                        !WIFSIGNALED(status) &&
813                        WIFEXITED(status) &&
814                        !WEXITSTATUS(status);
815         }
817         return TRUE;
820 static bool
821 io_start(struct io *io)
823         int pipefds[2] = { -1, -1 };
825         if (io->type == IO_FD)
826                 return TRUE;
828         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
829                 io->error = errno;
830                 return FALSE;
831         } else if (io->type == IO_AP) {
832                 pipefds[1] = io->pipe;
833         }
835         if ((io->pid = fork())) {
836                 if (io->pid == -1)
837                         io->error = errno;
838                 if (pipefds[!(io->type == IO_WR)] != -1)
839                         close(pipefds[!(io->type == IO_WR)]);
840                 if (io->pid != -1) {
841                         io->pipe = pipefds[!!(io->type == IO_WR)];
842                         return TRUE;
843                 }
845         } else {
846                 if (io->type != IO_FG) {
847                         int devnull = open("/dev/null", O_RDWR);
848                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
849                         int writefd = (io->type == IO_RD || io->type == IO_AP)
850                                                         ? pipefds[1] : devnull;
852                         dup2(readfd,  STDIN_FILENO);
853                         dup2(writefd, STDOUT_FILENO);
854                         dup2(devnull, STDERR_FILENO);
856                         close(devnull);
857                         if (pipefds[0] != -1)
858                                 close(pipefds[0]);
859                         if (pipefds[1] != -1)
860                                 close(pipefds[1]);
861                 }
863                 if (io->dir && *io->dir && chdir(io->dir) == -1)
864                         exit(errno);
866                 execvp(io->argv[0], (char *const*) io->argv);
867                 exit(errno);
868         }
870         if (pipefds[!!(io->type == IO_WR)] != -1)
871                 close(pipefds[!!(io->type == IO_WR)]);
872         return FALSE;
875 static bool
876 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
878         io_prepare(io, dir, type, argv);
879         return io_start(io);
882 static bool
883 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
885         struct io io = {};
887         io_prepare(&io, dir, type, argv);
888         io.pipe = fd;
889         return io_start(&io) && io_done(&io);
892 static bool
893 io_run_bg(const char **argv)
895         return io_complete(IO_BG, argv, NULL, -1);
898 static bool
899 io_run_fg(const char **argv, const char *dir)
901         return io_complete(IO_FG, argv, dir, -1);
904 static bool
905 io_run_append(const char **argv, int fd)
907         return io_complete(IO_AP, argv, NULL, -1);
910 static bool
911 io_run_rd(struct io *io, const char **argv, const char *dir)
913         return io_format(io, dir, IO_RD, argv, FORMAT_NONE) && io_start(io);
916 static bool
917 io_eof(struct io *io)
919         return io->eof;
922 static int
923 io_error(struct io *io)
925         return io->error;
928 static char *
929 io_strerror(struct io *io)
931         return strerror(io->error);
934 static bool
935 io_can_read(struct io *io)
937         struct timeval tv = { 0, 500 };
938         fd_set fds;
940         FD_ZERO(&fds);
941         FD_SET(io->pipe, &fds);
943         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
946 static ssize_t
947 io_read(struct io *io, void *buf, size_t bufsize)
949         do {
950                 ssize_t readsize = read(io->pipe, buf, bufsize);
952                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
953                         continue;
954                 else if (readsize == -1)
955                         io->error = errno;
956                 else if (readsize == 0)
957                         io->eof = 1;
958                 return readsize;
959         } while (1);
962 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
964 static char *
965 io_get(struct io *io, int c, bool can_read)
967         char *eol;
968         ssize_t readsize;
970         while (TRUE) {
971                 if (io->bufsize > 0) {
972                         eol = memchr(io->bufpos, c, io->bufsize);
973                         if (eol) {
974                                 char *line = io->bufpos;
976                                 *eol = 0;
977                                 io->bufpos = eol + 1;
978                                 io->bufsize -= io->bufpos - line;
979                                 return line;
980                         }
981                 }
983                 if (io_eof(io)) {
984                         if (io->bufsize) {
985                                 io->bufpos[io->bufsize] = 0;
986                                 io->bufsize = 0;
987                                 return io->bufpos;
988                         }
989                         return NULL;
990                 }
992                 if (!can_read)
993                         return NULL;
995                 if (io->bufsize > 0 && io->bufpos > io->buf)
996                         memmove(io->buf, io->bufpos, io->bufsize);
998                 if (io->bufalloc == io->bufsize) {
999                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
1000                                 return NULL;
1001                         io->bufalloc += BUFSIZ;
1002                 }
1004                 io->bufpos = io->buf;
1005                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1006                 if (io_error(io))
1007                         return NULL;
1008                 io->bufsize += readsize;
1009         }
1012 static bool
1013 io_write(struct io *io, const void *buf, size_t bufsize)
1015         size_t written = 0;
1017         while (!io_error(io) && written < bufsize) {
1018                 ssize_t size;
1020                 size = write(io->pipe, buf + written, bufsize - written);
1021                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1022                         continue;
1023                 else if (size == -1)
1024                         io->error = errno;
1025                 else
1026                         written += size;
1027         }
1029         return written == bufsize;
1032 static bool
1033 io_read_buf(struct io *io, char buf[], size_t bufsize)
1035         char *result = io_get(io, '\n', TRUE);
1037         if (result) {
1038                 result = chomp_string(result);
1039                 string_ncopy_do(buf, bufsize, result, strlen(result));
1040         }
1042         return io_done(io) && result;
1045 static bool
1046 io_run_buf(const char **argv, char buf[], size_t bufsize)
1048         struct io io = {};
1050         io_prepare(&io, NULL, IO_RD, argv);
1051         return io_start(&io) && io_read_buf(&io, buf, bufsize);
1054 static int
1055 io_load(struct io *io, const char *separators,
1056         int (*read_property)(char *, size_t, char *, size_t))
1058         char *name;
1059         int state = OK;
1061         if (!io_start(io))
1062                 return ERR;
1064         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1065                 char *value;
1066                 size_t namelen;
1067                 size_t valuelen;
1069                 name = chomp_string(name);
1070                 namelen = strcspn(name, separators);
1072                 if (name[namelen]) {
1073                         name[namelen] = 0;
1074                         value = chomp_string(name + namelen + 1);
1075                         valuelen = strlen(value);
1077                 } else {
1078                         value = "";
1079                         valuelen = 0;
1080                 }
1082                 state = read_property(name, namelen, value, valuelen);
1083         }
1085         if (state != ERR && io_error(io))
1086                 state = ERR;
1087         io_done(io);
1089         return state;
1092 static int
1093 io_run_load(const char **argv, const char *separators,
1094             int (*read_property)(char *, size_t, char *, size_t))
1096         struct io io = {};
1098         io_prepare(&io, NULL, IO_RD, argv);
1099         return io_load(&io, separators, read_property);
1103 /*
1104  * User requests
1105  */
1107 #define REQ_INFO \
1108         /* XXX: Keep the view request first and in sync with views[]. */ \
1109         REQ_GROUP("View switching") \
1110         REQ_(VIEW_MAIN,         "Show main view"), \
1111         REQ_(VIEW_DIFF,         "Show diff view"), \
1112         REQ_(VIEW_LOG,          "Show log view"), \
1113         REQ_(VIEW_TREE,         "Show tree view"), \
1114         REQ_(VIEW_BLOB,         "Show blob view"), \
1115         REQ_(VIEW_BLAME,        "Show blame view"), \
1116         REQ_(VIEW_BRANCH,       "Show branch view"), \
1117         REQ_(VIEW_HELP,         "Show help page"), \
1118         REQ_(VIEW_PAGER,        "Show pager view"), \
1119         REQ_(VIEW_STATUS,       "Show status view"), \
1120         REQ_(VIEW_STAGE,        "Show stage view"), \
1121         \
1122         REQ_GROUP("View manipulation") \
1123         REQ_(ENTER,             "Enter current line and scroll"), \
1124         REQ_(NEXT,              "Move to next"), \
1125         REQ_(PREVIOUS,          "Move to previous"), \
1126         REQ_(PARENT,            "Move to parent"), \
1127         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1128         REQ_(REFRESH,           "Reload and refresh"), \
1129         REQ_(MAXIMIZE,          "Maximize the current view"), \
1130         REQ_(VIEW_CLOSE,        "Close the current view"), \
1131         REQ_(QUIT,              "Close all views and quit"), \
1132         \
1133         REQ_GROUP("View specific requests") \
1134         REQ_(STATUS_UPDATE,     "Update file status"), \
1135         REQ_(STATUS_REVERT,     "Revert file changes"), \
1136         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1137         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1138         \
1139         REQ_GROUP("Cursor navigation") \
1140         REQ_(MOVE_UP,           "Move cursor one line up"), \
1141         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1142         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1143         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1144         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1145         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1146         \
1147         REQ_GROUP("Scrolling") \
1148         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1149         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1150         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1151         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1152         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1153         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1154         \
1155         REQ_GROUP("Searching") \
1156         REQ_(SEARCH,            "Search the view"), \
1157         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1158         REQ_(FIND_NEXT,         "Find next search match"), \
1159         REQ_(FIND_PREV,         "Find previous search match"), \
1160         \
1161         REQ_GROUP("Option manipulation") \
1162         REQ_(OPTIONS,           "Open option menu"), \
1163         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1164         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1165         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1166         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1167         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1168         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1169         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1170         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1171         \
1172         REQ_GROUP("Misc") \
1173         REQ_(PROMPT,            "Bring up the prompt"), \
1174         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1175         REQ_(SHOW_VERSION,      "Show version information"), \
1176         REQ_(STOP_LOADING,      "Stop all loading views"), \
1177         REQ_(EDIT,              "Open in editor"), \
1178         REQ_(NONE,              "Do nothing")
1181 /* User action requests. */
1182 enum request {
1183 #define REQ_GROUP(help)
1184 #define REQ_(req, help) REQ_##req
1186         /* Offset all requests to avoid conflicts with ncurses getch values. */
1187         REQ_UNKNOWN = KEY_MAX + 1,
1188         REQ_OFFSET,
1189         REQ_INFO
1191 #undef  REQ_GROUP
1192 #undef  REQ_
1193 };
1195 struct request_info {
1196         enum request request;
1197         const char *name;
1198         int namelen;
1199         const char *help;
1200 };
1202 static const struct request_info req_info[] = {
1203 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1204 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1205         REQ_INFO
1206 #undef  REQ_GROUP
1207 #undef  REQ_
1208 };
1210 static enum request
1211 get_request(const char *name)
1213         int namelen = strlen(name);
1214         int i;
1216         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1217                 if (enum_equals(req_info[i], name, namelen))
1218                         return req_info[i].request;
1220         return REQ_UNKNOWN;
1224 /*
1225  * Options
1226  */
1228 /* Option and state variables. */
1229 static enum date opt_date               = DATE_DEFAULT;
1230 static enum author opt_author           = AUTHOR_DEFAULT;
1231 static bool opt_line_number             = FALSE;
1232 static bool opt_line_graphics           = TRUE;
1233 static bool opt_rev_graph               = FALSE;
1234 static bool opt_show_refs               = TRUE;
1235 static int opt_num_interval             = 5;
1236 static double opt_hscroll               = 0.50;
1237 static double opt_scale_split_view      = 2.0 / 3.0;
1238 static int opt_tab_size                 = 8;
1239 static int opt_author_cols              = AUTHOR_COLS;
1240 static char opt_path[SIZEOF_STR]        = "";
1241 static char opt_file[SIZEOF_STR]        = "";
1242 static char opt_ref[SIZEOF_REF]         = "";
1243 static char opt_head[SIZEOF_REF]        = "";
1244 static char opt_remote[SIZEOF_REF]      = "";
1245 static char opt_encoding[20]            = "UTF-8";
1246 static iconv_t opt_iconv_in             = ICONV_NONE;
1247 static iconv_t opt_iconv_out            = ICONV_NONE;
1248 static char opt_search[SIZEOF_STR]      = "";
1249 static char opt_cdup[SIZEOF_STR]        = "";
1250 static char opt_prefix[SIZEOF_STR]      = "";
1251 static char opt_git_dir[SIZEOF_STR]     = "";
1252 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1253 static char opt_editor[SIZEOF_STR]      = "";
1254 static FILE *opt_tty                    = NULL;
1256 #define is_initial_commit()     (!get_ref_head())
1257 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1260 /*
1261  * Line-oriented content detection.
1262  */
1264 #define LINE_INFO \
1265 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1266 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1267 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1268 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1269 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1270 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1272 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1273 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1274 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1275 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1276 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1277 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1278 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1279 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1280 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1281 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1282 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1283 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1284 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1285 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1286 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1287 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1288 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1289 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1290 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1291 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1292 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1293 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1294 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1295 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1296 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1297 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1298 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1299 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1300 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1301 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1302 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1303 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1304 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1305 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1306 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1307 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1308 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1309 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1310 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1311 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1312 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1313 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1314 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1315 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1316 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1317 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1318 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1319 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1320 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1321 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1322 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1323 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1325 enum line_type {
1326 #define LINE(type, line, fg, bg, attr) \
1327         LINE_##type
1328         LINE_INFO,
1329         LINE_NONE
1330 #undef  LINE
1331 };
1333 struct line_info {
1334         const char *name;       /* Option name. */
1335         int namelen;            /* Size of option name. */
1336         const char *line;       /* The start of line to match. */
1337         int linelen;            /* Size of string to match. */
1338         int fg, bg, attr;       /* Color and text attributes for the lines. */
1339 };
1341 static struct line_info line_info[] = {
1342 #define LINE(type, line, fg, bg, attr) \
1343         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1344         LINE_INFO
1345 #undef  LINE
1346 };
1348 static enum line_type
1349 get_line_type(const char *line)
1351         int linelen = strlen(line);
1352         enum line_type type;
1354         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1355                 /* Case insensitive search matches Signed-off-by lines better. */
1356                 if (linelen >= line_info[type].linelen &&
1357                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1358                         return type;
1360         return LINE_DEFAULT;
1363 static inline int
1364 get_line_attr(enum line_type type)
1366         assert(type < ARRAY_SIZE(line_info));
1367         return COLOR_PAIR(type) | line_info[type].attr;
1370 static struct line_info *
1371 get_line_info(const char *name)
1373         size_t namelen = strlen(name);
1374         enum line_type type;
1376         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1377                 if (enum_equals(line_info[type], name, namelen))
1378                         return &line_info[type];
1380         return NULL;
1383 static void
1384 init_colors(void)
1386         int default_bg = line_info[LINE_DEFAULT].bg;
1387         int default_fg = line_info[LINE_DEFAULT].fg;
1388         enum line_type type;
1390         start_color();
1392         if (assume_default_colors(default_fg, default_bg) == ERR) {
1393                 default_bg = COLOR_BLACK;
1394                 default_fg = COLOR_WHITE;
1395         }
1397         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1398                 struct line_info *info = &line_info[type];
1399                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1400                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1402                 init_pair(type, fg, bg);
1403         }
1406 struct line {
1407         enum line_type type;
1409         /* State flags */
1410         unsigned int selected:1;
1411         unsigned int dirty:1;
1412         unsigned int cleareol:1;
1413         unsigned int other:16;
1415         void *data;             /* User data */
1416 };
1419 /*
1420  * Keys
1421  */
1423 struct keybinding {
1424         int alias;
1425         enum request request;
1426 };
1428 static struct keybinding default_keybindings[] = {
1429         /* View switching */
1430         { 'm',          REQ_VIEW_MAIN },
1431         { 'd',          REQ_VIEW_DIFF },
1432         { 'l',          REQ_VIEW_LOG },
1433         { 't',          REQ_VIEW_TREE },
1434         { 'f',          REQ_VIEW_BLOB },
1435         { 'B',          REQ_VIEW_BLAME },
1436         { 'H',          REQ_VIEW_BRANCH },
1437         { 'p',          REQ_VIEW_PAGER },
1438         { 'h',          REQ_VIEW_HELP },
1439         { 'S',          REQ_VIEW_STATUS },
1440         { 'c',          REQ_VIEW_STAGE },
1442         /* View manipulation */
1443         { 'q',          REQ_VIEW_CLOSE },
1444         { KEY_TAB,      REQ_VIEW_NEXT },
1445         { KEY_RETURN,   REQ_ENTER },
1446         { KEY_UP,       REQ_PREVIOUS },
1447         { KEY_DOWN,     REQ_NEXT },
1448         { 'R',          REQ_REFRESH },
1449         { KEY_F(5),     REQ_REFRESH },
1450         { 'O',          REQ_MAXIMIZE },
1452         /* Cursor navigation */
1453         { 'k',          REQ_MOVE_UP },
1454         { 'j',          REQ_MOVE_DOWN },
1455         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1456         { KEY_END,      REQ_MOVE_LAST_LINE },
1457         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1458         { ' ',          REQ_MOVE_PAGE_DOWN },
1459         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1460         { 'b',          REQ_MOVE_PAGE_UP },
1461         { '-',          REQ_MOVE_PAGE_UP },
1463         /* Scrolling */
1464         { KEY_LEFT,     REQ_SCROLL_LEFT },
1465         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1466         { KEY_IC,       REQ_SCROLL_LINE_UP },
1467         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1468         { 'w',          REQ_SCROLL_PAGE_UP },
1469         { 's',          REQ_SCROLL_PAGE_DOWN },
1471         /* Searching */
1472         { '/',          REQ_SEARCH },
1473         { '?',          REQ_SEARCH_BACK },
1474         { 'n',          REQ_FIND_NEXT },
1475         { 'N',          REQ_FIND_PREV },
1477         /* Misc */
1478         { 'Q',          REQ_QUIT },
1479         { 'z',          REQ_STOP_LOADING },
1480         { 'v',          REQ_SHOW_VERSION },
1481         { 'r',          REQ_SCREEN_REDRAW },
1482         { 'o',          REQ_OPTIONS },
1483         { '.',          REQ_TOGGLE_LINENO },
1484         { 'D',          REQ_TOGGLE_DATE },
1485         { 'A',          REQ_TOGGLE_AUTHOR },
1486         { 'g',          REQ_TOGGLE_REV_GRAPH },
1487         { 'F',          REQ_TOGGLE_REFS },
1488         { 'I',          REQ_TOGGLE_SORT_ORDER },
1489         { 'i',          REQ_TOGGLE_SORT_FIELD },
1490         { ':',          REQ_PROMPT },
1491         { 'u',          REQ_STATUS_UPDATE },
1492         { '!',          REQ_STATUS_REVERT },
1493         { 'M',          REQ_STATUS_MERGE },
1494         { '@',          REQ_STAGE_NEXT },
1495         { ',',          REQ_PARENT },
1496         { 'e',          REQ_EDIT },
1497 };
1499 #define KEYMAP_INFO \
1500         KEYMAP_(GENERIC), \
1501         KEYMAP_(MAIN), \
1502         KEYMAP_(DIFF), \
1503         KEYMAP_(LOG), \
1504         KEYMAP_(TREE), \
1505         KEYMAP_(BLOB), \
1506         KEYMAP_(BLAME), \
1507         KEYMAP_(BRANCH), \
1508         KEYMAP_(PAGER), \
1509         KEYMAP_(HELP), \
1510         KEYMAP_(STATUS), \
1511         KEYMAP_(STAGE)
1513 enum keymap {
1514 #define KEYMAP_(name) KEYMAP_##name
1515         KEYMAP_INFO
1516 #undef  KEYMAP_
1517 };
1519 static const struct enum_map keymap_table[] = {
1520 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1521         KEYMAP_INFO
1522 #undef  KEYMAP_
1523 };
1525 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1527 struct keybinding_table {
1528         struct keybinding *data;
1529         size_t size;
1530 };
1532 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1534 static void
1535 add_keybinding(enum keymap keymap, enum request request, int key)
1537         struct keybinding_table *table = &keybindings[keymap];
1538         size_t i;
1540         for (i = 0; i < keybindings[keymap].size; i++) {
1541                 if (keybindings[keymap].data[i].alias == key) {
1542                         keybindings[keymap].data[i].request = request;
1543                         return;
1544                 }
1545         }
1547         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1548         if (!table->data)
1549                 die("Failed to allocate keybinding");
1550         table->data[table->size].alias = key;
1551         table->data[table->size++].request = request;
1553         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1554                 int i;
1556                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1557                         if (default_keybindings[i].alias == key)
1558                                 default_keybindings[i].request = REQ_NONE;
1559         }
1562 /* Looks for a key binding first in the given map, then in the generic map, and
1563  * lastly in the default keybindings. */
1564 static enum request
1565 get_keybinding(enum keymap keymap, int key)
1567         size_t i;
1569         for (i = 0; i < keybindings[keymap].size; i++)
1570                 if (keybindings[keymap].data[i].alias == key)
1571                         return keybindings[keymap].data[i].request;
1573         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1574                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1575                         return keybindings[KEYMAP_GENERIC].data[i].request;
1577         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1578                 if (default_keybindings[i].alias == key)
1579                         return default_keybindings[i].request;
1581         return (enum request) key;
1585 struct key {
1586         const char *name;
1587         int value;
1588 };
1590 static const struct key key_table[] = {
1591         { "Enter",      KEY_RETURN },
1592         { "Space",      ' ' },
1593         { "Backspace",  KEY_BACKSPACE },
1594         { "Tab",        KEY_TAB },
1595         { "Escape",     KEY_ESC },
1596         { "Left",       KEY_LEFT },
1597         { "Right",      KEY_RIGHT },
1598         { "Up",         KEY_UP },
1599         { "Down",       KEY_DOWN },
1600         { "Insert",     KEY_IC },
1601         { "Delete",     KEY_DC },
1602         { "Hash",       '#' },
1603         { "Home",       KEY_HOME },
1604         { "End",        KEY_END },
1605         { "PageUp",     KEY_PPAGE },
1606         { "PageDown",   KEY_NPAGE },
1607         { "F1",         KEY_F(1) },
1608         { "F2",         KEY_F(2) },
1609         { "F3",         KEY_F(3) },
1610         { "F4",         KEY_F(4) },
1611         { "F5",         KEY_F(5) },
1612         { "F6",         KEY_F(6) },
1613         { "F7",         KEY_F(7) },
1614         { "F8",         KEY_F(8) },
1615         { "F9",         KEY_F(9) },
1616         { "F10",        KEY_F(10) },
1617         { "F11",        KEY_F(11) },
1618         { "F12",        KEY_F(12) },
1619 };
1621 static int
1622 get_key_value(const char *name)
1624         int i;
1626         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1627                 if (!strcasecmp(key_table[i].name, name))
1628                         return key_table[i].value;
1630         if (strlen(name) == 1 && isprint(*name))
1631                 return (int) *name;
1633         return ERR;
1636 static const char *
1637 get_key_name(int key_value)
1639         static char key_char[] = "'X'";
1640         const char *seq = NULL;
1641         int key;
1643         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1644                 if (key_table[key].value == key_value)
1645                         seq = key_table[key].name;
1647         if (seq == NULL &&
1648             key_value < 127 &&
1649             isprint(key_value)) {
1650                 key_char[1] = (char) key_value;
1651                 seq = key_char;
1652         }
1654         return seq ? seq : "(no key)";
1657 static bool
1658 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1660         const char *sep = *pos > 0 ? ", " : "";
1661         const char *keyname = get_key_name(keybinding->alias);
1663         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1666 static bool
1667 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1668                            enum keymap keymap, bool all)
1670         int i;
1672         for (i = 0; i < keybindings[keymap].size; i++) {
1673                 if (keybindings[keymap].data[i].request == request) {
1674                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1675                                 return FALSE;
1676                         if (!all)
1677                                 break;
1678                 }
1679         }
1681         return TRUE;
1684 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1686 static const char *
1687 get_keys(enum keymap keymap, enum request request, bool all)
1689         static char buf[BUFSIZ];
1690         size_t pos = 0;
1691         int i;
1693         buf[pos] = 0;
1695         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1696                 return "Too many keybindings!";
1697         if (pos > 0 && !all)
1698                 return buf;
1700         if (keymap != KEYMAP_GENERIC) {
1701                 /* Only the generic keymap includes the default keybindings when
1702                  * listing all keys. */
1703                 if (all)
1704                         return buf;
1706                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1707                         return "Too many keybindings!";
1708                 if (pos)
1709                         return buf;
1710         }
1712         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1713                 if (default_keybindings[i].request == request) {
1714                         if (!append_key(buf, &pos, &default_keybindings[i]))
1715                                 return "Too many keybindings!";
1716                         if (!all)
1717                                 return buf;
1718                 }
1719         }
1721         return buf;
1724 struct run_request {
1725         enum keymap keymap;
1726         int key;
1727         const char *argv[SIZEOF_ARG];
1728 };
1730 static struct run_request *run_request;
1731 static size_t run_requests;
1733 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1735 static enum request
1736 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1738         struct run_request *req;
1740         if (argc >= ARRAY_SIZE(req->argv) - 1)
1741                 return REQ_NONE;
1743         if (!realloc_run_requests(&run_request, run_requests, 1))
1744                 return REQ_NONE;
1746         req = &run_request[run_requests];
1747         req->keymap = keymap;
1748         req->key = key;
1749         req->argv[0] = NULL;
1751         if (!format_argv(req->argv, argv, FORMAT_NONE))
1752                 return REQ_NONE;
1754         return REQ_NONE + ++run_requests;
1757 static struct run_request *
1758 get_run_request(enum request request)
1760         if (request <= REQ_NONE)
1761                 return NULL;
1762         return &run_request[request - REQ_NONE - 1];
1765 static void
1766 add_builtin_run_requests(void)
1768         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1769         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1770         const char *commit[] = { "git", "commit", NULL };
1771         const char *gc[] = { "git", "gc", NULL };
1772         struct {
1773                 enum keymap keymap;
1774                 int key;
1775                 int argc;
1776                 const char **argv;
1777         } reqs[] = {
1778                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1779                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1780                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1781                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1782         };
1783         int i;
1785         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1786                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1788                 if (req != reqs[i].key)
1789                         continue;
1790                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1791                 if (req != REQ_NONE)
1792                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1793         }
1796 /*
1797  * User config file handling.
1798  */
1800 static int   config_lineno;
1801 static bool  config_errors;
1802 static const char *config_msg;
1804 static const struct enum_map color_map[] = {
1805 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1806         COLOR_MAP(DEFAULT),
1807         COLOR_MAP(BLACK),
1808         COLOR_MAP(BLUE),
1809         COLOR_MAP(CYAN),
1810         COLOR_MAP(GREEN),
1811         COLOR_MAP(MAGENTA),
1812         COLOR_MAP(RED),
1813         COLOR_MAP(WHITE),
1814         COLOR_MAP(YELLOW),
1815 };
1817 static const struct enum_map attr_map[] = {
1818 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1819         ATTR_MAP(NORMAL),
1820         ATTR_MAP(BLINK),
1821         ATTR_MAP(BOLD),
1822         ATTR_MAP(DIM),
1823         ATTR_MAP(REVERSE),
1824         ATTR_MAP(STANDOUT),
1825         ATTR_MAP(UNDERLINE),
1826 };
1828 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1830 static int parse_step(double *opt, const char *arg)
1832         *opt = atoi(arg);
1833         if (!strchr(arg, '%'))
1834                 return OK;
1836         /* "Shift down" so 100% and 1 does not conflict. */
1837         *opt = (*opt - 1) / 100;
1838         if (*opt >= 1.0) {
1839                 *opt = 0.99;
1840                 config_msg = "Step value larger than 100%";
1841                 return ERR;
1842         }
1843         if (*opt < 0.0) {
1844                 *opt = 1;
1845                 config_msg = "Invalid step value";
1846                 return ERR;
1847         }
1848         return OK;
1851 static int
1852 parse_int(int *opt, const char *arg, int min, int max)
1854         int value = atoi(arg);
1856         if (min <= value && value <= max) {
1857                 *opt = value;
1858                 return OK;
1859         }
1861         config_msg = "Integer value out of bound";
1862         return ERR;
1865 static bool
1866 set_color(int *color, const char *name)
1868         if (map_enum(color, color_map, name))
1869                 return TRUE;
1870         if (!prefixcmp(name, "color"))
1871                 return parse_int(color, name + 5, 0, 255) == OK;
1872         return FALSE;
1875 /* Wants: object fgcolor bgcolor [attribute] */
1876 static int
1877 option_color_command(int argc, const char *argv[])
1879         struct line_info *info;
1881         if (argc < 3) {
1882                 config_msg = "Wrong number of arguments given to color command";
1883                 return ERR;
1884         }
1886         info = get_line_info(argv[0]);
1887         if (!info) {
1888                 static const struct enum_map obsolete[] = {
1889                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1890                         ENUM_MAP("main-date",   LINE_DATE),
1891                         ENUM_MAP("main-author", LINE_AUTHOR),
1892                 };
1893                 int index;
1895                 if (!map_enum(&index, obsolete, argv[0])) {
1896                         config_msg = "Unknown color name";
1897                         return ERR;
1898                 }
1899                 info = &line_info[index];
1900         }
1902         if (!set_color(&info->fg, argv[1]) ||
1903             !set_color(&info->bg, argv[2])) {
1904                 config_msg = "Unknown color";
1905                 return ERR;
1906         }
1908         info->attr = 0;
1909         while (argc-- > 3) {
1910                 int attr;
1912                 if (!set_attribute(&attr, argv[argc])) {
1913                         config_msg = "Unknown attribute";
1914                         return ERR;
1915                 }
1916                 info->attr |= attr;
1917         }
1919         return OK;
1922 static int parse_bool(bool *opt, const char *arg)
1924         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1925                 ? TRUE : FALSE;
1926         return OK;
1929 static int parse_enum_do(unsigned int *opt, const char *arg,
1930                          const struct enum_map *map, size_t map_size)
1932         bool is_true;
1934         assert(map_size > 1);
1936         if (map_enum_do(map, map_size, (int *) opt, arg))
1937                 return OK;
1939         if (parse_bool(&is_true, arg) != OK)
1940                 return ERR;
1942         *opt = is_true ? map[1].value : map[0].value;
1943         return OK;
1946 #define parse_enum(opt, arg, map) \
1947         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1949 static int
1950 parse_string(char *opt, const char *arg, size_t optsize)
1952         int arglen = strlen(arg);
1954         switch (arg[0]) {
1955         case '\"':
1956         case '\'':
1957                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1958                         config_msg = "Unmatched quotation";
1959                         return ERR;
1960                 }
1961                 arg += 1; arglen -= 2;
1962         default:
1963                 string_ncopy_do(opt, optsize, arg, arglen);
1964                 return OK;
1965         }
1968 /* Wants: name = value */
1969 static int
1970 option_set_command(int argc, const char *argv[])
1972         if (argc != 3) {
1973                 config_msg = "Wrong number of arguments given to set command";
1974                 return ERR;
1975         }
1977         if (strcmp(argv[1], "=")) {
1978                 config_msg = "No value assigned";
1979                 return ERR;
1980         }
1982         if (!strcmp(argv[0], "show-author"))
1983                 return parse_enum(&opt_author, argv[2], author_map);
1985         if (!strcmp(argv[0], "show-date"))
1986                 return parse_enum(&opt_date, argv[2], date_map);
1988         if (!strcmp(argv[0], "show-rev-graph"))
1989                 return parse_bool(&opt_rev_graph, argv[2]);
1991         if (!strcmp(argv[0], "show-refs"))
1992                 return parse_bool(&opt_show_refs, argv[2]);
1994         if (!strcmp(argv[0], "show-line-numbers"))
1995                 return parse_bool(&opt_line_number, argv[2]);
1997         if (!strcmp(argv[0], "line-graphics"))
1998                 return parse_bool(&opt_line_graphics, argv[2]);
2000         if (!strcmp(argv[0], "line-number-interval"))
2001                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2003         if (!strcmp(argv[0], "author-width"))
2004                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2006         if (!strcmp(argv[0], "horizontal-scroll"))
2007                 return parse_step(&opt_hscroll, argv[2]);
2009         if (!strcmp(argv[0], "split-view-height"))
2010                 return parse_step(&opt_scale_split_view, argv[2]);
2012         if (!strcmp(argv[0], "tab-size"))
2013                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2015         if (!strcmp(argv[0], "commit-encoding"))
2016                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2018         config_msg = "Unknown variable name";
2019         return ERR;
2022 /* Wants: mode request key */
2023 static int
2024 option_bind_command(int argc, const char *argv[])
2026         enum request request;
2027         int keymap = -1;
2028         int key;
2030         if (argc < 3) {
2031                 config_msg = "Wrong number of arguments given to bind command";
2032                 return ERR;
2033         }
2035         if (!set_keymap(&keymap, argv[0])) {
2036                 config_msg = "Unknown key map";
2037                 return ERR;
2038         }
2040         key = get_key_value(argv[1]);
2041         if (key == ERR) {
2042                 config_msg = "Unknown key";
2043                 return ERR;
2044         }
2046         request = get_request(argv[2]);
2047         if (request == REQ_UNKNOWN) {
2048                 static const struct enum_map obsolete[] = {
2049                         ENUM_MAP("cherry-pick",         REQ_NONE),
2050                         ENUM_MAP("screen-resize",       REQ_NONE),
2051                         ENUM_MAP("tree-parent",         REQ_PARENT),
2052                 };
2053                 int alias;
2055                 if (map_enum(&alias, obsolete, argv[2])) {
2056                         if (alias != REQ_NONE)
2057                                 add_keybinding(keymap, alias, key);
2058                         config_msg = "Obsolete request name";
2059                         return ERR;
2060                 }
2061         }
2062         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2063                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2064         if (request == REQ_UNKNOWN) {
2065                 config_msg = "Unknown request name";
2066                 return ERR;
2067         }
2069         add_keybinding(keymap, request, key);
2071         return OK;
2074 static int
2075 set_option(const char *opt, char *value)
2077         const char *argv[SIZEOF_ARG];
2078         int argc = 0;
2080         if (!argv_from_string(argv, &argc, value)) {
2081                 config_msg = "Too many option arguments";
2082                 return ERR;
2083         }
2085         if (!strcmp(opt, "color"))
2086                 return option_color_command(argc, argv);
2088         if (!strcmp(opt, "set"))
2089                 return option_set_command(argc, argv);
2091         if (!strcmp(opt, "bind"))
2092                 return option_bind_command(argc, argv);
2094         config_msg = "Unknown option command";
2095         return ERR;
2098 static int
2099 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2101         int status = OK;
2103         config_lineno++;
2104         config_msg = "Internal error";
2106         /* Check for comment markers, since read_properties() will
2107          * only ensure opt and value are split at first " \t". */
2108         optlen = strcspn(opt, "#");
2109         if (optlen == 0)
2110                 return OK;
2112         if (opt[optlen] != 0) {
2113                 config_msg = "No option value";
2114                 status = ERR;
2116         }  else {
2117                 /* Look for comment endings in the value. */
2118                 size_t len = strcspn(value, "#");
2120                 if (len < valuelen) {
2121                         valuelen = len;
2122                         value[valuelen] = 0;
2123                 }
2125                 status = set_option(opt, value);
2126         }
2128         if (status == ERR) {
2129                 warn("Error on line %d, near '%.*s': %s",
2130                      config_lineno, (int) optlen, opt, config_msg);
2131                 config_errors = TRUE;
2132         }
2134         /* Always keep going if errors are encountered. */
2135         return OK;
2138 static void
2139 load_option_file(const char *path)
2141         struct io io = {};
2143         /* It's OK that the file doesn't exist. */
2144         if (!io_open(&io, "%s", path))
2145                 return;
2147         config_lineno = 0;
2148         config_errors = FALSE;
2150         if (io_load(&io, " \t", read_option) == ERR ||
2151             config_errors == TRUE)
2152                 warn("Errors while loading %s.", path);
2155 static int
2156 load_options(void)
2158         const char *home = getenv("HOME");
2159         const char *tigrc_user = getenv("TIGRC_USER");
2160         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2161         char buf[SIZEOF_STR];
2163         if (!tigrc_system)
2164                 tigrc_system = SYSCONFDIR "/tigrc";
2165         load_option_file(tigrc_system);
2167         if (!tigrc_user) {
2168                 if (!home || !string_format(buf, "%s/.tigrc", home))
2169                         return ERR;
2170                 tigrc_user = buf;
2171         }
2172         load_option_file(tigrc_user);
2174         /* Add _after_ loading config files to avoid adding run requests
2175          * that conflict with keybindings. */
2176         add_builtin_run_requests();
2178         return OK;
2182 /*
2183  * The viewer
2184  */
2186 struct view;
2187 struct view_ops;
2189 /* The display array of active views and the index of the current view. */
2190 static struct view *display[2];
2191 static unsigned int current_view;
2193 #define foreach_displayed_view(view, i) \
2194         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2196 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2198 /* Current head and commit ID */
2199 static char ref_blob[SIZEOF_REF]        = "";
2200 static char ref_commit[SIZEOF_REF]      = "HEAD";
2201 static char ref_head[SIZEOF_REF]        = "HEAD";
2202 static char ref_branch[SIZEOF_REF]      = "";
2204 enum view_type {
2205         VIEW_MAIN,
2206         VIEW_DIFF,
2207         VIEW_LOG,
2208         VIEW_TREE,
2209         VIEW_BLOB,
2210         VIEW_BLAME,
2211         VIEW_BRANCH,
2212         VIEW_HELP,
2213         VIEW_PAGER,
2214         VIEW_STATUS,
2215         VIEW_STAGE,
2216 };
2218 struct view {
2219         enum view_type type;    /* View type */
2220         const char *name;       /* View name */
2221         const char *cmd_env;    /* Command line set via environment */
2222         const char *id;         /* Points to either of ref_{head,commit,blob} */
2224         struct view_ops *ops;   /* View operations */
2226         enum keymap keymap;     /* What keymap does this view have */
2227         bool git_dir;           /* Whether the view requires a git directory. */
2229         char ref[SIZEOF_REF];   /* Hovered commit reference */
2230         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2232         int height, width;      /* The width and height of the main window */
2233         WINDOW *win;            /* The main window */
2234         WINDOW *title;          /* The title window living below the main window */
2236         /* Navigation */
2237         unsigned long offset;   /* Offset of the window top */
2238         unsigned long yoffset;  /* Offset from the window side. */
2239         unsigned long lineno;   /* Current line number */
2240         unsigned long p_offset; /* Previous offset of the window top */
2241         unsigned long p_yoffset;/* Previous offset from the window side */
2242         unsigned long p_lineno; /* Previous current line number */
2243         bool p_restore;         /* Should the previous position be restored. */
2245         /* Searching */
2246         char grep[SIZEOF_STR];  /* Search string */
2247         regex_t *regex;         /* Pre-compiled regexp */
2249         /* If non-NULL, points to the view that opened this view. If this view
2250          * is closed tig will switch back to the parent view. */
2251         struct view *parent;
2252         struct view *prev;
2254         /* Buffering */
2255         size_t lines;           /* Total number of lines */
2256         struct line *line;      /* Line index */
2257         unsigned int digits;    /* Number of digits in the lines member. */
2259         /* Drawing */
2260         struct line *curline;   /* Line currently being drawn. */
2261         enum line_type curtype; /* Attribute currently used for drawing. */
2262         unsigned long col;      /* Column when drawing. */
2263         bool has_scrolled;      /* View was scrolled. */
2265         /* Loading */
2266         struct io io;
2267         struct io *pipe;
2268         time_t start_time;
2269         time_t update_secs;
2270 };
2272 struct view_ops {
2273         /* What type of content being displayed. Used in the title bar. */
2274         const char *type;
2275         /* Default command arguments. */
2276         const char **argv;
2277         /* Open and reads in all view content. */
2278         bool (*open)(struct view *view);
2279         /* Read one line; updates view->line. */
2280         bool (*read)(struct view *view, char *data);
2281         /* Draw one line; @lineno must be < view->height. */
2282         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2283         /* Depending on view handle a special requests. */
2284         enum request (*request)(struct view *view, enum request request, struct line *line);
2285         /* Search for regexp in a line. */
2286         bool (*grep)(struct view *view, struct line *line);
2287         /* Select line */
2288         void (*select)(struct view *view, struct line *line);
2289         /* Prepare view for loading */
2290         bool (*prepare)(struct view *view);
2291 };
2293 static struct view_ops blame_ops;
2294 static struct view_ops blob_ops;
2295 static struct view_ops diff_ops;
2296 static struct view_ops help_ops;
2297 static struct view_ops log_ops;
2298 static struct view_ops main_ops;
2299 static struct view_ops pager_ops;
2300 static struct view_ops stage_ops;
2301 static struct view_ops status_ops;
2302 static struct view_ops tree_ops;
2303 static struct view_ops branch_ops;
2305 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2306         { type, name, #env, ref, ops, map, git }
2308 #define VIEW_(id, name, ops, git, ref) \
2309         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2311 static struct view views[] = {
2312         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2313         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2314         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2315         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2316         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2317         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2318         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2319         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2320         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2321         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2322         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2323 };
2325 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2327 #define foreach_view(view, i) \
2328         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2330 #define view_is_displayed(view) \
2331         (view == display[0] || view == display[1])
2333 static enum request
2334 view_request(struct view *view, enum request request)
2336         if (!view || !view->lines)
2337                 return request;
2338         return view->ops->request(view, request, &view->line[view->lineno]);
2342 /*
2343  * View drawing.
2344  */
2346 static inline void
2347 set_view_attr(struct view *view, enum line_type type)
2349         if (!view->curline->selected && view->curtype != type) {
2350                 (void) wattrset(view->win, get_line_attr(type));
2351                 wchgat(view->win, -1, 0, type, NULL);
2352                 view->curtype = type;
2353         }
2356 static int
2357 draw_chars(struct view *view, enum line_type type, const char *string,
2358            int max_len, bool use_tilde)
2360         static char out_buffer[BUFSIZ * 2];
2361         int len = 0;
2362         int col = 0;
2363         int trimmed = FALSE;
2364         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2366         if (max_len <= 0)
2367                 return 0;
2369         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2371         set_view_attr(view, type);
2372         if (len > 0) {
2373                 if (opt_iconv_out != ICONV_NONE) {
2374                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2375                         size_t inlen = len + 1;
2377                         char *outbuf = out_buffer;
2378                         size_t outlen = sizeof(out_buffer);
2380                         size_t ret;
2382                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2383                         if (ret != (size_t) -1) {
2384                                 string = out_buffer;
2385                                 len = sizeof(out_buffer) - outlen;
2386                         }
2387                 }
2389                 waddnstr(view->win, string, len);
2390         }
2391         if (trimmed && use_tilde) {
2392                 set_view_attr(view, LINE_DELIMITER);
2393                 waddch(view->win, '~');
2394                 col++;
2395         }
2397         return col;
2400 static int
2401 draw_space(struct view *view, enum line_type type, int max, int spaces)
2403         static char space[] = "                    ";
2404         int col = 0;
2406         spaces = MIN(max, spaces);
2408         while (spaces > 0) {
2409                 int len = MIN(spaces, sizeof(space) - 1);
2411                 col += draw_chars(view, type, space, len, FALSE);
2412                 spaces -= len;
2413         }
2415         return col;
2418 static bool
2419 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2421         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2422         return view->width + view->yoffset <= view->col;
2425 static bool
2426 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2428         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2429         int max = view->width + view->yoffset - view->col;
2430         int i;
2432         if (max < size)
2433                 size = max;
2435         set_view_attr(view, type);
2436         /* Using waddch() instead of waddnstr() ensures that
2437          * they'll be rendered correctly for the cursor line. */
2438         for (i = skip; i < size; i++)
2439                 waddch(view->win, graphic[i]);
2441         view->col += size;
2442         if (size < max && skip <= size)
2443                 waddch(view->win, ' ');
2444         view->col++;
2446         return view->width + view->yoffset <= view->col;
2449 static bool
2450 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2452         int max = MIN(view->width + view->yoffset - view->col, len);
2453         int col;
2455         if (text)
2456                 col = draw_chars(view, type, text, max - 1, trim);
2457         else
2458                 col = draw_space(view, type, max - 1, max - 1);
2460         view->col += col;
2461         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2462         return view->width + view->yoffset <= view->col;
2465 static bool
2466 draw_date(struct view *view, struct time *time)
2468         const char *date = mkdate(time, opt_date);
2469         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2471         return draw_field(view, LINE_DATE, date, cols, FALSE);
2474 static bool
2475 draw_author(struct view *view, const char *author)
2477         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2478         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2480         if (abbreviate && author)
2481                 author = get_author_initials(author);
2483         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2486 static bool
2487 draw_mode(struct view *view, mode_t mode)
2489         const char *str;
2491         if (S_ISDIR(mode))
2492                 str = "drwxr-xr-x";
2493         else if (S_ISLNK(mode))
2494                 str = "lrwxrwxrwx";
2495         else if (S_ISGITLINK(mode))
2496                 str = "m---------";
2497         else if (S_ISREG(mode) && mode & S_IXUSR)
2498                 str = "-rwxr-xr-x";
2499         else if (S_ISREG(mode))
2500                 str = "-rw-r--r--";
2501         else
2502                 str = "----------";
2504         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2507 static bool
2508 draw_lineno(struct view *view, unsigned int lineno)
2510         char number[10];
2511         int digits3 = view->digits < 3 ? 3 : view->digits;
2512         int max = MIN(view->width + view->yoffset - view->col, digits3);
2513         char *text = NULL;
2514         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2516         lineno += view->offset + 1;
2517         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2518                 static char fmt[] = "%1ld";
2520                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2521                 if (string_format(number, fmt, lineno))
2522                         text = number;
2523         }
2524         if (text)
2525                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2526         else
2527                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2528         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2531 static bool
2532 draw_view_line(struct view *view, unsigned int lineno)
2534         struct line *line;
2535         bool selected = (view->offset + lineno == view->lineno);
2537         assert(view_is_displayed(view));
2539         if (view->offset + lineno >= view->lines)
2540                 return FALSE;
2542         line = &view->line[view->offset + lineno];
2544         wmove(view->win, lineno, 0);
2545         if (line->cleareol)
2546                 wclrtoeol(view->win);
2547         view->col = 0;
2548         view->curline = line;
2549         view->curtype = LINE_NONE;
2550         line->selected = FALSE;
2551         line->dirty = line->cleareol = 0;
2553         if (selected) {
2554                 set_view_attr(view, LINE_CURSOR);
2555                 line->selected = TRUE;
2556                 view->ops->select(view, line);
2557         }
2559         return view->ops->draw(view, line, lineno);
2562 static void
2563 redraw_view_dirty(struct view *view)
2565         bool dirty = FALSE;
2566         int lineno;
2568         for (lineno = 0; lineno < view->height; lineno++) {
2569                 if (view->offset + lineno >= view->lines)
2570                         break;
2571                 if (!view->line[view->offset + lineno].dirty)
2572                         continue;
2573                 dirty = TRUE;
2574                 if (!draw_view_line(view, lineno))
2575                         break;
2576         }
2578         if (!dirty)
2579                 return;
2580         wnoutrefresh(view->win);
2583 static void
2584 redraw_view_from(struct view *view, int lineno)
2586         assert(0 <= lineno && lineno < view->height);
2588         for (; lineno < view->height; lineno++) {
2589                 if (!draw_view_line(view, lineno))
2590                         break;
2591         }
2593         wnoutrefresh(view->win);
2596 static void
2597 redraw_view(struct view *view)
2599         werase(view->win);
2600         redraw_view_from(view, 0);
2604 static void
2605 update_view_title(struct view *view)
2607         char buf[SIZEOF_STR];
2608         char state[SIZEOF_STR];
2609         size_t bufpos = 0, statelen = 0;
2611         assert(view_is_displayed(view));
2613         if (view->type != VIEW_STATUS && view->lines) {
2614                 unsigned int view_lines = view->offset + view->height;
2615                 unsigned int lines = view->lines
2616                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2617                                    : 0;
2619                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2620                                    view->ops->type,
2621                                    view->lineno + 1,
2622                                    view->lines,
2623                                    lines);
2625         }
2627         if (view->pipe) {
2628                 time_t secs = time(NULL) - view->start_time;
2630                 /* Three git seconds are a long time ... */
2631                 if (secs > 2)
2632                         string_format_from(state, &statelen, " loading %lds", secs);
2633         }
2635         string_format_from(buf, &bufpos, "[%s]", view->name);
2636         if (*view->ref && bufpos < view->width) {
2637                 size_t refsize = strlen(view->ref);
2638                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2640                 if (minsize < view->width)
2641                         refsize = view->width - minsize + 7;
2642                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2643         }
2645         if (statelen && bufpos < view->width) {
2646                 string_format_from(buf, &bufpos, "%s", state);
2647         }
2649         if (view == display[current_view])
2650                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2651         else
2652                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2654         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2655         wclrtoeol(view->title);
2656         wnoutrefresh(view->title);
2659 static int
2660 apply_step(double step, int value)
2662         if (step >= 1)
2663                 return (int) step;
2664         value *= step + 0.01;
2665         return value ? value : 1;
2668 static void
2669 resize_display(void)
2671         int offset, i;
2672         struct view *base = display[0];
2673         struct view *view = display[1] ? display[1] : display[0];
2675         /* Setup window dimensions */
2677         getmaxyx(stdscr, base->height, base->width);
2679         /* Make room for the status window. */
2680         base->height -= 1;
2682         if (view != base) {
2683                 /* Horizontal split. */
2684                 view->width   = base->width;
2685                 view->height  = apply_step(opt_scale_split_view, base->height);
2686                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2687                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2688                 base->height -= view->height;
2690                 /* Make room for the title bar. */
2691                 view->height -= 1;
2692         }
2694         /* Make room for the title bar. */
2695         base->height -= 1;
2697         offset = 0;
2699         foreach_displayed_view (view, i) {
2700                 if (!view->win) {
2701                         view->win = newwin(view->height, 0, offset, 0);
2702                         if (!view->win)
2703                                 die("Failed to create %s view", view->name);
2705                         scrollok(view->win, FALSE);
2707                         view->title = newwin(1, 0, offset + view->height, 0);
2708                         if (!view->title)
2709                                 die("Failed to create title window");
2711                 } else {
2712                         wresize(view->win, view->height, view->width);
2713                         mvwin(view->win,   offset, 0);
2714                         mvwin(view->title, offset + view->height, 0);
2715                 }
2717                 offset += view->height + 1;
2718         }
2721 static void
2722 redraw_display(bool clear)
2724         struct view *view;
2725         int i;
2727         foreach_displayed_view (view, i) {
2728                 if (clear)
2729                         wclear(view->win);
2730                 redraw_view(view);
2731                 update_view_title(view);
2732         }
2736 /*
2737  * Option management
2738  */
2740 static void
2741 toggle_enum_option_do(unsigned int *opt, const char *help,
2742                       const struct enum_map *map, size_t size)
2744         *opt = (*opt + 1) % size;
2745         redraw_display(FALSE);
2746         report("Displaying %s %s", enum_name(map[*opt]), help);
2749 #define toggle_enum_option(opt, help, map) \
2750         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2752 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2753 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2755 static void
2756 toggle_view_option(bool *option, const char *help)
2758         *option = !*option;
2759         redraw_display(FALSE);
2760         report("%sabling %s", *option ? "En" : "Dis", help);
2763 static void
2764 open_option_menu(void)
2766         const struct menu_item menu[] = {
2767                 { '.', "line numbers", &opt_line_number },
2768                 { 'D', "date display", &opt_date },
2769                 { 'A', "author display", &opt_author },
2770                 { 'g', "revision graph display", &opt_rev_graph },
2771                 { 'F', "reference display", &opt_show_refs },
2772                 { 0 }
2773         };
2774         int selected = 0;
2776         if (prompt_menu("Toggle option", menu, &selected)) {
2777                 if (menu[selected].data == &opt_date)
2778                         toggle_date();
2779                 else if (menu[selected].data == &opt_author)
2780                         toggle_author();
2781                 else
2782                         toggle_view_option(menu[selected].data, menu[selected].text);
2783         }
2786 static void
2787 maximize_view(struct view *view)
2789         memset(display, 0, sizeof(display));
2790         current_view = 0;
2791         display[current_view] = view;
2792         resize_display();
2793         redraw_display(FALSE);
2794         report("");
2798 /*
2799  * Navigation
2800  */
2802 static bool
2803 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2805         if (lineno >= view->lines)
2806                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2808         if (offset > lineno || offset + view->height <= lineno) {
2809                 unsigned long half = view->height / 2;
2811                 if (lineno > half)
2812                         offset = lineno - half;
2813                 else
2814                         offset = 0;
2815         }
2817         if (offset != view->offset || lineno != view->lineno) {
2818                 view->offset = offset;
2819                 view->lineno = lineno;
2820                 return TRUE;
2821         }
2823         return FALSE;
2826 /* Scrolling backend */
2827 static void
2828 do_scroll_view(struct view *view, int lines)
2830         bool redraw_current_line = FALSE;
2832         /* The rendering expects the new offset. */
2833         view->offset += lines;
2835         assert(0 <= view->offset && view->offset < view->lines);
2836         assert(lines);
2838         /* Move current line into the view. */
2839         if (view->lineno < view->offset) {
2840                 view->lineno = view->offset;
2841                 redraw_current_line = TRUE;
2842         } else if (view->lineno >= view->offset + view->height) {
2843                 view->lineno = view->offset + view->height - 1;
2844                 redraw_current_line = TRUE;
2845         }
2847         assert(view->offset <= view->lineno && view->lineno < view->lines);
2849         /* Redraw the whole screen if scrolling is pointless. */
2850         if (view->height < ABS(lines)) {
2851                 redraw_view(view);
2853         } else {
2854                 int line = lines > 0 ? view->height - lines : 0;
2855                 int end = line + ABS(lines);
2857                 scrollok(view->win, TRUE);
2858                 wscrl(view->win, lines);
2859                 scrollok(view->win, FALSE);
2861                 while (line < end && draw_view_line(view, line))
2862                         line++;
2864                 if (redraw_current_line)
2865                         draw_view_line(view, view->lineno - view->offset);
2866                 wnoutrefresh(view->win);
2867         }
2869         view->has_scrolled = TRUE;
2870         report("");
2873 /* Scroll frontend */
2874 static void
2875 scroll_view(struct view *view, enum request request)
2877         int lines = 1;
2879         assert(view_is_displayed(view));
2881         switch (request) {
2882         case REQ_SCROLL_LEFT:
2883                 if (view->yoffset == 0) {
2884                         report("Cannot scroll beyond the first column");
2885                         return;
2886                 }
2887                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2888                         view->yoffset = 0;
2889                 else
2890                         view->yoffset -= apply_step(opt_hscroll, view->width);
2891                 redraw_view_from(view, 0);
2892                 report("");
2893                 return;
2894         case REQ_SCROLL_RIGHT:
2895                 view->yoffset += apply_step(opt_hscroll, view->width);
2896                 redraw_view(view);
2897                 report("");
2898                 return;
2899         case REQ_SCROLL_PAGE_DOWN:
2900                 lines = view->height;
2901         case REQ_SCROLL_LINE_DOWN:
2902                 if (view->offset + lines > view->lines)
2903                         lines = view->lines - view->offset;
2905                 if (lines == 0 || view->offset + view->height >= view->lines) {
2906                         report("Cannot scroll beyond the last line");
2907                         return;
2908                 }
2909                 break;
2911         case REQ_SCROLL_PAGE_UP:
2912                 lines = view->height;
2913         case REQ_SCROLL_LINE_UP:
2914                 if (lines > view->offset)
2915                         lines = view->offset;
2917                 if (lines == 0) {
2918                         report("Cannot scroll beyond the first line");
2919                         return;
2920                 }
2922                 lines = -lines;
2923                 break;
2925         default:
2926                 die("request %d not handled in switch", request);
2927         }
2929         do_scroll_view(view, lines);
2932 /* Cursor moving */
2933 static void
2934 move_view(struct view *view, enum request request)
2936         int scroll_steps = 0;
2937         int steps;
2939         switch (request) {
2940         case REQ_MOVE_FIRST_LINE:
2941                 steps = -view->lineno;
2942                 break;
2944         case REQ_MOVE_LAST_LINE:
2945                 steps = view->lines - view->lineno - 1;
2946                 break;
2948         case REQ_MOVE_PAGE_UP:
2949                 steps = view->height > view->lineno
2950                       ? -view->lineno : -view->height;
2951                 break;
2953         case REQ_MOVE_PAGE_DOWN:
2954                 steps = view->lineno + view->height >= view->lines
2955                       ? view->lines - view->lineno - 1 : view->height;
2956                 break;
2958         case REQ_MOVE_UP:
2959                 steps = -1;
2960                 break;
2962         case REQ_MOVE_DOWN:
2963                 steps = 1;
2964                 break;
2966         default:
2967                 die("request %d not handled in switch", request);
2968         }
2970         if (steps <= 0 && view->lineno == 0) {
2971                 report("Cannot move beyond the first line");
2972                 return;
2974         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2975                 report("Cannot move beyond the last line");
2976                 return;
2977         }
2979         /* Move the current line */
2980         view->lineno += steps;
2981         assert(0 <= view->lineno && view->lineno < view->lines);
2983         /* Check whether the view needs to be scrolled */
2984         if (view->lineno < view->offset ||
2985             view->lineno >= view->offset + view->height) {
2986                 scroll_steps = steps;
2987                 if (steps < 0 && -steps > view->offset) {
2988                         scroll_steps = -view->offset;
2990                 } else if (steps > 0) {
2991                         if (view->lineno == view->lines - 1 &&
2992                             view->lines > view->height) {
2993                                 scroll_steps = view->lines - view->offset - 1;
2994                                 if (scroll_steps >= view->height)
2995                                         scroll_steps -= view->height - 1;
2996                         }
2997                 }
2998         }
3000         if (!view_is_displayed(view)) {
3001                 view->offset += scroll_steps;
3002                 assert(0 <= view->offset && view->offset < view->lines);
3003                 view->ops->select(view, &view->line[view->lineno]);
3004                 return;
3005         }
3007         /* Repaint the old "current" line if we be scrolling */
3008         if (ABS(steps) < view->height)
3009                 draw_view_line(view, view->lineno - steps - view->offset);
3011         if (scroll_steps) {
3012                 do_scroll_view(view, scroll_steps);
3013                 return;
3014         }
3016         /* Draw the current line */
3017         draw_view_line(view, view->lineno - view->offset);
3019         wnoutrefresh(view->win);
3020         report("");
3024 /*
3025  * Searching
3026  */
3028 static void search_view(struct view *view, enum request request);
3030 static bool
3031 grep_text(struct view *view, const char *text[])
3033         regmatch_t pmatch;
3034         size_t i;
3036         for (i = 0; text[i]; i++)
3037                 if (*text[i] &&
3038                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3039                         return TRUE;
3040         return FALSE;
3043 static void
3044 select_view_line(struct view *view, unsigned long lineno)
3046         unsigned long old_lineno = view->lineno;
3047         unsigned long old_offset = view->offset;
3049         if (goto_view_line(view, view->offset, lineno)) {
3050                 if (view_is_displayed(view)) {
3051                         if (old_offset != view->offset) {
3052                                 redraw_view(view);
3053                         } else {
3054                                 draw_view_line(view, old_lineno - view->offset);
3055                                 draw_view_line(view, view->lineno - view->offset);
3056                                 wnoutrefresh(view->win);
3057                         }
3058                 } else {
3059                         view->ops->select(view, &view->line[view->lineno]);
3060                 }
3061         }
3064 static void
3065 find_next(struct view *view, enum request request)
3067         unsigned long lineno = view->lineno;
3068         int direction;
3070         if (!*view->grep) {
3071                 if (!*opt_search)
3072                         report("No previous search");
3073                 else
3074                         search_view(view, request);
3075                 return;
3076         }
3078         switch (request) {
3079         case REQ_SEARCH:
3080         case REQ_FIND_NEXT:
3081                 direction = 1;
3082                 break;
3084         case REQ_SEARCH_BACK:
3085         case REQ_FIND_PREV:
3086                 direction = -1;
3087                 break;
3089         default:
3090                 return;
3091         }
3093         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3094                 lineno += direction;
3096         /* Note, lineno is unsigned long so will wrap around in which case it
3097          * will become bigger than view->lines. */
3098         for (; lineno < view->lines; lineno += direction) {
3099                 if (view->ops->grep(view, &view->line[lineno])) {
3100                         select_view_line(view, lineno);
3101                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3102                         return;
3103                 }
3104         }
3106         report("No match found for '%s'", view->grep);
3109 static void
3110 search_view(struct view *view, enum request request)
3112         int regex_err;
3114         if (view->regex) {
3115                 regfree(view->regex);
3116                 *view->grep = 0;
3117         } else {
3118                 view->regex = calloc(1, sizeof(*view->regex));
3119                 if (!view->regex)
3120                         return;
3121         }
3123         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3124         if (regex_err != 0) {
3125                 char buf[SIZEOF_STR] = "unknown error";
3127                 regerror(regex_err, view->regex, buf, sizeof(buf));
3128                 report("Search failed: %s", buf);
3129                 return;
3130         }
3132         string_copy(view->grep, opt_search);
3134         find_next(view, request);
3137 /*
3138  * Incremental updating
3139  */
3141 static void
3142 reset_view(struct view *view)
3144         int i;
3146         for (i = 0; i < view->lines; i++)
3147                 free(view->line[i].data);
3148         free(view->line);
3150         view->p_offset = view->offset;
3151         view->p_yoffset = view->yoffset;
3152         view->p_lineno = view->lineno;
3154         view->line = NULL;
3155         view->offset = 0;
3156         view->yoffset = 0;
3157         view->lines  = 0;
3158         view->lineno = 0;
3159         view->vid[0] = 0;
3160         view->update_secs = 0;
3163 static const char *
3164 format_arg(const char *name)
3166         static struct {
3167                 const char *name;
3168                 size_t namelen;
3169                 const char *value;
3170                 const char *value_if_empty;
3171         } vars[] = {
3172 #define FORMAT_VAR(name, value, value_if_empty) \
3173         { name, STRING_SIZE(name), value, value_if_empty }
3174                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3175                 FORMAT_VAR("%(file)",           opt_file,       ""),
3176                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3177                 FORMAT_VAR("%(head)",           ref_head,       ""),
3178                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3179                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3180                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3181         };
3182         int i;
3184         for (i = 0; i < ARRAY_SIZE(vars); i++)
3185                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3186                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3188         report("Unknown replacement: `%s`", name);
3189         return NULL;
3192 static bool
3193 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3195         char buf[SIZEOF_STR];
3196         int argc;
3197         bool noreplace = flags == FORMAT_NONE;
3199         argv_free(dst_argv);
3201         for (argc = 0; src_argv[argc]; argc++) {
3202                 const char *arg = src_argv[argc];
3203                 size_t bufpos = 0;
3205                 while (arg) {
3206                         char *next = strstr(arg, "%(");
3207                         int len = next - arg;
3208                         const char *value;
3210                         if (!next || noreplace) {
3211                                 len = strlen(arg);
3212                                 value = "";
3214                         } else {
3215                                 value = format_arg(next);
3217                                 if (!value) {
3218                                         return FALSE;
3219                                 }
3220                         }
3222                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3223                                 return FALSE;
3225                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3226                 }
3228                 dst_argv[argc] = strdup(buf);
3229                 if (!dst_argv[argc])
3230                         break;
3231         }
3233         dst_argv[argc] = NULL;
3235         return src_argv[argc] == NULL;
3238 static bool
3239 restore_view_position(struct view *view)
3241         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3242                 return FALSE;
3244         /* Changing the view position cancels the restoring. */
3245         /* FIXME: Changing back to the first line is not detected. */
3246         if (view->offset != 0 || view->lineno != 0) {
3247                 view->p_restore = FALSE;
3248                 return FALSE;
3249         }
3251         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3252             view_is_displayed(view))
3253                 werase(view->win);
3255         view->yoffset = view->p_yoffset;
3256         view->p_restore = FALSE;
3258         return TRUE;
3261 static void
3262 end_update(struct view *view, bool force)
3264         if (!view->pipe)
3265                 return;
3266         while (!view->ops->read(view, NULL))
3267                 if (!force)
3268                         return;
3269         if (force)
3270                 io_kill(view->pipe);
3271         io_done(view->pipe);
3272         view->pipe = NULL;
3275 static void
3276 setup_update(struct view *view, const char *vid)
3278         reset_view(view);
3279         string_copy_rev(view->vid, vid);
3280         view->pipe = &view->io;
3281         view->start_time = time(NULL);
3284 static bool
3285 prepare_update(struct view *view, const char *argv[], const char *dir)
3287         if (view->pipe)
3288                 end_update(view, TRUE);
3289         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3292 static bool
3293 prepare_update_file(struct view *view, const char *name)
3295         if (view->pipe)
3296                 end_update(view, TRUE);
3297         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3300 static bool
3301 begin_update(struct view *view, bool refresh)
3303         if (view->pipe)
3304                 end_update(view, TRUE);
3306         if (!refresh) {
3307                 if (view->ops->prepare) {
3308                         if (!view->ops->prepare(view))
3309                                 return FALSE;
3310                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3311                         return FALSE;
3312                 }
3314                 /* Put the current ref_* value to the view title ref
3315                  * member. This is needed by the blob view. Most other
3316                  * views sets it automatically after loading because the
3317                  * first line is a commit line. */
3318                 string_copy_rev(view->ref, view->id);
3319         }
3321         if (!io_start(&view->io))
3322                 return FALSE;
3324         setup_update(view, view->id);
3326         return TRUE;
3329 static bool
3330 update_view(struct view *view)
3332         char out_buffer[BUFSIZ * 2];
3333         char *line;
3334         /* Clear the view and redraw everything since the tree sorting
3335          * might have rearranged things. */
3336         bool redraw = view->lines == 0;
3337         bool can_read = TRUE;
3339         if (!view->pipe)
3340                 return TRUE;
3342         if (!io_can_read(view->pipe)) {
3343                 if (view->lines == 0 && view_is_displayed(view)) {
3344                         time_t secs = time(NULL) - view->start_time;
3346                         if (secs > 1 && secs > view->update_secs) {
3347                                 if (view->update_secs == 0)
3348                                         redraw_view(view);
3349                                 update_view_title(view);
3350                                 view->update_secs = secs;
3351                         }
3352                 }
3353                 return TRUE;
3354         }
3356         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3357                 if (opt_iconv_in != ICONV_NONE) {
3358                         ICONV_CONST char *inbuf = line;
3359                         size_t inlen = strlen(line) + 1;
3361                         char *outbuf = out_buffer;
3362                         size_t outlen = sizeof(out_buffer);
3364                         size_t ret;
3366                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3367                         if (ret != (size_t) -1)
3368                                 line = out_buffer;
3369                 }
3371                 if (!view->ops->read(view, line)) {
3372                         report("Allocation failure");
3373                         end_update(view, TRUE);
3374                         return FALSE;
3375                 }
3376         }
3378         {
3379                 unsigned long lines = view->lines;
3380                 int digits;
3382                 for (digits = 0; lines; digits++)
3383                         lines /= 10;
3385                 /* Keep the displayed view in sync with line number scaling. */
3386                 if (digits != view->digits) {
3387                         view->digits = digits;
3388                         if (opt_line_number || view->type == VIEW_BLAME)
3389                                 redraw = TRUE;
3390                 }
3391         }
3393         if (io_error(view->pipe)) {
3394                 report("Failed to read: %s", io_strerror(view->pipe));
3395                 end_update(view, TRUE);
3397         } else if (io_eof(view->pipe)) {
3398                 if (view_is_displayed(view))
3399                         report("");
3400                 end_update(view, FALSE);
3401         }
3403         if (restore_view_position(view))
3404                 redraw = TRUE;
3406         if (!view_is_displayed(view))
3407                 return TRUE;
3409         if (redraw)
3410                 redraw_view_from(view, 0);
3411         else
3412                 redraw_view_dirty(view);
3414         /* Update the title _after_ the redraw so that if the redraw picks up a
3415          * commit reference in view->ref it'll be available here. */
3416         update_view_title(view);
3417         return TRUE;
3420 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3422 static struct line *
3423 add_line_data(struct view *view, void *data, enum line_type type)
3425         struct line *line;
3427         if (!realloc_lines(&view->line, view->lines, 1))
3428                 return NULL;
3430         line = &view->line[view->lines++];
3431         memset(line, 0, sizeof(*line));
3432         line->type = type;
3433         line->data = data;
3434         line->dirty = 1;
3436         return line;
3439 static struct line *
3440 add_line_text(struct view *view, const char *text, enum line_type type)
3442         char *data = text ? strdup(text) : NULL;
3444         return data ? add_line_data(view, data, type) : NULL;
3447 static struct line *
3448 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3450         char buf[SIZEOF_STR];
3451         va_list args;
3453         va_start(args, fmt);
3454         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3455                 buf[0] = 0;
3456         va_end(args);
3458         return buf[0] ? add_line_text(view, buf, type) : NULL;
3461 /*
3462  * View opening
3463  */
3465 enum open_flags {
3466         OPEN_DEFAULT = 0,       /* Use default view switching. */
3467         OPEN_SPLIT = 1,         /* Split current view. */
3468         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3469         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3470         OPEN_PREPARED = 32,     /* Open already prepared command. */
3471 };
3473 static void
3474 open_view(struct view *prev, enum request request, enum open_flags flags)
3476         bool split = !!(flags & OPEN_SPLIT);
3477         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3478         bool nomaximize = !!(flags & OPEN_REFRESH);
3479         struct view *view = VIEW(request);
3480         int nviews = displayed_views();
3481         struct view *base_view = display[0];
3483         if (view == prev && nviews == 1 && !reload) {
3484                 report("Already in %s view", view->name);
3485                 return;
3486         }
3488         if (view->git_dir && !opt_git_dir[0]) {
3489                 report("The %s view is disabled in pager view", view->name);
3490                 return;
3491         }
3493         if (split) {
3494                 display[1] = view;
3495                 current_view = 1;
3496                 view->parent = prev;
3497         } else if (!nomaximize) {
3498                 /* Maximize the current view. */
3499                 memset(display, 0, sizeof(display));
3500                 current_view = 0;
3501                 display[current_view] = view;
3502         }
3504         /* No prev signals that this is the first loaded view. */
3505         if (prev && view != prev) {
3506                 view->prev = prev;
3507         }
3509         /* Resize the view when switching between split- and full-screen,
3510          * or when switching between two different full-screen views. */
3511         if (nviews != displayed_views() ||
3512             (nviews == 1 && base_view != display[0]))
3513                 resize_display();
3515         if (view->ops->open) {
3516                 if (view->pipe)
3517                         end_update(view, TRUE);
3518                 if (!view->ops->open(view)) {
3519                         report("Failed to load %s view", view->name);
3520                         return;
3521                 }
3522                 restore_view_position(view);
3524         } else if ((reload || strcmp(view->vid, view->id)) &&
3525                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3526                 report("Failed to load %s view", view->name);
3527                 return;
3528         }
3530         if (split && prev->lineno - prev->offset >= prev->height) {
3531                 /* Take the title line into account. */
3532                 int lines = prev->lineno - prev->offset - prev->height + 1;
3534                 /* Scroll the view that was split if the current line is
3535                  * outside the new limited view. */
3536                 do_scroll_view(prev, lines);
3537         }
3539         if (prev && view != prev && split && view_is_displayed(prev)) {
3540                 /* "Blur" the previous view. */
3541                 update_view_title(prev);
3542         }
3544         if (view->pipe && view->lines == 0) {
3545                 /* Clear the old view and let the incremental updating refill
3546                  * the screen. */
3547                 werase(view->win);
3548                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3549                 report("");
3550         } else if (view_is_displayed(view)) {
3551                 redraw_view(view);
3552                 report("");
3553         }
3556 static void
3557 open_external_viewer(const char *argv[], const char *dir)
3559         def_prog_mode();           /* save current tty modes */
3560         endwin();                  /* restore original tty modes */
3561         io_run_fg(argv, dir);
3562         fprintf(stderr, "Press Enter to continue");
3563         getc(opt_tty);
3564         reset_prog_mode();
3565         redraw_display(TRUE);
3568 static void
3569 open_mergetool(const char *file)
3571         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3573         open_external_viewer(mergetool_argv, opt_cdup);
3576 static void
3577 open_editor(const char *file)
3579         const char *editor_argv[] = { "vi", file, NULL };
3580         const char *editor;
3582         editor = getenv("GIT_EDITOR");
3583         if (!editor && *opt_editor)
3584                 editor = opt_editor;
3585         if (!editor)
3586                 editor = getenv("VISUAL");
3587         if (!editor)
3588                 editor = getenv("EDITOR");
3589         if (!editor)
3590                 editor = "vi";
3592         editor_argv[0] = editor;
3593         open_external_viewer(editor_argv, opt_cdup);
3596 static void
3597 open_run_request(enum request request)
3599         struct run_request *req = get_run_request(request);
3600         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3602         if (!req) {
3603                 report("Unknown run request");
3604                 return;
3605         }
3607         if (format_argv(argv, req->argv, FORMAT_ALL))
3608                 open_external_viewer(argv, NULL);
3609         argv_free(argv);
3612 /*
3613  * User request switch noodle
3614  */
3616 static int
3617 view_driver(struct view *view, enum request request)
3619         int i;
3621         if (request == REQ_NONE)
3622                 return TRUE;
3624         if (request > REQ_NONE) {
3625                 open_run_request(request);
3626                 view_request(view, REQ_REFRESH);
3627                 return TRUE;
3628         }
3630         request = view_request(view, request);
3631         if (request == REQ_NONE)
3632                 return TRUE;
3634         switch (request) {
3635         case REQ_MOVE_UP:
3636         case REQ_MOVE_DOWN:
3637         case REQ_MOVE_PAGE_UP:
3638         case REQ_MOVE_PAGE_DOWN:
3639         case REQ_MOVE_FIRST_LINE:
3640         case REQ_MOVE_LAST_LINE:
3641                 move_view(view, request);
3642                 break;
3644         case REQ_SCROLL_LEFT:
3645         case REQ_SCROLL_RIGHT:
3646         case REQ_SCROLL_LINE_DOWN:
3647         case REQ_SCROLL_LINE_UP:
3648         case REQ_SCROLL_PAGE_DOWN:
3649         case REQ_SCROLL_PAGE_UP:
3650                 scroll_view(view, request);
3651                 break;
3653         case REQ_VIEW_BLAME:
3654                 if (!opt_file[0]) {
3655                         report("No file chosen, press %s to open tree view",
3656                                get_key(view->keymap, REQ_VIEW_TREE));
3657                         break;
3658                 }
3659                 open_view(view, request, OPEN_DEFAULT);
3660                 break;
3662         case REQ_VIEW_BLOB:
3663                 if (!ref_blob[0]) {
3664                         report("No file chosen, press %s to open tree view",
3665                                get_key(view->keymap, REQ_VIEW_TREE));
3666                         break;
3667                 }
3668                 open_view(view, request, OPEN_DEFAULT);
3669                 break;
3671         case REQ_VIEW_PAGER:
3672                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3673                         report("No pager content, press %s to run command from prompt",
3674                                get_key(view->keymap, REQ_PROMPT));
3675                         break;
3676                 }
3677                 open_view(view, request, OPEN_DEFAULT);
3678                 break;
3680         case REQ_VIEW_STAGE:
3681                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3682                         report("No stage content, press %s to open the status view and choose file",
3683                                get_key(view->keymap, REQ_VIEW_STATUS));
3684                         break;
3685                 }
3686                 open_view(view, request, OPEN_DEFAULT);
3687                 break;
3689         case REQ_VIEW_STATUS:
3690                 if (opt_is_inside_work_tree == FALSE) {
3691                         report("The status view requires a working tree");
3692                         break;
3693                 }
3694                 open_view(view, request, OPEN_DEFAULT);
3695                 break;
3697         case REQ_VIEW_MAIN:
3698         case REQ_VIEW_DIFF:
3699         case REQ_VIEW_LOG:
3700         case REQ_VIEW_TREE:
3701         case REQ_VIEW_HELP:
3702         case REQ_VIEW_BRANCH:
3703                 open_view(view, request, OPEN_DEFAULT);
3704                 break;
3706         case REQ_NEXT:
3707         case REQ_PREVIOUS:
3708                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3710                 if (view->parent) {
3711                         int line;
3713                         view = view->parent;
3714                         line = view->lineno;
3715                         move_view(view, request);
3716                         if (view_is_displayed(view))
3717                                 update_view_title(view);
3718                         if (line != view->lineno)
3719                                 view_request(view, REQ_ENTER);
3720                 } else {
3721                         move_view(view, request);
3722                 }
3723                 break;
3725         case REQ_VIEW_NEXT:
3726         {
3727                 int nviews = displayed_views();
3728                 int next_view = (current_view + 1) % nviews;
3730                 if (next_view == current_view) {
3731                         report("Only one view is displayed");
3732                         break;
3733                 }
3735                 current_view = next_view;
3736                 /* Blur out the title of the previous view. */
3737                 update_view_title(view);
3738                 report("");
3739                 break;
3740         }
3741         case REQ_REFRESH:
3742                 report("Refreshing is not yet supported for the %s view", view->name);
3743                 break;
3745         case REQ_MAXIMIZE:
3746                 if (displayed_views() == 2)
3747                         maximize_view(view);
3748                 break;
3750         case REQ_OPTIONS:
3751                 open_option_menu();
3752                 break;
3754         case REQ_TOGGLE_LINENO:
3755                 toggle_view_option(&opt_line_number, "line numbers");
3756                 break;
3758         case REQ_TOGGLE_DATE:
3759                 toggle_date();
3760                 break;
3762         case REQ_TOGGLE_AUTHOR:
3763                 toggle_author();
3764                 break;
3766         case REQ_TOGGLE_REV_GRAPH:
3767                 toggle_view_option(&opt_rev_graph, "revision graph display");
3768                 break;
3770         case REQ_TOGGLE_REFS:
3771                 toggle_view_option(&opt_show_refs, "reference display");
3772                 break;
3774         case REQ_TOGGLE_SORT_FIELD:
3775         case REQ_TOGGLE_SORT_ORDER:
3776                 report("Sorting is not yet supported for the %s view", view->name);
3777                 break;
3779         case REQ_SEARCH:
3780         case REQ_SEARCH_BACK:
3781                 search_view(view, request);
3782                 break;
3784         case REQ_FIND_NEXT:
3785         case REQ_FIND_PREV:
3786                 find_next(view, request);
3787                 break;
3789         case REQ_STOP_LOADING:
3790                 foreach_view(view, i) {
3791                         if (view->pipe)
3792                                 report("Stopped loading the %s view", view->name),
3793                         end_update(view, TRUE);
3794                 }
3795                 break;
3797         case REQ_SHOW_VERSION:
3798                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3799                 return TRUE;
3801         case REQ_SCREEN_REDRAW:
3802                 redraw_display(TRUE);
3803                 break;
3805         case REQ_EDIT:
3806                 report("Nothing to edit");
3807                 break;
3809         case REQ_ENTER:
3810                 report("Nothing to enter");
3811                 break;
3813         case REQ_VIEW_CLOSE:
3814                 /* XXX: Mark closed views by letting view->prev point to the
3815                  * view itself. Parents to closed view should never be
3816                  * followed. */
3817                 if (view->prev && view->prev != view) {
3818                         maximize_view(view->prev);
3819                         view->prev = view;
3820                         break;
3821                 }
3822                 /* Fall-through */
3823         case REQ_QUIT:
3824                 return FALSE;
3826         default:
3827                 report("Unknown key, press %s for help",
3828                        get_key(view->keymap, REQ_VIEW_HELP));
3829                 return TRUE;
3830         }
3832         return TRUE;
3836 /*
3837  * View backend utilities
3838  */
3840 enum sort_field {
3841         ORDERBY_NAME,
3842         ORDERBY_DATE,
3843         ORDERBY_AUTHOR,
3844 };
3846 struct sort_state {
3847         const enum sort_field *fields;
3848         size_t size, current;
3849         bool reverse;
3850 };
3852 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3853 #define get_sort_field(state) ((state).fields[(state).current])
3854 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3856 static void
3857 sort_view(struct view *view, enum request request, struct sort_state *state,
3858           int (*compare)(const void *, const void *))
3860         switch (request) {
3861         case REQ_TOGGLE_SORT_FIELD:
3862                 state->current = (state->current + 1) % state->size;
3863                 break;
3865         case REQ_TOGGLE_SORT_ORDER:
3866                 state->reverse = !state->reverse;
3867                 break;
3868         default:
3869                 die("Not a sort request");
3870         }
3872         qsort(view->line, view->lines, sizeof(*view->line), compare);
3873         redraw_view(view);
3876 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3878 /* Small author cache to reduce memory consumption. It uses binary
3879  * search to lookup or find place to position new entries. No entries
3880  * are ever freed. */
3881 static const char *
3882 get_author(const char *name)
3884         static const char **authors;
3885         static size_t authors_size;
3886         int from = 0, to = authors_size - 1;
3888         while (from <= to) {
3889                 size_t pos = (to + from) / 2;
3890                 int cmp = strcmp(name, authors[pos]);
3892                 if (!cmp)
3893                         return authors[pos];
3895                 if (cmp < 0)
3896                         to = pos - 1;
3897                 else
3898                         from = pos + 1;
3899         }
3901         if (!realloc_authors(&authors, authors_size, 1))
3902                 return NULL;
3903         name = strdup(name);
3904         if (!name)
3905                 return NULL;
3907         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3908         authors[from] = name;
3909         authors_size++;
3911         return name;
3914 static void
3915 parse_timesec(struct time *time, const char *sec)
3917         time->sec = (time_t) atol(sec);
3920 static void
3921 parse_timezone(struct time *time, const char *zone)
3923         long tz;
3925         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3926         tz += ('0' - zone[2]) * 60 * 60;
3927         tz += ('0' - zone[3]) * 60 * 10;
3928         tz += ('0' - zone[4]) * 60;
3930         if (zone[0] == '-')
3931                 tz = -tz;
3933         time->tz = tz;
3934         time->sec -= tz;
3937 /* Parse author lines where the name may be empty:
3938  *      author  <email@address.tld> 1138474660 +0100
3939  */
3940 static void
3941 parse_author_line(char *ident, const char **author, struct time *time)
3943         char *nameend = strchr(ident, '<');
3944         char *emailend = strchr(ident, '>');
3946         if (nameend && emailend)
3947                 *nameend = *emailend = 0;
3948         ident = chomp_string(ident);
3949         if (!*ident) {
3950                 if (nameend)
3951                         ident = chomp_string(nameend + 1);
3952                 if (!*ident)
3953                         ident = "Unknown";
3954         }
3956         *author = get_author(ident);
3958         /* Parse epoch and timezone */
3959         if (emailend && emailend[1] == ' ') {
3960                 char *secs = emailend + 2;
3961                 char *zone = strchr(secs, ' ');
3963                 parse_timesec(time, secs);
3965                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3966                         parse_timezone(time, zone + 1);
3967         }
3970 static bool
3971 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3973         char rev[SIZEOF_REV];
3974         const char *revlist_argv[] = {
3975                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3976         };
3977         struct menu_item *items;
3978         char text[SIZEOF_STR];
3979         bool ok = TRUE;
3980         int i;
3982         items = calloc(*parents + 1, sizeof(*items));
3983         if (!items)
3984                 return FALSE;
3986         for (i = 0; i < *parents; i++) {
3987                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3988                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3989                     !(items[i].text = strdup(text))) {
3990                         ok = FALSE;
3991                         break;
3992                 }
3993         }
3995         if (ok) {
3996                 *parents = 0;
3997                 ok = prompt_menu("Select parent", items, parents);
3998         }
3999         for (i = 0; items[i].text; i++)
4000                 free((char *) items[i].text);
4001         free(items);
4002         return ok;
4005 static bool
4006 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4008         char buf[SIZEOF_STR * 4];
4009         const char *revlist_argv[] = {
4010                 "git", "log", "--no-color", "-1",
4011                         "--pretty=format:%P", id, "--", path, NULL
4012         };
4013         int parents;
4015         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4016             (parents = strlen(buf) / 40) < 0) {
4017                 report("Failed to get parent information");
4018                 return FALSE;
4020         } else if (parents == 0) {
4021                 if (path)
4022                         report("Path '%s' does not exist in the parent", path);
4023                 else
4024                         report("The selected commit has no parents");
4025                 return FALSE;
4026         }
4028         if (parents == 1)
4029                 parents = 0;
4030         else if (!open_commit_parent_menu(buf, &parents))
4031                 return FALSE;
4033         string_copy_rev(rev, &buf[41 * parents]);
4034         return TRUE;
4037 /*
4038  * Pager backend
4039  */
4041 static bool
4042 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4044         char text[SIZEOF_STR];
4046         if (opt_line_number && draw_lineno(view, lineno))
4047                 return TRUE;
4049         string_expand(text, sizeof(text), line->data, opt_tab_size);
4050         draw_text(view, line->type, text, TRUE);
4051         return TRUE;
4054 static bool
4055 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4057         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4058         char ref[SIZEOF_STR];
4060         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4061                 return TRUE;
4063         /* This is the only fatal call, since it can "corrupt" the buffer. */
4064         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4065                 return FALSE;
4067         return TRUE;
4070 static void
4071 add_pager_refs(struct view *view, struct line *line)
4073         char buf[SIZEOF_STR];
4074         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4075         struct ref_list *list;
4076         size_t bufpos = 0, i;
4077         const char *sep = "Refs: ";
4078         bool is_tag = FALSE;
4080         assert(line->type == LINE_COMMIT);
4082         list = get_ref_list(commit_id);
4083         if (!list) {
4084                 if (view->type == VIEW_DIFF)
4085                         goto try_add_describe_ref;
4086                 return;
4087         }
4089         for (i = 0; i < list->size; i++) {
4090                 struct ref *ref = list->refs[i];
4091                 const char *fmt = ref->tag    ? "%s[%s]" :
4092                                   ref->remote ? "%s<%s>" : "%s%s";
4094                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4095                         return;
4096                 sep = ", ";
4097                 if (ref->tag)
4098                         is_tag = TRUE;
4099         }
4101         if (!is_tag && view->type == VIEW_DIFF) {
4102 try_add_describe_ref:
4103                 /* Add <tag>-g<commit_id> "fake" reference. */
4104                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4105                         return;
4106         }
4108         if (bufpos == 0)
4109                 return;
4111         add_line_text(view, buf, LINE_PP_REFS);
4114 static bool
4115 pager_read(struct view *view, char *data)
4117         struct line *line;
4119         if (!data)
4120                 return TRUE;
4122         line = add_line_text(view, data, get_line_type(data));
4123         if (!line)
4124                 return FALSE;
4126         if (line->type == LINE_COMMIT &&
4127             (view->type == VIEW_DIFF ||
4128              view->type == VIEW_LOG))
4129                 add_pager_refs(view, line);
4131         return TRUE;
4134 static enum request
4135 pager_request(struct view *view, enum request request, struct line *line)
4137         int split = 0;
4139         if (request != REQ_ENTER)
4140                 return request;
4142         if (line->type == LINE_COMMIT &&
4143            (view->type == VIEW_LOG ||
4144             view->type == VIEW_PAGER)) {
4145                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4146                 split = 1;
4147         }
4149         /* Always scroll the view even if it was split. That way
4150          * you can use Enter to scroll through the log view and
4151          * split open each commit diff. */
4152         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4154         /* FIXME: A minor workaround. Scrolling the view will call report("")
4155          * but if we are scrolling a non-current view this won't properly
4156          * update the view title. */
4157         if (split)
4158                 update_view_title(view);
4160         return REQ_NONE;
4163 static bool
4164 pager_grep(struct view *view, struct line *line)
4166         const char *text[] = { line->data, NULL };
4168         return grep_text(view, text);
4171 static void
4172 pager_select(struct view *view, struct line *line)
4174         if (line->type == LINE_COMMIT) {
4175                 char *text = (char *)line->data + STRING_SIZE("commit ");
4177                 if (view->type != VIEW_PAGER)
4178                         string_copy_rev(view->ref, text);
4179                 string_copy_rev(ref_commit, text);
4180         }
4183 static struct view_ops pager_ops = {
4184         "line",
4185         NULL,
4186         NULL,
4187         pager_read,
4188         pager_draw,
4189         pager_request,
4190         pager_grep,
4191         pager_select,
4192 };
4194 static const char *log_argv[SIZEOF_ARG] = {
4195         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4196 };
4198 static enum request
4199 log_request(struct view *view, enum request request, struct line *line)
4201         switch (request) {
4202         case REQ_REFRESH:
4203                 load_refs();
4204                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4205                 return REQ_NONE;
4206         default:
4207                 return pager_request(view, request, line);
4208         }
4211 static struct view_ops log_ops = {
4212         "line",
4213         log_argv,
4214         NULL,
4215         pager_read,
4216         pager_draw,
4217         log_request,
4218         pager_grep,
4219         pager_select,
4220 };
4222 static const char *diff_argv[SIZEOF_ARG] = {
4223         "git", "show", "--pretty=fuller", "--no-color", "--root",
4224                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4225 };
4227 static struct view_ops diff_ops = {
4228         "line",
4229         diff_argv,
4230         NULL,
4231         pager_read,
4232         pager_draw,
4233         pager_request,
4234         pager_grep,
4235         pager_select,
4236 };
4238 /*
4239  * Help backend
4240  */
4242 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4244 static bool
4245 help_open_keymap_title(struct view *view, enum keymap keymap)
4247         struct line *line;
4249         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4250                                help_keymap_hidden[keymap] ? '+' : '-',
4251                                enum_name(keymap_table[keymap]));
4252         if (line)
4253                 line->other = keymap;
4255         return help_keymap_hidden[keymap];
4258 static void
4259 help_open_keymap(struct view *view, enum keymap keymap)
4261         const char *group = NULL;
4262         char buf[SIZEOF_STR];
4263         size_t bufpos;
4264         bool add_title = TRUE;
4265         int i;
4267         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4268                 const char *key = NULL;
4270                 if (req_info[i].request == REQ_NONE)
4271                         continue;
4273                 if (!req_info[i].request) {
4274                         group = req_info[i].help;
4275                         continue;
4276                 }
4278                 key = get_keys(keymap, req_info[i].request, TRUE);
4279                 if (!key || !*key)
4280                         continue;
4282                 if (add_title && help_open_keymap_title(view, keymap))
4283                         return;
4284                 add_title = FALSE;
4286                 if (group) {
4287                         add_line_text(view, group, LINE_HELP_GROUP);
4288                         group = NULL;
4289                 }
4291                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4292                                 enum_name(req_info[i]), req_info[i].help);
4293         }
4295         group = "External commands:";
4297         for (i = 0; i < run_requests; i++) {
4298                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4299                 const char *key;
4300                 int argc;
4302                 if (!req || req->keymap != keymap)
4303                         continue;
4305                 key = get_key_name(req->key);
4306                 if (!*key)
4307                         key = "(no key defined)";
4309                 if (add_title && help_open_keymap_title(view, keymap))
4310                         return;
4311                 if (group) {
4312                         add_line_text(view, group, LINE_HELP_GROUP);
4313                         group = NULL;
4314                 }
4316                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4317                         if (!string_format_from(buf, &bufpos, "%s%s",
4318                                                 argc ? " " : "", req->argv[argc]))
4319                                 return;
4321                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4322         }
4325 static bool
4326 help_open(struct view *view)
4328         enum keymap keymap;
4330         reset_view(view);
4331         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4332         add_line_text(view, "", LINE_DEFAULT);
4334         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4335                 help_open_keymap(view, keymap);
4337         return TRUE;
4340 static enum request
4341 help_request(struct view *view, enum request request, struct line *line)
4343         switch (request) {
4344         case REQ_ENTER:
4345                 if (line->type == LINE_HELP_KEYMAP) {
4346                         help_keymap_hidden[line->other] =
4347                                 !help_keymap_hidden[line->other];
4348                         view->p_restore = TRUE;
4349                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4350                 }
4352                 return REQ_NONE;
4353         default:
4354                 return pager_request(view, request, line);
4355         }
4358 static struct view_ops help_ops = {
4359         "line",
4360         NULL,
4361         help_open,
4362         NULL,
4363         pager_draw,
4364         help_request,
4365         pager_grep,
4366         pager_select,
4367 };
4370 /*
4371  * Tree backend
4372  */
4374 struct tree_stack_entry {
4375         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4376         unsigned long lineno;           /* Line number to restore */
4377         char *name;                     /* Position of name in opt_path */
4378 };
4380 /* The top of the path stack. */
4381 static struct tree_stack_entry *tree_stack = NULL;
4382 unsigned long tree_lineno = 0;
4384 static void
4385 pop_tree_stack_entry(void)
4387         struct tree_stack_entry *entry = tree_stack;
4389         tree_lineno = entry->lineno;
4390         entry->name[0] = 0;
4391         tree_stack = entry->prev;
4392         free(entry);
4395 static void
4396 push_tree_stack_entry(const char *name, unsigned long lineno)
4398         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4399         size_t pathlen = strlen(opt_path);
4401         if (!entry)
4402                 return;
4404         entry->prev = tree_stack;
4405         entry->name = opt_path + pathlen;
4406         tree_stack = entry;
4408         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4409                 pop_tree_stack_entry();
4410                 return;
4411         }
4413         /* Move the current line to the first tree entry. */
4414         tree_lineno = 1;
4415         entry->lineno = lineno;
4418 /* Parse output from git-ls-tree(1):
4419  *
4420  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4421  */
4423 #define SIZEOF_TREE_ATTR \
4424         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4426 #define SIZEOF_TREE_MODE \
4427         STRING_SIZE("100644 ")
4429 #define TREE_ID_OFFSET \
4430         STRING_SIZE("100644 blob ")
4432 struct tree_entry {
4433         char id[SIZEOF_REV];
4434         mode_t mode;
4435         struct time time;               /* Date from the author ident. */
4436         const char *author;             /* Author of the commit. */
4437         char name[1];
4438 };
4440 static const char *
4441 tree_path(const struct line *line)
4443         return ((struct tree_entry *) line->data)->name;
4446 static int
4447 tree_compare_entry(const struct line *line1, const struct line *line2)
4449         if (line1->type != line2->type)
4450                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4451         return strcmp(tree_path(line1), tree_path(line2));
4454 static const enum sort_field tree_sort_fields[] = {
4455         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4456 };
4457 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4459 static int
4460 tree_compare(const void *l1, const void *l2)
4462         const struct line *line1 = (const struct line *) l1;
4463         const struct line *line2 = (const struct line *) l2;
4464         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4465         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4467         if (line1->type == LINE_TREE_HEAD)
4468                 return -1;
4469         if (line2->type == LINE_TREE_HEAD)
4470                 return 1;
4472         switch (get_sort_field(tree_sort_state)) {
4473         case ORDERBY_DATE:
4474                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4476         case ORDERBY_AUTHOR:
4477                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4479         case ORDERBY_NAME:
4480         default:
4481                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4482         }
4486 static struct line *
4487 tree_entry(struct view *view, enum line_type type, const char *path,
4488            const char *mode, const char *id)
4490         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4491         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4493         if (!entry || !line) {
4494                 free(entry);
4495                 return NULL;
4496         }
4498         strncpy(entry->name, path, strlen(path));
4499         if (mode)
4500                 entry->mode = strtoul(mode, NULL, 8);
4501         if (id)
4502                 string_copy_rev(entry->id, id);
4504         return line;
4507 static bool
4508 tree_read_date(struct view *view, char *text, bool *read_date)
4510         static const char *author_name;
4511         static struct time author_time;
4513         if (!text && *read_date) {
4514                 *read_date = FALSE;
4515                 return TRUE;
4517         } else if (!text) {
4518                 char *path = *opt_path ? opt_path : ".";
4519                 /* Find next entry to process */
4520                 const char *log_file[] = {
4521                         "git", "log", "--no-color", "--pretty=raw",
4522                                 "--cc", "--raw", view->id, "--", path, NULL
4523                 };
4524                 struct io io = {};
4526                 if (!view->lines) {
4527                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4528                         report("Tree is empty");
4529                         return TRUE;
4530                 }
4532                 if (!io_run_rd(&io, log_file, opt_cdup)) {
4533                         report("Failed to load tree data");
4534                         return TRUE;
4535                 }
4537                 io_done(view->pipe);
4538                 view->io = io;
4539                 *read_date = TRUE;
4540                 return FALSE;
4542         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4543                 parse_author_line(text + STRING_SIZE("author "),
4544                                   &author_name, &author_time);
4546         } else if (*text == ':') {
4547                 char *pos;
4548                 size_t annotated = 1;
4549                 size_t i;
4551                 pos = strchr(text, '\t');
4552                 if (!pos)
4553                         return TRUE;
4554                 text = pos + 1;
4555                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4556                         text += strlen(opt_path);
4557                 pos = strchr(text, '/');
4558                 if (pos)
4559                         *pos = 0;
4561                 for (i = 1; i < view->lines; i++) {
4562                         struct line *line = &view->line[i];
4563                         struct tree_entry *entry = line->data;
4565                         annotated += !!entry->author;
4566                         if (entry->author || strcmp(entry->name, text))
4567                                 continue;
4569                         entry->author = author_name;
4570                         entry->time = author_time;
4571                         line->dirty = 1;
4572                         break;
4573                 }
4575                 if (annotated == view->lines)
4576                         io_kill(view->pipe);
4577         }
4578         return TRUE;
4581 static bool
4582 tree_read(struct view *view, char *text)
4584         static bool read_date = FALSE;
4585         struct tree_entry *data;
4586         struct line *entry, *line;
4587         enum line_type type;
4588         size_t textlen = text ? strlen(text) : 0;
4589         char *path = text + SIZEOF_TREE_ATTR;
4591         if (read_date || !text)
4592                 return tree_read_date(view, text, &read_date);
4594         if (textlen <= SIZEOF_TREE_ATTR)
4595                 return FALSE;
4596         if (view->lines == 0 &&
4597             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4598                 return FALSE;
4600         /* Strip the path part ... */
4601         if (*opt_path) {
4602                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4603                 size_t striplen = strlen(opt_path);
4605                 if (pathlen > striplen)
4606                         memmove(path, path + striplen,
4607                                 pathlen - striplen + 1);
4609                 /* Insert "link" to parent directory. */
4610                 if (view->lines == 1 &&
4611                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4612                         return FALSE;
4613         }
4615         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4616         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4617         if (!entry)
4618                 return FALSE;
4619         data = entry->data;
4621         /* Skip "Directory ..." and ".." line. */
4622         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4623                 if (tree_compare_entry(line, entry) <= 0)
4624                         continue;
4626                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4628                 line->data = data;
4629                 line->type = type;
4630                 for (; line <= entry; line++)
4631                         line->dirty = line->cleareol = 1;
4632                 return TRUE;
4633         }
4635         if (tree_lineno > view->lineno) {
4636                 view->lineno = tree_lineno;
4637                 tree_lineno = 0;
4638         }
4640         return TRUE;
4643 static bool
4644 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4646         struct tree_entry *entry = line->data;
4648         if (line->type == LINE_TREE_HEAD) {
4649                 if (draw_text(view, line->type, "Directory path /", TRUE))
4650                         return TRUE;
4651         } else {
4652                 if (draw_mode(view, entry->mode))
4653                         return TRUE;
4655                 if (opt_author && draw_author(view, entry->author))
4656                         return TRUE;
4658                 if (opt_date && draw_date(view, &entry->time))
4659                         return TRUE;
4660         }
4661         if (draw_text(view, line->type, entry->name, TRUE))
4662                 return TRUE;
4663         return TRUE;
4666 static void
4667 open_blob_editor(const char *id)
4669         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4670         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4671         int fd = mkstemp(file);
4673         if (fd == -1)
4674                 report("Failed to create temporary file");
4675         else if (!io_run_append(blob_argv, fd))
4676                 report("Failed to save blob data to file");
4677         else
4678                 open_editor(file);
4679         if (fd != -1)
4680                 unlink(file);
4683 static enum request
4684 tree_request(struct view *view, enum request request, struct line *line)
4686         enum open_flags flags;
4687         struct tree_entry *entry = line->data;
4689         switch (request) {
4690         case REQ_VIEW_BLAME:
4691                 if (line->type != LINE_TREE_FILE) {
4692                         report("Blame only supported for files");
4693                         return REQ_NONE;
4694                 }
4696                 string_copy(opt_ref, view->vid);
4697                 return request;
4699         case REQ_EDIT:
4700                 if (line->type != LINE_TREE_FILE) {
4701                         report("Edit only supported for files");
4702                 } else if (!is_head_commit(view->vid)) {
4703                         open_blob_editor(entry->id);
4704                 } else {
4705                         open_editor(opt_file);
4706                 }
4707                 return REQ_NONE;
4709         case REQ_TOGGLE_SORT_FIELD:
4710         case REQ_TOGGLE_SORT_ORDER:
4711                 sort_view(view, request, &tree_sort_state, tree_compare);
4712                 return REQ_NONE;
4714         case REQ_PARENT:
4715                 if (!*opt_path) {
4716                         /* quit view if at top of tree */
4717                         return REQ_VIEW_CLOSE;
4718                 }
4719                 /* fake 'cd  ..' */
4720                 line = &view->line[1];
4721                 break;
4723         case REQ_ENTER:
4724                 break;
4726         default:
4727                 return request;
4728         }
4730         /* Cleanup the stack if the tree view is at a different tree. */
4731         while (!*opt_path && tree_stack)
4732                 pop_tree_stack_entry();
4734         switch (line->type) {
4735         case LINE_TREE_DIR:
4736                 /* Depending on whether it is a subdirectory or parent link
4737                  * mangle the path buffer. */
4738                 if (line == &view->line[1] && *opt_path) {
4739                         pop_tree_stack_entry();
4741                 } else {
4742                         const char *basename = tree_path(line);
4744                         push_tree_stack_entry(basename, view->lineno);
4745                 }
4747                 /* Trees and subtrees share the same ID, so they are not not
4748                  * unique like blobs. */
4749                 flags = OPEN_RELOAD;
4750                 request = REQ_VIEW_TREE;
4751                 break;
4753         case LINE_TREE_FILE:
4754                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4755                 request = REQ_VIEW_BLOB;
4756                 break;
4758         default:
4759                 return REQ_NONE;
4760         }
4762         open_view(view, request, flags);
4763         if (request == REQ_VIEW_TREE)
4764                 view->lineno = tree_lineno;
4766         return REQ_NONE;
4769 static bool
4770 tree_grep(struct view *view, struct line *line)
4772         struct tree_entry *entry = line->data;
4773         const char *text[] = {
4774                 entry->name,
4775                 opt_author ? entry->author : "",
4776                 mkdate(&entry->time, opt_date),
4777                 NULL
4778         };
4780         return grep_text(view, text);
4783 static void
4784 tree_select(struct view *view, struct line *line)
4786         struct tree_entry *entry = line->data;
4788         if (line->type == LINE_TREE_FILE) {
4789                 string_copy_rev(ref_blob, entry->id);
4790                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4792         } else if (line->type != LINE_TREE_DIR) {
4793                 return;
4794         }
4796         string_copy_rev(view->ref, entry->id);
4799 static bool
4800 tree_prepare(struct view *view)
4802         if (view->lines == 0 && opt_prefix[0]) {
4803                 char *pos = opt_prefix;
4805                 while (pos && *pos) {
4806                         char *end = strchr(pos, '/');
4808                         if (end)
4809                                 *end = 0;
4810                         push_tree_stack_entry(pos, 0);
4811                         pos = end;
4812                         if (end) {
4813                                 *end = '/';
4814                                 pos++;
4815                         }
4816                 }
4818         } else if (strcmp(view->vid, view->id)) {
4819                 opt_path[0] = 0;
4820         }
4822         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4825 static const char *tree_argv[SIZEOF_ARG] = {
4826         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4827 };
4829 static struct view_ops tree_ops = {
4830         "file",
4831         tree_argv,
4832         NULL,
4833         tree_read,
4834         tree_draw,
4835         tree_request,
4836         tree_grep,
4837         tree_select,
4838         tree_prepare,
4839 };
4841 static bool
4842 blob_read(struct view *view, char *line)
4844         if (!line)
4845                 return TRUE;
4846         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4849 static enum request
4850 blob_request(struct view *view, enum request request, struct line *line)
4852         switch (request) {
4853         case REQ_EDIT:
4854                 open_blob_editor(view->vid);
4855                 return REQ_NONE;
4856         default:
4857                 return pager_request(view, request, line);
4858         }
4861 static const char *blob_argv[SIZEOF_ARG] = {
4862         "git", "cat-file", "blob", "%(blob)", NULL
4863 };
4865 static struct view_ops blob_ops = {
4866         "line",
4867         blob_argv,
4868         NULL,
4869         blob_read,
4870         pager_draw,
4871         blob_request,
4872         pager_grep,
4873         pager_select,
4874 };
4876 /*
4877  * Blame backend
4878  *
4879  * Loading the blame view is a two phase job:
4880  *
4881  *  1. File content is read either using opt_file from the
4882  *     filesystem or using git-cat-file.
4883  *  2. Then blame information is incrementally added by
4884  *     reading output from git-blame.
4885  */
4887 struct blame_commit {
4888         char id[SIZEOF_REV];            /* SHA1 ID. */
4889         char title[128];                /* First line of the commit message. */
4890         const char *author;             /* Author of the commit. */
4891         struct time time;               /* Date from the author ident. */
4892         char filename[128];             /* Name of file. */
4893         bool has_previous;              /* Was a "previous" line detected. */
4894 };
4896 struct blame {
4897         struct blame_commit *commit;
4898         unsigned long lineno;
4899         char text[1];
4900 };
4902 static bool
4903 blame_open(struct view *view)
4905         char path[SIZEOF_STR];
4907         if (!view->prev && *opt_prefix) {
4908                 string_copy(path, opt_file);
4909                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4910                         return FALSE;
4911         }
4913         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4914                 const char *blame_cat_file_argv[] = {
4915                         "git", "cat-file", "blob", path, NULL
4916                 };
4918                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4919                     !io_run_rd(&view->io, blame_cat_file_argv, opt_cdup))
4920                         return FALSE;
4921         }
4923         setup_update(view, opt_file);
4924         string_format(view->ref, "%s ...", opt_file);
4926         return TRUE;
4929 static struct blame_commit *
4930 get_blame_commit(struct view *view, const char *id)
4932         size_t i;
4934         for (i = 0; i < view->lines; i++) {
4935                 struct blame *blame = view->line[i].data;
4937                 if (!blame->commit)
4938                         continue;
4940                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4941                         return blame->commit;
4942         }
4944         {
4945                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4947                 if (commit)
4948                         string_ncopy(commit->id, id, SIZEOF_REV);
4949                 return commit;
4950         }
4953 static bool
4954 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4956         const char *pos = *posref;
4958         *posref = NULL;
4959         pos = strchr(pos + 1, ' ');
4960         if (!pos || !isdigit(pos[1]))
4961                 return FALSE;
4962         *number = atoi(pos + 1);
4963         if (*number < min || *number > max)
4964                 return FALSE;
4966         *posref = pos;
4967         return TRUE;
4970 static struct blame_commit *
4971 parse_blame_commit(struct view *view, const char *text, int *blamed)
4973         struct blame_commit *commit;
4974         struct blame *blame;
4975         const char *pos = text + SIZEOF_REV - 2;
4976         size_t orig_lineno = 0;
4977         size_t lineno;
4978         size_t group;
4980         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4981                 return NULL;
4983         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4984             !parse_number(&pos, &lineno, 1, view->lines) ||
4985             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4986                 return NULL;
4988         commit = get_blame_commit(view, text);
4989         if (!commit)
4990                 return NULL;
4992         *blamed += group;
4993         while (group--) {
4994                 struct line *line = &view->line[lineno + group - 1];
4996                 blame = line->data;
4997                 blame->commit = commit;
4998                 blame->lineno = orig_lineno + group - 1;
4999                 line->dirty = 1;
5000         }
5002         return commit;
5005 static bool
5006 blame_read_file(struct view *view, const char *line, bool *read_file)
5008         if (!line) {
5009                 const char *blame_argv[] = {
5010                         "git", "blame", "--incremental",
5011                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5012                 };
5013                 struct io io = {};
5015                 if (view->lines == 0 && !view->prev)
5016                         die("No blame exist for %s", view->vid);
5018                 if (view->lines == 0 || !io_run_rd(&io, blame_argv, opt_cdup)) {
5019                         report("Failed to load blame data");
5020                         return TRUE;
5021                 }
5023                 io_done(view->pipe);
5024                 view->io = io;
5025                 *read_file = FALSE;
5026                 return FALSE;
5028         } else {
5029                 size_t linelen = strlen(line);
5030                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5032                 if (!blame)
5033                         return FALSE;
5035                 blame->commit = NULL;
5036                 strncpy(blame->text, line, linelen);
5037                 blame->text[linelen] = 0;
5038                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5039         }
5042 static bool
5043 match_blame_header(const char *name, char **line)
5045         size_t namelen = strlen(name);
5046         bool matched = !strncmp(name, *line, namelen);
5048         if (matched)
5049                 *line += namelen;
5051         return matched;
5054 static bool
5055 blame_read(struct view *view, char *line)
5057         static struct blame_commit *commit = NULL;
5058         static int blamed = 0;
5059         static bool read_file = TRUE;
5061         if (read_file)
5062                 return blame_read_file(view, line, &read_file);
5064         if (!line) {
5065                 /* Reset all! */
5066                 commit = NULL;
5067                 blamed = 0;
5068                 read_file = TRUE;
5069                 string_format(view->ref, "%s", view->vid);
5070                 if (view_is_displayed(view)) {
5071                         update_view_title(view);
5072                         redraw_view_from(view, 0);
5073                 }
5074                 return TRUE;
5075         }
5077         if (!commit) {
5078                 commit = parse_blame_commit(view, line, &blamed);
5079                 string_format(view->ref, "%s %2d%%", view->vid,
5080                               view->lines ? blamed * 100 / view->lines : 0);
5082         } else if (match_blame_header("author ", &line)) {
5083                 commit->author = get_author(line);
5085         } else if (match_blame_header("author-time ", &line)) {
5086                 parse_timesec(&commit->time, line);
5088         } else if (match_blame_header("author-tz ", &line)) {
5089                 parse_timezone(&commit->time, line);
5091         } else if (match_blame_header("summary ", &line)) {
5092                 string_ncopy(commit->title, line, strlen(line));
5094         } else if (match_blame_header("previous ", &line)) {
5095                 commit->has_previous = TRUE;
5097         } else if (match_blame_header("filename ", &line)) {
5098                 string_ncopy(commit->filename, line, strlen(line));
5099                 commit = NULL;
5100         }
5102         return TRUE;
5105 static bool
5106 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5108         struct blame *blame = line->data;
5109         struct time *time = NULL;
5110         const char *id = NULL, *author = NULL;
5111         char text[SIZEOF_STR];
5113         if (blame->commit && *blame->commit->filename) {
5114                 id = blame->commit->id;
5115                 author = blame->commit->author;
5116                 time = &blame->commit->time;
5117         }
5119         if (opt_date && draw_date(view, time))
5120                 return TRUE;
5122         if (opt_author && draw_author(view, author))
5123                 return TRUE;
5125         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5126                 return TRUE;
5128         if (draw_lineno(view, lineno))
5129                 return TRUE;
5131         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5132         draw_text(view, LINE_DEFAULT, text, TRUE);
5133         return TRUE;
5136 static bool
5137 check_blame_commit(struct blame *blame, bool check_null_id)
5139         if (!blame->commit)
5140                 report("Commit data not loaded yet");
5141         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5142                 report("No commit exist for the selected line");
5143         else
5144                 return TRUE;
5145         return FALSE;
5148 static void
5149 setup_blame_parent_line(struct view *view, struct blame *blame)
5151         const char *diff_tree_argv[] = {
5152                 "git", "diff-tree", "-U0", blame->commit->id,
5153                         "--", blame->commit->filename, NULL
5154         };
5155         struct io io = {};
5156         int parent_lineno = -1;
5157         int blamed_lineno = -1;
5158         char *line;
5160         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5161                 return;
5163         while ((line = io_get(&io, '\n', TRUE))) {
5164                 if (*line == '@') {
5165                         char *pos = strchr(line, '+');
5167                         parent_lineno = atoi(line + 4);
5168                         if (pos)
5169                                 blamed_lineno = atoi(pos + 1);
5171                 } else if (*line == '+' && parent_lineno != -1) {
5172                         if (blame->lineno == blamed_lineno - 1 &&
5173                             !strcmp(blame->text, line + 1)) {
5174                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5175                                 break;
5176                         }
5177                         blamed_lineno++;
5178                 }
5179         }
5181         io_done(&io);
5184 static enum request
5185 blame_request(struct view *view, enum request request, struct line *line)
5187         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5188         struct blame *blame = line->data;
5190         switch (request) {
5191         case REQ_VIEW_BLAME:
5192                 if (check_blame_commit(blame, TRUE)) {
5193                         string_copy(opt_ref, blame->commit->id);
5194                         string_copy(opt_file, blame->commit->filename);
5195                         if (blame->lineno)
5196                                 view->lineno = blame->lineno;
5197                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5198                 }
5199                 break;
5201         case REQ_PARENT:
5202                 if (check_blame_commit(blame, TRUE) &&
5203                     select_commit_parent(blame->commit->id, opt_ref,
5204                                          blame->commit->filename)) {
5205                         string_copy(opt_file, blame->commit->filename);
5206                         setup_blame_parent_line(view, blame);
5207                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5208                 }
5209                 break;
5211         case REQ_ENTER:
5212                 if (!check_blame_commit(blame, FALSE))
5213                         break;
5215                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5216                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5217                         break;
5219                 if (!strcmp(blame->commit->id, NULL_ID)) {
5220                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5221                         const char *diff_index_argv[] = {
5222                                 "git", "diff-index", "--root", "--patch-with-stat",
5223                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5224                         };
5226                         if (!blame->commit->has_previous) {
5227                                 diff_index_argv[1] = "diff";
5228                                 diff_index_argv[2] = "--no-color";
5229                                 diff_index_argv[6] = "--";
5230                                 diff_index_argv[7] = "/dev/null";
5231                         }
5233                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5234                                 report("Failed to allocate diff command");
5235                                 break;
5236                         }
5237                         flags |= OPEN_PREPARED;
5238                 }
5240                 open_view(view, REQ_VIEW_DIFF, flags);
5241                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5242                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5243                 break;
5245         default:
5246                 return request;
5247         }
5249         return REQ_NONE;
5252 static bool
5253 blame_grep(struct view *view, struct line *line)
5255         struct blame *blame = line->data;
5256         struct blame_commit *commit = blame->commit;
5257         const char *text[] = {
5258                 blame->text,
5259                 commit ? commit->title : "",
5260                 commit ? commit->id : "",
5261                 commit && opt_author ? commit->author : "",
5262                 commit ? mkdate(&commit->time, opt_date) : "",
5263                 NULL
5264         };
5266         return grep_text(view, text);
5269 static void
5270 blame_select(struct view *view, struct line *line)
5272         struct blame *blame = line->data;
5273         struct blame_commit *commit = blame->commit;
5275         if (!commit)
5276                 return;
5278         if (!strcmp(commit->id, NULL_ID))
5279                 string_ncopy(ref_commit, "HEAD", 4);
5280         else
5281                 string_copy_rev(ref_commit, commit->id);
5284 static struct view_ops blame_ops = {
5285         "line",
5286         NULL,
5287         blame_open,
5288         blame_read,
5289         blame_draw,
5290         blame_request,
5291         blame_grep,
5292         blame_select,
5293 };
5295 /*
5296  * Branch backend
5297  */
5299 struct branch {
5300         const char *author;             /* Author of the last commit. */
5301         struct time time;               /* Date of the last activity. */
5302         const struct ref *ref;          /* Name and commit ID information. */
5303 };
5305 static const struct ref branch_all;
5307 static const enum sort_field branch_sort_fields[] = {
5308         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5309 };
5310 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5312 static int
5313 branch_compare(const void *l1, const void *l2)
5315         const struct branch *branch1 = ((const struct line *) l1)->data;
5316         const struct branch *branch2 = ((const struct line *) l2)->data;
5318         switch (get_sort_field(branch_sort_state)) {
5319         case ORDERBY_DATE:
5320                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5322         case ORDERBY_AUTHOR:
5323                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5325         case ORDERBY_NAME:
5326         default:
5327                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5328         }
5331 static bool
5332 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5334         struct branch *branch = line->data;
5335         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5337         if (opt_date && draw_date(view, &branch->time))
5338                 return TRUE;
5340         if (opt_author && draw_author(view, branch->author))
5341                 return TRUE;
5343         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5344         return TRUE;
5347 static enum request
5348 branch_request(struct view *view, enum request request, struct line *line)
5350         struct branch *branch = line->data;
5352         switch (request) {
5353         case REQ_REFRESH:
5354                 load_refs();
5355                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5356                 return REQ_NONE;
5358         case REQ_TOGGLE_SORT_FIELD:
5359         case REQ_TOGGLE_SORT_ORDER:
5360                 sort_view(view, request, &branch_sort_state, branch_compare);
5361                 return REQ_NONE;
5363         case REQ_ENTER:
5364                 if (branch->ref == &branch_all) {
5365                         const char *all_branches_argv[] = {
5366                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5367                                       "--topo-order", "--all", NULL
5368                         };
5369                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5371                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5372                                 report("Failed to load view of all branches");
5373                                 return REQ_NONE;
5374                         }
5375                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5376                 } else {
5377                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5378                 }
5379                 return REQ_NONE;
5381         default:
5382                 return request;
5383         }
5386 static bool
5387 branch_read(struct view *view, char *line)
5389         static char id[SIZEOF_REV];
5390         struct branch *reference;
5391         size_t i;
5393         if (!line)
5394                 return TRUE;
5396         switch (get_line_type(line)) {
5397         case LINE_COMMIT:
5398                 string_copy_rev(id, line + STRING_SIZE("commit "));
5399                 return TRUE;
5401         case LINE_AUTHOR:
5402                 for (i = 0, reference = NULL; i < view->lines; i++) {
5403                         struct branch *branch = view->line[i].data;
5405                         if (strcmp(branch->ref->id, id))
5406                                 continue;
5408                         view->line[i].dirty = TRUE;
5409                         if (reference) {
5410                                 branch->author = reference->author;
5411                                 branch->time = reference->time;
5412                                 continue;
5413                         }
5415                         parse_author_line(line + STRING_SIZE("author "),
5416                                           &branch->author, &branch->time);
5417                         reference = branch;
5418                 }
5419                 return TRUE;
5421         default:
5422                 return TRUE;
5423         }
5427 static bool
5428 branch_open_visitor(void *data, const struct ref *ref)
5430         struct view *view = data;
5431         struct branch *branch;
5433         if (ref->tag || ref->ltag || ref->remote)
5434                 return TRUE;
5436         branch = calloc(1, sizeof(*branch));
5437         if (!branch)
5438                 return FALSE;
5440         branch->ref = ref;
5441         return !!add_line_data(view, branch, LINE_DEFAULT);
5444 static bool
5445 branch_open(struct view *view)
5447         const char *branch_log[] = {
5448                 "git", "log", "--no-color", "--pretty=raw",
5449                         "--simplify-by-decoration", "--all", NULL
5450         };
5452         if (!io_run_rd(&view->io, branch_log, NULL)) {
5453                 report("Failed to load branch data");
5454                 return TRUE;
5455         }
5457         setup_update(view, view->id);
5458         branch_open_visitor(view, &branch_all);
5459         foreach_ref(branch_open_visitor, view);
5460         view->p_restore = TRUE;
5462         return TRUE;
5465 static bool
5466 branch_grep(struct view *view, struct line *line)
5468         struct branch *branch = line->data;
5469         const char *text[] = {
5470                 branch->ref->name,
5471                 branch->author,
5472                 NULL
5473         };
5475         return grep_text(view, text);
5478 static void
5479 branch_select(struct view *view, struct line *line)
5481         struct branch *branch = line->data;
5483         string_copy_rev(view->ref, branch->ref->id);
5484         string_copy_rev(ref_commit, branch->ref->id);
5485         string_copy_rev(ref_head, branch->ref->id);
5486         string_copy_rev(ref_branch, branch->ref->name);
5489 static struct view_ops branch_ops = {
5490         "branch",
5491         NULL,
5492         branch_open,
5493         branch_read,
5494         branch_draw,
5495         branch_request,
5496         branch_grep,
5497         branch_select,
5498 };
5500 /*
5501  * Status backend
5502  */
5504 struct status {
5505         char status;
5506         struct {
5507                 mode_t mode;
5508                 char rev[SIZEOF_REV];
5509                 char name[SIZEOF_STR];
5510         } old;
5511         struct {
5512                 mode_t mode;
5513                 char rev[SIZEOF_REV];
5514                 char name[SIZEOF_STR];
5515         } new;
5516 };
5518 static char status_onbranch[SIZEOF_STR];
5519 static struct status stage_status;
5520 static enum line_type stage_line_type;
5521 static size_t stage_chunks;
5522 static int *stage_chunk;
5524 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5526 /* This should work even for the "On branch" line. */
5527 static inline bool
5528 status_has_none(struct view *view, struct line *line)
5530         return line < view->line + view->lines && !line[1].data;
5533 /* Get fields from the diff line:
5534  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5535  */
5536 static inline bool
5537 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5539         const char *old_mode = buf +  1;
5540         const char *new_mode = buf +  8;
5541         const char *old_rev  = buf + 15;
5542         const char *new_rev  = buf + 56;
5543         const char *status   = buf + 97;
5545         if (bufsize < 98 ||
5546             old_mode[-1] != ':' ||
5547             new_mode[-1] != ' ' ||
5548             old_rev[-1]  != ' ' ||
5549             new_rev[-1]  != ' ' ||
5550             status[-1]   != ' ')
5551                 return FALSE;
5553         file->status = *status;
5555         string_copy_rev(file->old.rev, old_rev);
5556         string_copy_rev(file->new.rev, new_rev);
5558         file->old.mode = strtoul(old_mode, NULL, 8);
5559         file->new.mode = strtoul(new_mode, NULL, 8);
5561         file->old.name[0] = file->new.name[0] = 0;
5563         return TRUE;
5566 static bool
5567 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5569         struct status *unmerged = NULL;
5570         char *buf;
5571         struct io io = {};
5573         if (!io_run(&io, argv, opt_cdup, IO_RD))
5574                 return FALSE;
5576         add_line_data(view, NULL, type);
5578         while ((buf = io_get(&io, 0, TRUE))) {
5579                 struct status *file = unmerged;
5581                 if (!file) {
5582                         file = calloc(1, sizeof(*file));
5583                         if (!file || !add_line_data(view, file, type))
5584                                 goto error_out;
5585                 }
5587                 /* Parse diff info part. */
5588                 if (status) {
5589                         file->status = status;
5590                         if (status == 'A')
5591                                 string_copy(file->old.rev, NULL_ID);
5593                 } else if (!file->status || file == unmerged) {
5594                         if (!status_get_diff(file, buf, strlen(buf)))
5595                                 goto error_out;
5597                         buf = io_get(&io, 0, TRUE);
5598                         if (!buf)
5599                                 break;
5601                         /* Collapse all modified entries that follow an
5602                          * associated unmerged entry. */
5603                         if (unmerged == file) {
5604                                 unmerged->status = 'U';
5605                                 unmerged = NULL;
5606                         } else if (file->status == 'U') {
5607                                 unmerged = file;
5608                         }
5609                 }
5611                 /* Grab the old name for rename/copy. */
5612                 if (!*file->old.name &&
5613                     (file->status == 'R' || file->status == 'C')) {
5614                         string_ncopy(file->old.name, buf, strlen(buf));
5616                         buf = io_get(&io, 0, TRUE);
5617                         if (!buf)
5618                                 break;
5619                 }
5621                 /* git-ls-files just delivers a NUL separated list of
5622                  * file names similar to the second half of the
5623                  * git-diff-* output. */
5624                 string_ncopy(file->new.name, buf, strlen(buf));
5625                 if (!*file->old.name)
5626                         string_copy(file->old.name, file->new.name);
5627                 file = NULL;
5628         }
5630         if (io_error(&io)) {
5631 error_out:
5632                 io_done(&io);
5633                 return FALSE;
5634         }
5636         if (!view->line[view->lines - 1].data)
5637                 add_line_data(view, NULL, LINE_STAT_NONE);
5639         io_done(&io);
5640         return TRUE;
5643 /* Don't show unmerged entries in the staged section. */
5644 static const char *status_diff_index_argv[] = {
5645         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5646                              "--cached", "-M", "HEAD", NULL
5647 };
5649 static const char *status_diff_files_argv[] = {
5650         "git", "diff-files", "-z", NULL
5651 };
5653 static const char *status_list_other_argv[] = {
5654         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5655 };
5657 static const char *status_list_no_head_argv[] = {
5658         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5659 };
5661 static const char *update_index_argv[] = {
5662         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5663 };
5665 /* Restore the previous line number to stay in the context or select a
5666  * line with something that can be updated. */
5667 static void
5668 status_restore(struct view *view)
5670         if (view->p_lineno >= view->lines)
5671                 view->p_lineno = view->lines - 1;
5672         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5673                 view->p_lineno++;
5674         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5675                 view->p_lineno--;
5677         /* If the above fails, always skip the "On branch" line. */
5678         if (view->p_lineno < view->lines)
5679                 view->lineno = view->p_lineno;
5680         else
5681                 view->lineno = 1;
5683         if (view->lineno < view->offset)
5684                 view->offset = view->lineno;
5685         else if (view->offset + view->height <= view->lineno)
5686                 view->offset = view->lineno - view->height + 1;
5688         view->p_restore = FALSE;
5691 static void
5692 status_update_onbranch(void)
5694         static const char *paths[][2] = {
5695                 { "rebase-apply/rebasing",      "Rebasing" },
5696                 { "rebase-apply/applying",      "Applying mailbox" },
5697                 { "rebase-apply/",              "Rebasing mailbox" },
5698                 { "rebase-merge/interactive",   "Interactive rebase" },
5699                 { "rebase-merge/",              "Rebase merge" },
5700                 { "MERGE_HEAD",                 "Merging" },
5701                 { "BISECT_LOG",                 "Bisecting" },
5702                 { "HEAD",                       "On branch" },
5703         };
5704         char buf[SIZEOF_STR];
5705         struct stat stat;
5706         int i;
5708         if (is_initial_commit()) {
5709                 string_copy(status_onbranch, "Initial commit");
5710                 return;
5711         }
5713         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5714                 char *head = opt_head;
5716                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5717                     lstat(buf, &stat) < 0)
5718                         continue;
5720                 if (!*opt_head) {
5721                         struct io io = {};
5723                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5724                             io_read_buf(&io, buf, sizeof(buf))) {
5725                                 head = buf;
5726                                 if (!prefixcmp(head, "refs/heads/"))
5727                                         head += STRING_SIZE("refs/heads/");
5728                         }
5729                 }
5731                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5732                         string_copy(status_onbranch, opt_head);
5733                 return;
5734         }
5736         string_copy(status_onbranch, "Not currently on any branch");
5739 /* First parse staged info using git-diff-index(1), then parse unstaged
5740  * info using git-diff-files(1), and finally untracked files using
5741  * git-ls-files(1). */
5742 static bool
5743 status_open(struct view *view)
5745         reset_view(view);
5747         add_line_data(view, NULL, LINE_STAT_HEAD);
5748         status_update_onbranch();
5750         io_run_bg(update_index_argv);
5752         if (is_initial_commit()) {
5753                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5754                         return FALSE;
5755         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5756                 return FALSE;
5757         }
5759         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5760             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5761                 return FALSE;
5763         /* Restore the exact position or use the specialized restore
5764          * mode? */
5765         if (!view->p_restore)
5766                 status_restore(view);
5767         return TRUE;
5770 static bool
5771 status_draw(struct view *view, struct line *line, unsigned int lineno)
5773         struct status *status = line->data;
5774         enum line_type type;
5775         const char *text;
5777         if (!status) {
5778                 switch (line->type) {
5779                 case LINE_STAT_STAGED:
5780                         type = LINE_STAT_SECTION;
5781                         text = "Changes to be committed:";
5782                         break;
5784                 case LINE_STAT_UNSTAGED:
5785                         type = LINE_STAT_SECTION;
5786                         text = "Changed but not updated:";
5787                         break;
5789                 case LINE_STAT_UNTRACKED:
5790                         type = LINE_STAT_SECTION;
5791                         text = "Untracked files:";
5792                         break;
5794                 case LINE_STAT_NONE:
5795                         type = LINE_DEFAULT;
5796                         text = "  (no files)";
5797                         break;
5799                 case LINE_STAT_HEAD:
5800                         type = LINE_STAT_HEAD;
5801                         text = status_onbranch;
5802                         break;
5804                 default:
5805                         return FALSE;
5806                 }
5807         } else {
5808                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5810                 buf[0] = status->status;
5811                 if (draw_text(view, line->type, buf, TRUE))
5812                         return TRUE;
5813                 type = LINE_DEFAULT;
5814                 text = status->new.name;
5815         }
5817         draw_text(view, type, text, TRUE);
5818         return TRUE;
5821 static enum request
5822 status_load_error(struct view *view, struct view *stage, const char *path)
5824         if (displayed_views() == 2 || display[current_view] != view)
5825                 maximize_view(view);
5826         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5827         return REQ_NONE;
5830 static enum request
5831 status_enter(struct view *view, struct line *line)
5833         struct status *status = line->data;
5834         const char *oldpath = status ? status->old.name : NULL;
5835         /* Diffs for unmerged entries are empty when passing the new
5836          * path, so leave it empty. */
5837         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5838         const char *info;
5839         enum open_flags split;
5840         struct view *stage = VIEW(REQ_VIEW_STAGE);
5842         if (line->type == LINE_STAT_NONE ||
5843             (!status && line[1].type == LINE_STAT_NONE)) {
5844                 report("No file to diff");
5845                 return REQ_NONE;
5846         }
5848         switch (line->type) {
5849         case LINE_STAT_STAGED:
5850                 if (is_initial_commit()) {
5851                         const char *no_head_diff_argv[] = {
5852                                 "git", "diff", "--no-color", "--patch-with-stat",
5853                                         "--", "/dev/null", newpath, NULL
5854                         };
5856                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5857                                 return status_load_error(view, stage, newpath);
5858                 } else {
5859                         const char *index_show_argv[] = {
5860                                 "git", "diff-index", "--root", "--patch-with-stat",
5861                                         "-C", "-M", "--cached", "HEAD", "--",
5862                                         oldpath, newpath, NULL
5863                         };
5865                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5866                                 return status_load_error(view, stage, newpath);
5867                 }
5869                 if (status)
5870                         info = "Staged changes to %s";
5871                 else
5872                         info = "Staged changes";
5873                 break;
5875         case LINE_STAT_UNSTAGED:
5876         {
5877                 const char *files_show_argv[] = {
5878                         "git", "diff-files", "--root", "--patch-with-stat",
5879                                 "-C", "-M", "--", oldpath, newpath, NULL
5880                 };
5882                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5883                         return status_load_error(view, stage, newpath);
5884                 if (status)
5885                         info = "Unstaged changes to %s";
5886                 else
5887                         info = "Unstaged changes";
5888                 break;
5889         }
5890         case LINE_STAT_UNTRACKED:
5891                 if (!newpath) {
5892                         report("No file to show");
5893                         return REQ_NONE;
5894                 }
5896                 if (!suffixcmp(status->new.name, -1, "/")) {
5897                         report("Cannot display a directory");
5898                         return REQ_NONE;
5899                 }
5901                 if (!prepare_update_file(stage, newpath))
5902                         return status_load_error(view, stage, newpath);
5903                 info = "Untracked file %s";
5904                 break;
5906         case LINE_STAT_HEAD:
5907                 return REQ_NONE;
5909         default:
5910                 die("line type %d not handled in switch", line->type);
5911         }
5913         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5914         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5915         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5916                 if (status) {
5917                         stage_status = *status;
5918                 } else {
5919                         memset(&stage_status, 0, sizeof(stage_status));
5920                 }
5922                 stage_line_type = line->type;
5923                 stage_chunks = 0;
5924                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5925         }
5927         return REQ_NONE;
5930 static bool
5931 status_exists(struct status *status, enum line_type type)
5933         struct view *view = VIEW(REQ_VIEW_STATUS);
5934         unsigned long lineno;
5936         for (lineno = 0; lineno < view->lines; lineno++) {
5937                 struct line *line = &view->line[lineno];
5938                 struct status *pos = line->data;
5940                 if (line->type != type)
5941                         continue;
5942                 if (!pos && (!status || !status->status) && line[1].data) {
5943                         select_view_line(view, lineno);
5944                         return TRUE;
5945                 }
5946                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5947                         select_view_line(view, lineno);
5948                         return TRUE;
5949                 }
5950         }
5952         return FALSE;
5956 static bool
5957 status_update_prepare(struct io *io, enum line_type type)
5959         const char *staged_argv[] = {
5960                 "git", "update-index", "-z", "--index-info", NULL
5961         };
5962         const char *others_argv[] = {
5963                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5964         };
5966         switch (type) {
5967         case LINE_STAT_STAGED:
5968                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5970         case LINE_STAT_UNSTAGED:
5971         case LINE_STAT_UNTRACKED:
5972                 return io_run(io, others_argv, opt_cdup, IO_WR);
5974         default:
5975                 die("line type %d not handled in switch", type);
5976                 return FALSE;
5977         }
5980 static bool
5981 status_update_write(struct io *io, struct status *status, enum line_type type)
5983         char buf[SIZEOF_STR];
5984         size_t bufsize = 0;
5986         switch (type) {
5987         case LINE_STAT_STAGED:
5988                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5989                                         status->old.mode,
5990                                         status->old.rev,
5991                                         status->old.name, 0))
5992                         return FALSE;
5993                 break;
5995         case LINE_STAT_UNSTAGED:
5996         case LINE_STAT_UNTRACKED:
5997                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5998                         return FALSE;
5999                 break;
6001         default:
6002                 die("line type %d not handled in switch", type);
6003         }
6005         return io_write(io, buf, bufsize);
6008 static bool
6009 status_update_file(struct status *status, enum line_type type)
6011         struct io io = {};
6012         bool result;
6014         if (!status_update_prepare(&io, type))
6015                 return FALSE;
6017         result = status_update_write(&io, status, type);
6018         return io_done(&io) && result;
6021 static bool
6022 status_update_files(struct view *view, struct line *line)
6024         char buf[sizeof(view->ref)];
6025         struct io io = {};
6026         bool result = TRUE;
6027         struct line *pos = view->line + view->lines;
6028         int files = 0;
6029         int file, done;
6030         int cursor_y = -1, cursor_x = -1;
6032         if (!status_update_prepare(&io, line->type))
6033                 return FALSE;
6035         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6036                 files++;
6038         string_copy(buf, view->ref);
6039         getsyx(cursor_y, cursor_x);
6040         for (file = 0, done = 5; result && file < files; line++, file++) {
6041                 int almost_done = file * 100 / files;
6043                 if (almost_done > done) {
6044                         done = almost_done;
6045                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6046                                       file, files, done);
6047                         update_view_title(view);
6048                         setsyx(cursor_y, cursor_x);
6049                         doupdate();
6050                 }
6051                 result = status_update_write(&io, line->data, line->type);
6052         }
6053         string_copy(view->ref, buf);
6055         return io_done(&io) && result;
6058 static bool
6059 status_update(struct view *view)
6061         struct line *line = &view->line[view->lineno];
6063         assert(view->lines);
6065         if (!line->data) {
6066                 /* This should work even for the "On branch" line. */
6067                 if (line < view->line + view->lines && !line[1].data) {
6068                         report("Nothing to update");
6069                         return FALSE;
6070                 }
6072                 if (!status_update_files(view, line + 1)) {
6073                         report("Failed to update file status");
6074                         return FALSE;
6075                 }
6077         } else if (!status_update_file(line->data, line->type)) {
6078                 report("Failed to update file status");
6079                 return FALSE;
6080         }
6082         return TRUE;
6085 static bool
6086 status_revert(struct status *status, enum line_type type, bool has_none)
6088         if (!status || type != LINE_STAT_UNSTAGED) {
6089                 if (type == LINE_STAT_STAGED) {
6090                         report("Cannot revert changes to staged files");
6091                 } else if (type == LINE_STAT_UNTRACKED) {
6092                         report("Cannot revert changes to untracked files");
6093                 } else if (has_none) {
6094                         report("Nothing to revert");
6095                 } else {
6096                         report("Cannot revert changes to multiple files");
6097                 }
6099         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6100                 char mode[10] = "100644";
6101                 const char *reset_argv[] = {
6102                         "git", "update-index", "--cacheinfo", mode,
6103                                 status->old.rev, status->old.name, NULL
6104                 };
6105                 const char *checkout_argv[] = {
6106                         "git", "checkout", "--", status->old.name, NULL
6107                 };
6109                 if (status->status == 'U') {
6110                         string_format(mode, "%5o", status->old.mode);
6112                         if (status->old.mode == 0 && status->new.mode == 0) {
6113                                 reset_argv[2] = "--force-remove";
6114                                 reset_argv[3] = status->old.name;
6115                                 reset_argv[4] = NULL;
6116                         }
6118                         if (!io_run_fg(reset_argv, opt_cdup))
6119                                 return FALSE;
6120                         if (status->old.mode == 0 && status->new.mode == 0)
6121                                 return TRUE;
6122                 }
6124                 return io_run_fg(checkout_argv, opt_cdup);
6125         }
6127         return FALSE;
6130 static enum request
6131 status_request(struct view *view, enum request request, struct line *line)
6133         struct status *status = line->data;
6135         switch (request) {
6136         case REQ_STATUS_UPDATE:
6137                 if (!status_update(view))
6138                         return REQ_NONE;
6139                 break;
6141         case REQ_STATUS_REVERT:
6142                 if (!status_revert(status, line->type, status_has_none(view, line)))
6143                         return REQ_NONE;
6144                 break;
6146         case REQ_STATUS_MERGE:
6147                 if (!status || status->status != 'U') {
6148                         report("Merging only possible for files with unmerged status ('U').");
6149                         return REQ_NONE;
6150                 }
6151                 open_mergetool(status->new.name);
6152                 break;
6154         case REQ_EDIT:
6155                 if (!status)
6156                         return request;
6157                 if (status->status == 'D') {
6158                         report("File has been deleted.");
6159                         return REQ_NONE;
6160                 }
6162                 open_editor(status->new.name);
6163                 break;
6165         case REQ_VIEW_BLAME:
6166                 if (status)
6167                         opt_ref[0] = 0;
6168                 return request;
6170         case REQ_ENTER:
6171                 /* After returning the status view has been split to
6172                  * show the stage view. No further reloading is
6173                  * necessary. */
6174                 return status_enter(view, line);
6176         case REQ_REFRESH:
6177                 /* Simply reload the view. */
6178                 break;
6180         default:
6181                 return request;
6182         }
6184         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6186         return REQ_NONE;
6189 static void
6190 status_select(struct view *view, struct line *line)
6192         struct status *status = line->data;
6193         char file[SIZEOF_STR] = "all files";
6194         const char *text;
6195         const char *key;
6197         if (status && !string_format(file, "'%s'", status->new.name))
6198                 return;
6200         if (!status && line[1].type == LINE_STAT_NONE)
6201                 line++;
6203         switch (line->type) {
6204         case LINE_STAT_STAGED:
6205                 text = "Press %s to unstage %s for commit";
6206                 break;
6208         case LINE_STAT_UNSTAGED:
6209                 text = "Press %s to stage %s for commit";
6210                 break;
6212         case LINE_STAT_UNTRACKED:
6213                 text = "Press %s to stage %s for addition";
6214                 break;
6216         case LINE_STAT_HEAD:
6217         case LINE_STAT_NONE:
6218                 text = "Nothing to update";
6219                 break;
6221         default:
6222                 die("line type %d not handled in switch", line->type);
6223         }
6225         if (status && status->status == 'U') {
6226                 text = "Press %s to resolve conflict in %s";
6227                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6229         } else {
6230                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6231         }
6233         string_format(view->ref, text, key, file);
6234         if (status)
6235                 string_copy(opt_file, status->new.name);
6238 static bool
6239 status_grep(struct view *view, struct line *line)
6241         struct status *status = line->data;
6243         if (status) {
6244                 const char buf[2] = { status->status, 0 };
6245                 const char *text[] = { status->new.name, buf, NULL };
6247                 return grep_text(view, text);
6248         }
6250         return FALSE;
6253 static struct view_ops status_ops = {
6254         "file",
6255         NULL,
6256         status_open,
6257         NULL,
6258         status_draw,
6259         status_request,
6260         status_grep,
6261         status_select,
6262 };
6265 static bool
6266 stage_diff_write(struct io *io, struct line *line, struct line *end)
6268         while (line < end) {
6269                 if (!io_write(io, line->data, strlen(line->data)) ||
6270                     !io_write(io, "\n", 1))
6271                         return FALSE;
6272                 line++;
6273                 if (line->type == LINE_DIFF_CHUNK ||
6274                     line->type == LINE_DIFF_HEADER)
6275                         break;
6276         }
6278         return TRUE;
6281 static struct line *
6282 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6284         for (; view->line < line; line--)
6285                 if (line->type == type)
6286                         return line;
6288         return NULL;
6291 static bool
6292 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6294         const char *apply_argv[SIZEOF_ARG] = {
6295                 "git", "apply", "--whitespace=nowarn", NULL
6296         };
6297         struct line *diff_hdr;
6298         struct io io = {};
6299         int argc = 3;
6301         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6302         if (!diff_hdr)
6303                 return FALSE;
6305         if (!revert)
6306                 apply_argv[argc++] = "--cached";
6307         if (revert || stage_line_type == LINE_STAT_STAGED)
6308                 apply_argv[argc++] = "-R";
6309         apply_argv[argc++] = "-";
6310         apply_argv[argc++] = NULL;
6311         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6312                 return FALSE;
6314         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6315             !stage_diff_write(&io, chunk, view->line + view->lines))
6316                 chunk = NULL;
6318         io_done(&io);
6319         io_run_bg(update_index_argv);
6321         return chunk ? TRUE : FALSE;
6324 static bool
6325 stage_update(struct view *view, struct line *line)
6327         struct line *chunk = NULL;
6329         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6330                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6332         if (chunk) {
6333                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6334                         report("Failed to apply chunk");
6335                         return FALSE;
6336                 }
6338         } else if (!stage_status.status) {
6339                 view = VIEW(REQ_VIEW_STATUS);
6341                 for (line = view->line; line < view->line + view->lines; line++)
6342                         if (line->type == stage_line_type)
6343                                 break;
6345                 if (!status_update_files(view, line + 1)) {
6346                         report("Failed to update files");
6347                         return FALSE;
6348                 }
6350         } else if (!status_update_file(&stage_status, stage_line_type)) {
6351                 report("Failed to update file");
6352                 return FALSE;
6353         }
6355         return TRUE;
6358 static bool
6359 stage_revert(struct view *view, struct line *line)
6361         struct line *chunk = NULL;
6363         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6364                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6366         if (chunk) {
6367                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6368                         return FALSE;
6370                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6371                         report("Failed to revert chunk");
6372                         return FALSE;
6373                 }
6374                 return TRUE;
6376         } else {
6377                 return status_revert(stage_status.status ? &stage_status : NULL,
6378                                      stage_line_type, FALSE);
6379         }
6383 static void
6384 stage_next(struct view *view, struct line *line)
6386         int i;
6388         if (!stage_chunks) {
6389                 for (line = view->line; line < view->line + view->lines; line++) {
6390                         if (line->type != LINE_DIFF_CHUNK)
6391                                 continue;
6393                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6394                                 report("Allocation failure");
6395                                 return;
6396                         }
6398                         stage_chunk[stage_chunks++] = line - view->line;
6399                 }
6400         }
6402         for (i = 0; i < stage_chunks; i++) {
6403                 if (stage_chunk[i] > view->lineno) {
6404                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6405                         report("Chunk %d of %d", i + 1, stage_chunks);
6406                         return;
6407                 }
6408         }
6410         report("No next chunk found");
6413 static enum request
6414 stage_request(struct view *view, enum request request, struct line *line)
6416         switch (request) {
6417         case REQ_STATUS_UPDATE:
6418                 if (!stage_update(view, line))
6419                         return REQ_NONE;
6420                 break;
6422         case REQ_STATUS_REVERT:
6423                 if (!stage_revert(view, line))
6424                         return REQ_NONE;
6425                 break;
6427         case REQ_STAGE_NEXT:
6428                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6429                         report("File is untracked; press %s to add",
6430                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6431                         return REQ_NONE;
6432                 }
6433                 stage_next(view, line);
6434                 return REQ_NONE;
6436         case REQ_EDIT:
6437                 if (!stage_status.new.name[0])
6438                         return request;
6439                 if (stage_status.status == 'D') {
6440                         report("File has been deleted.");
6441                         return REQ_NONE;
6442                 }
6444                 open_editor(stage_status.new.name);
6445                 break;
6447         case REQ_REFRESH:
6448                 /* Reload everything ... */
6449                 break;
6451         case REQ_VIEW_BLAME:
6452                 if (stage_status.new.name[0]) {
6453                         string_copy(opt_file, stage_status.new.name);
6454                         opt_ref[0] = 0;
6455                 }
6456                 return request;
6458         case REQ_ENTER:
6459                 return pager_request(view, request, line);
6461         default:
6462                 return request;
6463         }
6465         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6466         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6468         /* Check whether the staged entry still exists, and close the
6469          * stage view if it doesn't. */
6470         if (!status_exists(&stage_status, stage_line_type)) {
6471                 status_restore(VIEW(REQ_VIEW_STATUS));
6472                 return REQ_VIEW_CLOSE;
6473         }
6475         if (stage_line_type == LINE_STAT_UNTRACKED) {
6476                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6477                         report("Cannot display a directory");
6478                         return REQ_NONE;
6479                 }
6481                 if (!prepare_update_file(view, stage_status.new.name)) {
6482                         report("Failed to open file: %s", strerror(errno));
6483                         return REQ_NONE;
6484                 }
6485         }
6486         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6488         return REQ_NONE;
6491 static struct view_ops stage_ops = {
6492         "line",
6493         NULL,
6494         NULL,
6495         pager_read,
6496         pager_draw,
6497         stage_request,
6498         pager_grep,
6499         pager_select,
6500 };
6503 /*
6504  * Revision graph
6505  */
6507 struct commit {
6508         char id[SIZEOF_REV];            /* SHA1 ID. */
6509         char title[128];                /* First line of the commit message. */
6510         const char *author;             /* Author of the commit. */
6511         struct time time;               /* Date from the author ident. */
6512         struct ref_list *refs;          /* Repository references. */
6513         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6514         size_t graph_size;              /* The width of the graph array. */
6515         bool has_parents;               /* Rewritten --parents seen. */
6516 };
6518 /* Size of rev graph with no  "padding" columns */
6519 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6521 struct rev_graph {
6522         struct rev_graph *prev, *next, *parents;
6523         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6524         size_t size;
6525         struct commit *commit;
6526         size_t pos;
6527         unsigned int boundary:1;
6528 };
6530 /* Parents of the commit being visualized. */
6531 static struct rev_graph graph_parents[4];
6533 /* The current stack of revisions on the graph. */
6534 static struct rev_graph graph_stacks[4] = {
6535         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6536         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6537         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6538         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6539 };
6541 static inline bool
6542 graph_parent_is_merge(struct rev_graph *graph)
6544         return graph->parents->size > 1;
6547 static inline void
6548 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6550         struct commit *commit = graph->commit;
6552         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6553                 commit->graph[commit->graph_size++] = symbol;
6556 static void
6557 clear_rev_graph(struct rev_graph *graph)
6559         graph->boundary = 0;
6560         graph->size = graph->pos = 0;
6561         graph->commit = NULL;
6562         memset(graph->parents, 0, sizeof(*graph->parents));
6565 static void
6566 done_rev_graph(struct rev_graph *graph)
6568         if (graph_parent_is_merge(graph) &&
6569             graph->pos < graph->size - 1 &&
6570             graph->next->size == graph->size + graph->parents->size - 1) {
6571                 size_t i = graph->pos + graph->parents->size - 1;
6573                 graph->commit->graph_size = i * 2;
6574                 while (i < graph->next->size - 1) {
6575                         append_to_rev_graph(graph, ' ');
6576                         append_to_rev_graph(graph, '\\');
6577                         i++;
6578                 }
6579         }
6581         clear_rev_graph(graph);
6584 static void
6585 push_rev_graph(struct rev_graph *graph, const char *parent)
6587         int i;
6589         /* "Collapse" duplicate parents lines.
6590          *
6591          * FIXME: This needs to also update update the drawn graph but
6592          * for now it just serves as a method for pruning graph lines. */
6593         for (i = 0; i < graph->size; i++)
6594                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6595                         return;
6597         if (graph->size < SIZEOF_REVITEMS) {
6598                 string_copy_rev(graph->rev[graph->size++], parent);
6599         }
6602 static chtype
6603 get_rev_graph_symbol(struct rev_graph *graph)
6605         chtype symbol;
6607         if (graph->boundary)
6608                 symbol = REVGRAPH_BOUND;
6609         else if (graph->parents->size == 0)
6610                 symbol = REVGRAPH_INIT;
6611         else if (graph_parent_is_merge(graph))
6612                 symbol = REVGRAPH_MERGE;
6613         else if (graph->pos >= graph->size)
6614                 symbol = REVGRAPH_BRANCH;
6615         else
6616                 symbol = REVGRAPH_COMMIT;
6618         return symbol;
6621 static void
6622 draw_rev_graph(struct rev_graph *graph)
6624         struct rev_filler {
6625                 chtype separator, line;
6626         };
6627         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6628         static struct rev_filler fillers[] = {
6629                 { ' ',  '|' },
6630                 { '`',  '.' },
6631                 { '\'', ' ' },
6632                 { '/',  ' ' },
6633         };
6634         chtype symbol = get_rev_graph_symbol(graph);
6635         struct rev_filler *filler;
6636         size_t i;
6638         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6639         filler = &fillers[DEFAULT];
6641         for (i = 0; i < graph->pos; i++) {
6642                 append_to_rev_graph(graph, filler->line);
6643                 if (graph_parent_is_merge(graph->prev) &&
6644                     graph->prev->pos == i)
6645                         filler = &fillers[RSHARP];
6647                 append_to_rev_graph(graph, filler->separator);
6648         }
6650         /* Place the symbol for this revision. */
6651         append_to_rev_graph(graph, symbol);
6653         if (graph->prev->size > graph->size)
6654                 filler = &fillers[RDIAG];
6655         else
6656                 filler = &fillers[DEFAULT];
6658         i++;
6660         for (; i < graph->size; i++) {
6661                 append_to_rev_graph(graph, filler->separator);
6662                 append_to_rev_graph(graph, filler->line);
6663                 if (graph_parent_is_merge(graph->prev) &&
6664                     i < graph->prev->pos + graph->parents->size)
6665                         filler = &fillers[RSHARP];
6666                 if (graph->prev->size > graph->size)
6667                         filler = &fillers[LDIAG];
6668         }
6670         if (graph->prev->size > graph->size) {
6671                 append_to_rev_graph(graph, filler->separator);
6672                 if (filler->line != ' ')
6673                         append_to_rev_graph(graph, filler->line);
6674         }
6677 /* Prepare the next rev graph */
6678 static void
6679 prepare_rev_graph(struct rev_graph *graph)
6681         size_t i;
6683         /* First, traverse all lines of revisions up to the active one. */
6684         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6685                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6686                         break;
6688                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6689         }
6691         /* Interleave the new revision parent(s). */
6692         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6693                 push_rev_graph(graph->next, graph->parents->rev[i]);
6695         /* Lastly, put any remaining revisions. */
6696         for (i = graph->pos + 1; i < graph->size; i++)
6697                 push_rev_graph(graph->next, graph->rev[i]);
6700 static void
6701 update_rev_graph(struct view *view, struct rev_graph *graph)
6703         /* If this is the finalizing update ... */
6704         if (graph->commit)
6705                 prepare_rev_graph(graph);
6707         /* Graph visualization needs a one rev look-ahead,
6708          * so the first update doesn't visualize anything. */
6709         if (!graph->prev->commit)
6710                 return;
6712         if (view->lines > 2)
6713                 view->line[view->lines - 3].dirty = 1;
6714         if (view->lines > 1)
6715                 view->line[view->lines - 2].dirty = 1;
6716         draw_rev_graph(graph->prev);
6717         done_rev_graph(graph->prev->prev);
6721 /*
6722  * Main view backend
6723  */
6725 static const char *main_argv[SIZEOF_ARG] = {
6726         "git", "log", "--no-color", "--pretty=raw", "--parents",
6727                       "--topo-order", "%(head)", NULL
6728 };
6730 static bool
6731 main_draw(struct view *view, struct line *line, unsigned int lineno)
6733         struct commit *commit = line->data;
6735         if (!commit->author)
6736                 return FALSE;
6738         if (opt_date && draw_date(view, &commit->time))
6739                 return TRUE;
6741         if (opt_author && draw_author(view, commit->author))
6742                 return TRUE;
6744         if (opt_rev_graph && commit->graph_size &&
6745             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6746                 return TRUE;
6748         if (opt_show_refs && commit->refs) {
6749                 size_t i;
6751                 for (i = 0; i < commit->refs->size; i++) {
6752                         struct ref *ref = commit->refs->refs[i];
6753                         enum line_type type;
6755                         if (ref->head)
6756                                 type = LINE_MAIN_HEAD;
6757                         else if (ref->ltag)
6758                                 type = LINE_MAIN_LOCAL_TAG;
6759                         else if (ref->tag)
6760                                 type = LINE_MAIN_TAG;
6761                         else if (ref->tracked)
6762                                 type = LINE_MAIN_TRACKED;
6763                         else if (ref->remote)
6764                                 type = LINE_MAIN_REMOTE;
6765                         else
6766                                 type = LINE_MAIN_REF;
6768                         if (draw_text(view, type, "[", TRUE) ||
6769                             draw_text(view, type, ref->name, TRUE) ||
6770                             draw_text(view, type, "]", TRUE))
6771                                 return TRUE;
6773                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6774                                 return TRUE;
6775                 }
6776         }
6778         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6779         return TRUE;
6782 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6783 static bool
6784 main_read(struct view *view, char *line)
6786         static struct rev_graph *graph = graph_stacks;
6787         enum line_type type;
6788         struct commit *commit;
6790         if (!line) {
6791                 int i;
6793                 if (!view->lines && !view->prev)
6794                         die("No revisions match the given arguments.");
6795                 if (view->lines > 0) {
6796                         commit = view->line[view->lines - 1].data;
6797                         view->line[view->lines - 1].dirty = 1;
6798                         if (!commit->author) {
6799                                 view->lines--;
6800                                 free(commit);
6801                                 graph->commit = NULL;
6802                         }
6803                 }
6804                 update_rev_graph(view, graph);
6806                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6807                         clear_rev_graph(&graph_stacks[i]);
6808                 return TRUE;
6809         }
6811         type = get_line_type(line);
6812         if (type == LINE_COMMIT) {
6813                 commit = calloc(1, sizeof(struct commit));
6814                 if (!commit)
6815                         return FALSE;
6817                 line += STRING_SIZE("commit ");
6818                 if (*line == '-') {
6819                         graph->boundary = 1;
6820                         line++;
6821                 }
6823                 string_copy_rev(commit->id, line);
6824                 commit->refs = get_ref_list(commit->id);
6825                 graph->commit = commit;
6826                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6828                 while ((line = strchr(line, ' '))) {
6829                         line++;
6830                         push_rev_graph(graph->parents, line);
6831                         commit->has_parents = TRUE;
6832                 }
6833                 return TRUE;
6834         }
6836         if (!view->lines)
6837                 return TRUE;
6838         commit = view->line[view->lines - 1].data;
6840         switch (type) {
6841         case LINE_PARENT:
6842                 if (commit->has_parents)
6843                         break;
6844                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6845                 break;
6847         case LINE_AUTHOR:
6848                 parse_author_line(line + STRING_SIZE("author "),
6849                                   &commit->author, &commit->time);
6850                 update_rev_graph(view, graph);
6851                 graph = graph->next;
6852                 break;
6854         default:
6855                 /* Fill in the commit title if it has not already been set. */
6856                 if (commit->title[0])
6857                         break;
6859                 /* Require titles to start with a non-space character at the
6860                  * offset used by git log. */
6861                 if (strncmp(line, "    ", 4))
6862                         break;
6863                 line += 4;
6864                 /* Well, if the title starts with a whitespace character,
6865                  * try to be forgiving.  Otherwise we end up with no title. */
6866                 while (isspace(*line))
6867                         line++;
6868                 if (*line == '\0')
6869                         break;
6870                 /* FIXME: More graceful handling of titles; append "..." to
6871                  * shortened titles, etc. */
6873                 string_expand(commit->title, sizeof(commit->title), line, 1);
6874                 view->line[view->lines - 1].dirty = 1;
6875         }
6877         return TRUE;
6880 static enum request
6881 main_request(struct view *view, enum request request, struct line *line)
6883         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6885         switch (request) {
6886         case REQ_ENTER:
6887                 open_view(view, REQ_VIEW_DIFF, flags);
6888                 break;
6889         case REQ_REFRESH:
6890                 load_refs();
6891                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6892                 break;
6893         default:
6894                 return request;
6895         }
6897         return REQ_NONE;
6900 static bool
6901 grep_refs(struct ref_list *list, regex_t *regex)
6903         regmatch_t pmatch;
6904         size_t i;
6906         if (!opt_show_refs || !list)
6907                 return FALSE;
6909         for (i = 0; i < list->size; i++) {
6910                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6911                         return TRUE;
6912         }
6914         return FALSE;
6917 static bool
6918 main_grep(struct view *view, struct line *line)
6920         struct commit *commit = line->data;
6921         const char *text[] = {
6922                 commit->title,
6923                 opt_author ? commit->author : "",
6924                 mkdate(&commit->time, opt_date),
6925                 NULL
6926         };
6928         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6931 static void
6932 main_select(struct view *view, struct line *line)
6934         struct commit *commit = line->data;
6936         string_copy_rev(view->ref, commit->id);
6937         string_copy_rev(ref_commit, view->ref);
6940 static struct view_ops main_ops = {
6941         "commit",
6942         main_argv,
6943         NULL,
6944         main_read,
6945         main_draw,
6946         main_request,
6947         main_grep,
6948         main_select,
6949 };
6952 /*
6953  * Status management
6954  */
6956 /* Whether or not the curses interface has been initialized. */
6957 static bool cursed = FALSE;
6959 /* Terminal hacks and workarounds. */
6960 static bool use_scroll_redrawwin;
6961 static bool use_scroll_status_wclear;
6963 /* The status window is used for polling keystrokes. */
6964 static WINDOW *status_win;
6966 /* Reading from the prompt? */
6967 static bool input_mode = FALSE;
6969 static bool status_empty = FALSE;
6971 /* Update status and title window. */
6972 static void
6973 report(const char *msg, ...)
6975         struct view *view = display[current_view];
6977         if (input_mode)
6978                 return;
6980         if (!view) {
6981                 char buf[SIZEOF_STR];
6982                 va_list args;
6984                 va_start(args, msg);
6985                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6986                         buf[sizeof(buf) - 1] = 0;
6987                         buf[sizeof(buf) - 2] = '.';
6988                         buf[sizeof(buf) - 3] = '.';
6989                         buf[sizeof(buf) - 4] = '.';
6990                 }
6991                 va_end(args);
6992                 die("%s", buf);
6993         }
6995         if (!status_empty || *msg) {
6996                 va_list args;
6998                 va_start(args, msg);
7000                 wmove(status_win, 0, 0);
7001                 if (view->has_scrolled && use_scroll_status_wclear)
7002                         wclear(status_win);
7003                 if (*msg) {
7004                         vwprintw(status_win, msg, args);
7005                         status_empty = FALSE;
7006                 } else {
7007                         status_empty = TRUE;
7008                 }
7009                 wclrtoeol(status_win);
7010                 wnoutrefresh(status_win);
7012                 va_end(args);
7013         }
7015         update_view_title(view);
7018 static void
7019 init_display(void)
7021         const char *term;
7022         int x, y;
7024         /* Initialize the curses library */
7025         if (isatty(STDIN_FILENO)) {
7026                 cursed = !!initscr();
7027                 opt_tty = stdin;
7028         } else {
7029                 /* Leave stdin and stdout alone when acting as a pager. */
7030                 opt_tty = fopen("/dev/tty", "r+");
7031                 if (!opt_tty)
7032                         die("Failed to open /dev/tty");
7033                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7034         }
7036         if (!cursed)
7037                 die("Failed to initialize curses");
7039         nonl();         /* Disable conversion and detect newlines from input. */
7040         cbreak();       /* Take input chars one at a time, no wait for \n */
7041         noecho();       /* Don't echo input */
7042         leaveok(stdscr, FALSE);
7044         if (has_colors())
7045                 init_colors();
7047         getmaxyx(stdscr, y, x);
7048         status_win = newwin(1, 0, y - 1, 0);
7049         if (!status_win)
7050                 die("Failed to create status window");
7052         /* Enable keyboard mapping */
7053         keypad(status_win, TRUE);
7054         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7056         TABSIZE = opt_tab_size;
7058         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7059         if (term && !strcmp(term, "gnome-terminal")) {
7060                 /* In the gnome-terminal-emulator, the message from
7061                  * scrolling up one line when impossible followed by
7062                  * scrolling down one line causes corruption of the
7063                  * status line. This is fixed by calling wclear. */
7064                 use_scroll_status_wclear = TRUE;
7065                 use_scroll_redrawwin = FALSE;
7067         } else if (term && !strcmp(term, "xrvt-xpm")) {
7068                 /* No problems with full optimizations in xrvt-(unicode)
7069                  * and aterm. */
7070                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7072         } else {
7073                 /* When scrolling in (u)xterm the last line in the
7074                  * scrolling direction will update slowly. */
7075                 use_scroll_redrawwin = TRUE;
7076                 use_scroll_status_wclear = FALSE;
7077         }
7080 static int
7081 get_input(int prompt_position)
7083         struct view *view;
7084         int i, key, cursor_y, cursor_x;
7085         bool loading = FALSE;
7087         if (prompt_position)
7088                 input_mode = TRUE;
7090         while (TRUE) {
7091                 foreach_view (view, i) {
7092                         update_view(view);
7093                         if (view_is_displayed(view) && view->has_scrolled &&
7094                             use_scroll_redrawwin)
7095                                 redrawwin(view->win);
7096                         view->has_scrolled = FALSE;
7097                         if (view->pipe)
7098                                 loading = TRUE;
7099                 }
7101                 /* Update the cursor position. */
7102                 if (prompt_position) {
7103                         getbegyx(status_win, cursor_y, cursor_x);
7104                         cursor_x = prompt_position;
7105                 } else {
7106                         view = display[current_view];
7107                         getbegyx(view->win, cursor_y, cursor_x);
7108                         cursor_x = view->width - 1;
7109                         cursor_y += view->lineno - view->offset;
7110                 }
7111                 setsyx(cursor_y, cursor_x);
7113                 /* Refresh, accept single keystroke of input */
7114                 doupdate();
7115                 nodelay(status_win, loading);
7116                 key = wgetch(status_win);
7118                 /* wgetch() with nodelay() enabled returns ERR when
7119                  * there's no input. */
7120                 if (key == ERR) {
7122                 } else if (key == KEY_RESIZE) {
7123                         int height, width;
7125                         getmaxyx(stdscr, height, width);
7127                         wresize(status_win, 1, width);
7128                         mvwin(status_win, height - 1, 0);
7129                         wnoutrefresh(status_win);
7130                         resize_display();
7131                         redraw_display(TRUE);
7133                 } else {
7134                         input_mode = FALSE;
7135                         return key;
7136                 }
7137         }
7140 static char *
7141 prompt_input(const char *prompt, input_handler handler, void *data)
7143         enum input_status status = INPUT_OK;
7144         static char buf[SIZEOF_STR];
7145         size_t pos = 0;
7147         buf[pos] = 0;
7149         while (status == INPUT_OK || status == INPUT_SKIP) {
7150                 int key;
7152                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7153                 wclrtoeol(status_win);
7155                 key = get_input(pos + 1);
7156                 switch (key) {
7157                 case KEY_RETURN:
7158                 case KEY_ENTER:
7159                 case '\n':
7160                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7161                         break;
7163                 case KEY_BACKSPACE:
7164                         if (pos > 0)
7165                                 buf[--pos] = 0;
7166                         else
7167                                 status = INPUT_CANCEL;
7168                         break;
7170                 case KEY_ESC:
7171                         status = INPUT_CANCEL;
7172                         break;
7174                 default:
7175                         if (pos >= sizeof(buf)) {
7176                                 report("Input string too long");
7177                                 return NULL;
7178                         }
7180                         status = handler(data, buf, key);
7181                         if (status == INPUT_OK)
7182                                 buf[pos++] = (char) key;
7183                 }
7184         }
7186         /* Clear the status window */
7187         status_empty = FALSE;
7188         report("");
7190         if (status == INPUT_CANCEL)
7191                 return NULL;
7193         buf[pos++] = 0;
7195         return buf;
7198 static enum input_status
7199 prompt_yesno_handler(void *data, char *buf, int c)
7201         if (c == 'y' || c == 'Y')
7202                 return INPUT_STOP;
7203         if (c == 'n' || c == 'N')
7204                 return INPUT_CANCEL;
7205         return INPUT_SKIP;
7208 static bool
7209 prompt_yesno(const char *prompt)
7211         char prompt2[SIZEOF_STR];
7213         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7214                 return FALSE;
7216         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7219 static enum input_status
7220 read_prompt_handler(void *data, char *buf, int c)
7222         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7225 static char *
7226 read_prompt(const char *prompt)
7228         return prompt_input(prompt, read_prompt_handler, NULL);
7231 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7233         enum input_status status = INPUT_OK;
7234         int size = 0;
7236         while (items[size].text)
7237                 size++;
7239         while (status == INPUT_OK) {
7240                 const struct menu_item *item = &items[*selected];
7241                 int key;
7242                 int i;
7244                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7245                           prompt, *selected + 1, size);
7246                 if (item->hotkey)
7247                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7248                 wprintw(status_win, "%s", item->text);
7249                 wclrtoeol(status_win);
7251                 key = get_input(COLS - 1);
7252                 switch (key) {
7253                 case KEY_RETURN:
7254                 case KEY_ENTER:
7255                 case '\n':
7256                         status = INPUT_STOP;
7257                         break;
7259                 case KEY_LEFT:
7260                 case KEY_UP:
7261                         *selected = *selected - 1;
7262                         if (*selected < 0)
7263                                 *selected = size - 1;
7264                         break;
7266                 case KEY_RIGHT:
7267                 case KEY_DOWN:
7268                         *selected = (*selected + 1) % size;
7269                         break;
7271                 case KEY_ESC:
7272                         status = INPUT_CANCEL;
7273                         break;
7275                 default:
7276                         for (i = 0; items[i].text; i++)
7277                                 if (items[i].hotkey == key) {
7278                                         *selected = i;
7279                                         status = INPUT_STOP;
7280                                         break;
7281                                 }
7282                 }
7283         }
7285         /* Clear the status window */
7286         status_empty = FALSE;
7287         report("");
7289         return status != INPUT_CANCEL;
7292 /*
7293  * Repository properties
7294  */
7296 static struct ref **refs = NULL;
7297 static size_t refs_size = 0;
7298 static struct ref *refs_head = NULL;
7300 static struct ref_list **ref_lists = NULL;
7301 static size_t ref_lists_size = 0;
7303 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7304 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7305 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7307 static int
7308 compare_refs(const void *ref1_, const void *ref2_)
7310         const struct ref *ref1 = *(const struct ref **)ref1_;
7311         const struct ref *ref2 = *(const struct ref **)ref2_;
7313         if (ref1->tag != ref2->tag)
7314                 return ref2->tag - ref1->tag;
7315         if (ref1->ltag != ref2->ltag)
7316                 return ref2->ltag - ref2->ltag;
7317         if (ref1->head != ref2->head)
7318                 return ref2->head - ref1->head;
7319         if (ref1->tracked != ref2->tracked)
7320                 return ref2->tracked - ref1->tracked;
7321         if (ref1->remote != ref2->remote)
7322                 return ref2->remote - ref1->remote;
7323         return strcmp(ref1->name, ref2->name);
7326 static void
7327 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7329         size_t i;
7331         for (i = 0; i < refs_size; i++)
7332                 if (!visitor(data, refs[i]))
7333                         break;
7336 static struct ref *
7337 get_ref_head()
7339         return refs_head;
7342 static struct ref_list *
7343 get_ref_list(const char *id)
7345         struct ref_list *list;
7346         size_t i;
7348         for (i = 0; i < ref_lists_size; i++)
7349                 if (!strcmp(id, ref_lists[i]->id))
7350                         return ref_lists[i];
7352         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7353                 return NULL;
7354         list = calloc(1, sizeof(*list));
7355         if (!list)
7356                 return NULL;
7358         for (i = 0; i < refs_size; i++) {
7359                 if (!strcmp(id, refs[i]->id) &&
7360                     realloc_refs_list(&list->refs, list->size, 1))
7361                         list->refs[list->size++] = refs[i];
7362         }
7364         if (!list->refs) {
7365                 free(list);
7366                 return NULL;
7367         }
7369         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7370         ref_lists[ref_lists_size++] = list;
7371         return list;
7374 static int
7375 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7377         struct ref *ref = NULL;
7378         bool tag = FALSE;
7379         bool ltag = FALSE;
7380         bool remote = FALSE;
7381         bool tracked = FALSE;
7382         bool head = FALSE;
7383         int from = 0, to = refs_size - 1;
7385         if (!prefixcmp(name, "refs/tags/")) {
7386                 if (!suffixcmp(name, namelen, "^{}")) {
7387                         namelen -= 3;
7388                         name[namelen] = 0;
7389                 } else {
7390                         ltag = TRUE;
7391                 }
7393                 tag = TRUE;
7394                 namelen -= STRING_SIZE("refs/tags/");
7395                 name    += STRING_SIZE("refs/tags/");
7397         } else if (!prefixcmp(name, "refs/remotes/")) {
7398                 remote = TRUE;
7399                 namelen -= STRING_SIZE("refs/remotes/");
7400                 name    += STRING_SIZE("refs/remotes/");
7401                 tracked  = !strcmp(opt_remote, name);
7403         } else if (!prefixcmp(name, "refs/heads/")) {
7404                 namelen -= STRING_SIZE("refs/heads/");
7405                 name    += STRING_SIZE("refs/heads/");
7406                 if (!strncmp(opt_head, name, namelen))
7407                         return OK;
7409         } else if (!strcmp(name, "HEAD")) {
7410                 head     = TRUE;
7411                 if (*opt_head) {
7412                         namelen  = strlen(opt_head);
7413                         name     = opt_head;
7414                 }
7415         }
7417         /* If we are reloading or it's an annotated tag, replace the
7418          * previous SHA1 with the resolved commit id; relies on the fact
7419          * git-ls-remote lists the commit id of an annotated tag right
7420          * before the commit id it points to. */
7421         while (from <= to) {
7422                 size_t pos = (to + from) / 2;
7423                 int cmp = strcmp(name, refs[pos]->name);
7425                 if (!cmp) {
7426                         ref = refs[pos];
7427                         break;
7428                 }
7430                 if (cmp < 0)
7431                         to = pos - 1;
7432                 else
7433                         from = pos + 1;
7434         }
7436         if (!ref) {
7437                 if (!realloc_refs(&refs, refs_size, 1))
7438                         return ERR;
7439                 ref = calloc(1, sizeof(*ref) + namelen);
7440                 if (!ref)
7441                         return ERR;
7442                 memmove(refs + from + 1, refs + from,
7443                         (refs_size - from) * sizeof(*refs));
7444                 refs[from] = ref;
7445                 strncpy(ref->name, name, namelen);
7446                 refs_size++;
7447         }
7449         ref->head = head;
7450         ref->tag = tag;
7451         ref->ltag = ltag;
7452         ref->remote = remote;
7453         ref->tracked = tracked;
7454         string_copy_rev(ref->id, id);
7456         if (head)
7457                 refs_head = ref;
7458         return OK;
7461 static int
7462 load_refs(void)
7464         const char *head_argv[] = {
7465                 "git", "symbolic-ref", "HEAD", NULL
7466         };
7467         static const char *ls_remote_argv[SIZEOF_ARG] = {
7468                 "git", "ls-remote", opt_git_dir, NULL
7469         };
7470         static bool init = FALSE;
7471         size_t i;
7473         if (!init) {
7474                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7475                         die("TIG_LS_REMOTE contains too many arguments");
7476                 init = TRUE;
7477         }
7479         if (!*opt_git_dir)
7480                 return OK;
7482         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7483             !prefixcmp(opt_head, "refs/heads/")) {
7484                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7486                 memmove(opt_head, offset, strlen(offset) + 1);
7487         }
7489         refs_head = NULL;
7490         for (i = 0; i < refs_size; i++)
7491                 refs[i]->id[0] = 0;
7493         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7494                 return ERR;
7496         /* Update the ref lists to reflect changes. */
7497         for (i = 0; i < ref_lists_size; i++) {
7498                 struct ref_list *list = ref_lists[i];
7499                 size_t old, new;
7501                 for (old = new = 0; old < list->size; old++)
7502                         if (!strcmp(list->id, list->refs[old]->id))
7503                                 list->refs[new++] = list->refs[old];
7504                 list->size = new;
7505         }
7507         return OK;
7510 static void
7511 set_remote_branch(const char *name, const char *value, size_t valuelen)
7513         if (!strcmp(name, ".remote")) {
7514                 string_ncopy(opt_remote, value, valuelen);
7516         } else if (*opt_remote && !strcmp(name, ".merge")) {
7517                 size_t from = strlen(opt_remote);
7519                 if (!prefixcmp(value, "refs/heads/"))
7520                         value += STRING_SIZE("refs/heads/");
7522                 if (!string_format_from(opt_remote, &from, "/%s", value))
7523                         opt_remote[0] = 0;
7524         }
7527 static void
7528 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7530         const char *argv[SIZEOF_ARG] = { name, "=" };
7531         int argc = 1 + (cmd == option_set_command);
7532         int error = ERR;
7534         if (!argv_from_string(argv, &argc, value))
7535                 config_msg = "Too many option arguments";
7536         else
7537                 error = cmd(argc, argv);
7539         if (error == ERR)
7540                 warn("Option 'tig.%s': %s", name, config_msg);
7543 static bool
7544 set_environment_variable(const char *name, const char *value)
7546         size_t len = strlen(name) + 1 + strlen(value) + 1;
7547         char *env = malloc(len);
7549         if (env &&
7550             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7551             putenv(env) == 0)
7552                 return TRUE;
7553         free(env);
7554         return FALSE;
7557 static void
7558 set_work_tree(const char *value)
7560         char cwd[SIZEOF_STR];
7562         if (!getcwd(cwd, sizeof(cwd)))
7563                 die("Failed to get cwd path: %s", strerror(errno));
7564         if (chdir(opt_git_dir) < 0)
7565                 die("Failed to chdir(%s): %s", strerror(errno));
7566         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7567                 die("Failed to get git path: %s", strerror(errno));
7568         if (chdir(cwd) < 0)
7569                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7570         if (chdir(value) < 0)
7571                 die("Failed to chdir(%s): %s", value, strerror(errno));
7572         if (!getcwd(cwd, sizeof(cwd)))
7573                 die("Failed to get cwd path: %s", strerror(errno));
7574         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7575                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7576         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7577                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7578         opt_is_inside_work_tree = TRUE;
7581 static int
7582 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7584         if (!strcmp(name, "i18n.commitencoding"))
7585                 string_ncopy(opt_encoding, value, valuelen);
7587         else if (!strcmp(name, "core.editor"))
7588                 string_ncopy(opt_editor, value, valuelen);
7590         else if (!strcmp(name, "core.worktree"))
7591                 set_work_tree(value);
7593         else if (!prefixcmp(name, "tig.color."))
7594                 set_repo_config_option(name + 10, value, option_color_command);
7596         else if (!prefixcmp(name, "tig.bind."))
7597                 set_repo_config_option(name + 9, value, option_bind_command);
7599         else if (!prefixcmp(name, "tig."))
7600                 set_repo_config_option(name + 4, value, option_set_command);
7602         else if (*opt_head && !prefixcmp(name, "branch.") &&
7603                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7604                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7606         return OK;
7609 static int
7610 load_git_config(void)
7612         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7614         return io_run_load(config_list_argv, "=", read_repo_config_option);
7617 static int
7618 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7620         if (!opt_git_dir[0]) {
7621                 string_ncopy(opt_git_dir, name, namelen);
7623         } else if (opt_is_inside_work_tree == -1) {
7624                 /* This can be 3 different values depending on the
7625                  * version of git being used. If git-rev-parse does not
7626                  * understand --is-inside-work-tree it will simply echo
7627                  * the option else either "true" or "false" is printed.
7628                  * Default to true for the unknown case. */
7629                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7631         } else if (*name == '.') {
7632                 string_ncopy(opt_cdup, name, namelen);
7634         } else {
7635                 string_ncopy(opt_prefix, name, namelen);
7636         }
7638         return OK;
7641 static int
7642 load_repo_info(void)
7644         const char *rev_parse_argv[] = {
7645                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7646                         "--show-cdup", "--show-prefix", NULL
7647         };
7649         return io_run_load(rev_parse_argv, "=", read_repo_info);
7653 /*
7654  * Main
7655  */
7657 static const char usage[] =
7658 "tig " TIG_VERSION " (" __DATE__ ")\n"
7659 "\n"
7660 "Usage: tig        [options] [revs] [--] [paths]\n"
7661 "   or: tig show   [options] [revs] [--] [paths]\n"
7662 "   or: tig blame  [rev] path\n"
7663 "   or: tig status\n"
7664 "   or: tig <      [git command output]\n"
7665 "\n"
7666 "Options:\n"
7667 "  -v, --version   Show version and exit\n"
7668 "  -h, --help      Show help message and exit";
7670 static void __NORETURN
7671 quit(int sig)
7673         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7674         if (cursed)
7675                 endwin();
7676         exit(0);
7679 static void __NORETURN
7680 die(const char *err, ...)
7682         va_list args;
7684         endwin();
7686         va_start(args, err);
7687         fputs("tig: ", stderr);
7688         vfprintf(stderr, err, args);
7689         fputs("\n", stderr);
7690         va_end(args);
7692         exit(1);
7695 static void
7696 warn(const char *msg, ...)
7698         va_list args;
7700         va_start(args, msg);
7701         fputs("tig warning: ", stderr);
7702         vfprintf(stderr, msg, args);
7703         fputs("\n", stderr);
7704         va_end(args);
7707 static enum request
7708 parse_options(int argc, const char *argv[])
7710         enum request request = REQ_VIEW_MAIN;
7711         const char *subcommand;
7712         bool seen_dashdash = FALSE;
7713         /* XXX: This is vulnerable to the user overriding options
7714          * required for the main view parser. */
7715         const char *custom_argv[SIZEOF_ARG] = {
7716                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7717                         "--topo-order", NULL
7718         };
7719         int i, j = 6;
7721         if (!isatty(STDIN_FILENO)) {
7722                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7723                 return REQ_VIEW_PAGER;
7724         }
7726         if (argc <= 1)
7727                 return REQ_NONE;
7729         subcommand = argv[1];
7730         if (!strcmp(subcommand, "status")) {
7731                 if (argc > 2)
7732                         warn("ignoring arguments after `%s'", subcommand);
7733                 return REQ_VIEW_STATUS;
7735         } else if (!strcmp(subcommand, "blame")) {
7736                 if (argc <= 2 || argc > 4)
7737                         die("invalid number of options to blame\n\n%s", usage);
7739                 i = 2;
7740                 if (argc == 4) {
7741                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7742                         i++;
7743                 }
7745                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7746                 return REQ_VIEW_BLAME;
7748         } else if (!strcmp(subcommand, "show")) {
7749                 request = REQ_VIEW_DIFF;
7751         } else {
7752                 subcommand = NULL;
7753         }
7755         if (subcommand) {
7756                 custom_argv[1] = subcommand;
7757                 j = 2;
7758         }
7760         for (i = 1 + !!subcommand; i < argc; i++) {
7761                 const char *opt = argv[i];
7763                 if (seen_dashdash || !strcmp(opt, "--")) {
7764                         seen_dashdash = TRUE;
7766                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7767                         printf("tig version %s\n", TIG_VERSION);
7768                         quit(0);
7770                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7771                         printf("%s\n", usage);
7772                         quit(0);
7773                 }
7775                 custom_argv[j++] = opt;
7776                 if (j >= ARRAY_SIZE(custom_argv))
7777                         die("command too long");
7778         }
7780         if (!prepare_update(VIEW(request), custom_argv, NULL))
7781                 die("Failed to format arguments");
7783         return request;
7786 int
7787 main(int argc, const char *argv[])
7789         const char *codeset = "UTF-8";
7790         enum request request = parse_options(argc, argv);
7791         struct view *view;
7792         size_t i;
7794         signal(SIGINT, quit);
7795         signal(SIGPIPE, SIG_IGN);
7797         if (setlocale(LC_ALL, "")) {
7798                 codeset = nl_langinfo(CODESET);
7799         }
7801         if (load_repo_info() == ERR)
7802                 die("Failed to load repo info.");
7804         if (load_options() == ERR)
7805                 die("Failed to load user config.");
7807         if (load_git_config() == ERR)
7808                 die("Failed to load repo config.");
7810         /* Require a git repository unless when running in pager mode. */
7811         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7812                 die("Not a git repository");
7814         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7815                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7816                 if (opt_iconv_in == ICONV_NONE)
7817                         die("Failed to initialize character set conversion");
7818         }
7820         if (codeset && strcmp(codeset, "UTF-8")) {
7821                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7822                 if (opt_iconv_out == ICONV_NONE)
7823                         die("Failed to initialize character set conversion");
7824         }
7826         if (load_refs() == ERR)
7827                 die("Failed to load refs.");
7829         foreach_view (view, i)
7830                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7831                         die("Too many arguments in the `%s` environment variable",
7832                             view->cmd_env);
7834         init_display();
7836         if (request != REQ_NONE)
7837                 open_view(NULL, request, OPEN_PREPARED);
7838         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7840         while (view_driver(display[current_view], request)) {
7841                 int key = get_input(0);
7843                 view = display[current_view];
7844                 request = get_keybinding(view->keymap, key);
7846                 /* Some low-level request handling. This keeps access to
7847                  * status_win restricted. */
7848                 switch (request) {
7849                 case REQ_NONE:
7850                         report("Unknown key, press %s for help",
7851                                get_key(view->keymap, REQ_VIEW_HELP));
7852                         break;
7853                 case REQ_PROMPT:
7854                 {
7855                         char *cmd = read_prompt(":");
7857                         if (cmd && isdigit(*cmd)) {
7858                                 int lineno = view->lineno + 1;
7860                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7861                                         select_view_line(view, lineno - 1);
7862                                         report("");
7863                                 } else {
7864                                         report("Unable to parse '%s' as a line number", cmd);
7865                                 }
7867                         } else if (cmd) {
7868                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7869                                 const char *argv[SIZEOF_ARG] = { "git" };
7870                                 int argc = 1;
7872                                 /* When running random commands, initially show the
7873                                  * command in the title. However, it maybe later be
7874                                  * overwritten if a commit line is selected. */
7875                                 string_ncopy(next->ref, cmd, strlen(cmd));
7877                                 if (!argv_from_string(argv, &argc, cmd)) {
7878                                         report("Too many arguments");
7879                                 } else if (!prepare_update(next, argv, NULL)) {
7880                                         report("Failed to format command");
7881                                 } else {
7882                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7883                                 }
7884                         }
7886                         request = REQ_NONE;
7887                         break;
7888                 }
7889                 case REQ_SEARCH:
7890                 case REQ_SEARCH_BACK:
7891                 {
7892                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7893                         char *search = read_prompt(prompt);
7895                         if (search)
7896                                 string_ncopy(opt_search, search, strlen(search));
7897                         else if (*opt_search)
7898                                 request = request == REQ_SEARCH ?
7899                                         REQ_FIND_NEXT :
7900                                         REQ_FIND_PREV;
7901                         else
7902                                 request = REQ_NONE;
7903                         break;
7904                 }
7905                 default:
7906                         break;
7907                 }
7908         }
7910         quit(0);
7912         return 0;