Code

Make the blame view format its own command arguments
[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]);
691 /*
692  * Executing external commands.
693  */
695 enum io_type {
696         IO_FD,                  /* File descriptor based IO. */
697         IO_BG,                  /* Execute command in the background. */
698         IO_FG,                  /* Execute command with same std{in,out,err}. */
699         IO_RD,                  /* Read only fork+exec IO. */
700         IO_WR,                  /* Write only fork+exec IO. */
701         IO_AP,                  /* Append fork+exec output to file. */
702 };
704 struct io {
705         enum io_type type;      /* The requested type of pipe. */
706         const char *dir;        /* Directory from which to execute. */
707         pid_t pid;              /* PID of spawned process. */
708         int pipe;               /* Pipe end for reading or writing. */
709         int error;              /* Error status. */
710         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
711         char *buf;              /* Read buffer. */
712         size_t bufalloc;        /* Allocated buffer size. */
713         size_t bufsize;         /* Buffer content size. */
714         char *bufpos;           /* Current buffer position. */
715         unsigned int eof:1;     /* Has end of file been reached. */
716 };
718 static void
719 io_reset(struct io *io)
721         io->pipe = -1;
722         io->pid = 0;
723         io->buf = io->bufpos = NULL;
724         io->bufalloc = io->bufsize = 0;
725         io->error = 0;
726         io->eof = 0;
729 static void
730 io_init(struct io *io, const char *dir, enum io_type type)
732         io_reset(io);
733         io->type = type;
734         io->dir = dir;
737 static bool
738 io_format(struct io *io, const char *dir, enum io_type type,
739           const char *argv[], enum format_flags flags)
741         io_init(io, dir, type);
742         return format_argv(io->argv, argv, flags);
745 static bool
746 io_open(struct io *io, const char *fmt, ...)
748         char name[SIZEOF_STR] = "";
749         bool fits;
750         va_list args;
752         io_init(io, NULL, IO_FD);
754         va_start(args, fmt);
755         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
756         va_end(args);
758         if (!fits) {
759                 io->error = ENAMETOOLONG;
760                 return FALSE;
761         }
762         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
763         if (io->pipe == -1)
764                 io->error = errno;
765         return io->pipe != -1;
768 static bool
769 io_kill(struct io *io)
771         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
774 static bool
775 io_done(struct io *io)
777         pid_t pid = io->pid;
779         if (io->pipe != -1)
780                 close(io->pipe);
781         free(io->buf);
782         io_reset(io);
784         while (pid > 0) {
785                 int status;
786                 pid_t waiting = waitpid(pid, &status, 0);
788                 if (waiting < 0) {
789                         if (errno == EINTR)
790                                 continue;
791                         io->error = errno;
792                         return FALSE;
793                 }
795                 return waiting == pid &&
796                        !WIFSIGNALED(status) &&
797                        WIFEXITED(status) &&
798                        !WEXITSTATUS(status);
799         }
801         return TRUE;
804 static bool
805 io_start(struct io *io)
807         int pipefds[2] = { -1, -1 };
809         if (io->type == IO_FD)
810                 return TRUE;
812         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
813                 io->error = errno;
814                 return FALSE;
815         } else if (io->type == IO_AP) {
816                 pipefds[1] = io->pipe;
817         }
819         if ((io->pid = fork())) {
820                 if (io->pid == -1)
821                         io->error = errno;
822                 if (pipefds[!(io->type == IO_WR)] != -1)
823                         close(pipefds[!(io->type == IO_WR)]);
824                 if (io->pid != -1) {
825                         io->pipe = pipefds[!!(io->type == IO_WR)];
826                         return TRUE;
827                 }
829         } else {
830                 if (io->type != IO_FG) {
831                         int devnull = open("/dev/null", O_RDWR);
832                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
833                         int writefd = (io->type == IO_RD || io->type == IO_AP)
834                                                         ? pipefds[1] : devnull;
836                         dup2(readfd,  STDIN_FILENO);
837                         dup2(writefd, STDOUT_FILENO);
838                         dup2(devnull, STDERR_FILENO);
840                         close(devnull);
841                         if (pipefds[0] != -1)
842                                 close(pipefds[0]);
843                         if (pipefds[1] != -1)
844                                 close(pipefds[1]);
845                 }
847                 if (io->dir && *io->dir && chdir(io->dir) == -1)
848                         exit(errno);
850                 execvp(io->argv[0], (char *const*) io->argv);
851                 exit(errno);
852         }
854         if (pipefds[!!(io->type == IO_WR)] != -1)
855                 close(pipefds[!!(io->type == IO_WR)]);
856         return FALSE;
859 static bool
860 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
862         io_init(io, dir, type);
863         if (!format_argv(io->argv, argv, FORMAT_NONE))
864                 return FALSE;
865         return io_start(io);
868 static int
869 io_complete(struct io *io)
871         return io_start(io) && io_done(io);
874 static int
875 io_run_bg(const char **argv)
877         struct io io = {};
879         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
880                 return FALSE;
881         return io_complete(&io);
884 static bool
885 io_run_fg(const char **argv, const char *dir)
887         struct io io = {};
889         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
890                 return FALSE;
891         return io_complete(&io);
894 static bool
895 io_run_append(const char **argv, enum format_flags flags, int fd)
897         struct io io = {};
899         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
900                 close(fd);
901                 return FALSE;
902         }
904         io.pipe = fd;
905         return io_complete(&io);
908 static bool
909 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
911         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
914 static bool
915 io_eof(struct io *io)
917         return io->eof;
920 static int
921 io_error(struct io *io)
923         return io->error;
926 static char *
927 io_strerror(struct io *io)
929         return strerror(io->error);
932 static bool
933 io_can_read(struct io *io)
935         struct timeval tv = { 0, 500 };
936         fd_set fds;
938         FD_ZERO(&fds);
939         FD_SET(io->pipe, &fds);
941         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
944 static ssize_t
945 io_read(struct io *io, void *buf, size_t bufsize)
947         do {
948                 ssize_t readsize = read(io->pipe, buf, bufsize);
950                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
951                         continue;
952                 else if (readsize == -1)
953                         io->error = errno;
954                 else if (readsize == 0)
955                         io->eof = 1;
956                 return readsize;
957         } while (1);
960 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
962 static char *
963 io_get(struct io *io, int c, bool can_read)
965         char *eol;
966         ssize_t readsize;
968         while (TRUE) {
969                 if (io->bufsize > 0) {
970                         eol = memchr(io->bufpos, c, io->bufsize);
971                         if (eol) {
972                                 char *line = io->bufpos;
974                                 *eol = 0;
975                                 io->bufpos = eol + 1;
976                                 io->bufsize -= io->bufpos - line;
977                                 return line;
978                         }
979                 }
981                 if (io_eof(io)) {
982                         if (io->bufsize) {
983                                 io->bufpos[io->bufsize] = 0;
984                                 io->bufsize = 0;
985                                 return io->bufpos;
986                         }
987                         return NULL;
988                 }
990                 if (!can_read)
991                         return NULL;
993                 if (io->bufsize > 0 && io->bufpos > io->buf)
994                         memmove(io->buf, io->bufpos, io->bufsize);
996                 if (io->bufalloc == io->bufsize) {
997                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
998                                 return NULL;
999                         io->bufalloc += BUFSIZ;
1000                 }
1002                 io->bufpos = io->buf;
1003                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1004                 if (io_error(io))
1005                         return NULL;
1006                 io->bufsize += readsize;
1007         }
1010 static bool
1011 io_write(struct io *io, const void *buf, size_t bufsize)
1013         size_t written = 0;
1015         while (!io_error(io) && written < bufsize) {
1016                 ssize_t size;
1018                 size = write(io->pipe, buf + written, bufsize - written);
1019                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1020                         continue;
1021                 else if (size == -1)
1022                         io->error = errno;
1023                 else
1024                         written += size;
1025         }
1027         return written == bufsize;
1030 static bool
1031 io_read_buf(struct io *io, char buf[], size_t bufsize)
1033         char *result = io_get(io, '\n', TRUE);
1035         if (result) {
1036                 result = chomp_string(result);
1037                 string_ncopy_do(buf, bufsize, result, strlen(result));
1038         }
1040         return io_done(io) && result;
1043 static bool
1044 io_run_buf(const char **argv, char buf[], size_t bufsize)
1046         struct io io = {};
1048         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1049             && io_read_buf(&io, buf, bufsize);
1052 static int
1053 io_load(struct io *io, const char *separators,
1054         int (*read_property)(char *, size_t, char *, size_t))
1056         char *name;
1057         int state = OK;
1059         if (!io_start(io))
1060                 return ERR;
1062         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1063                 char *value;
1064                 size_t namelen;
1065                 size_t valuelen;
1067                 name = chomp_string(name);
1068                 namelen = strcspn(name, separators);
1070                 if (name[namelen]) {
1071                         name[namelen] = 0;
1072                         value = chomp_string(name + namelen + 1);
1073                         valuelen = strlen(value);
1075                 } else {
1076                         value = "";
1077                         valuelen = 0;
1078                 }
1080                 state = read_property(name, namelen, value, valuelen);
1081         }
1083         if (state != ERR && io_error(io))
1084                 state = ERR;
1085         io_done(io);
1087         return state;
1090 static int
1091 io_run_load(const char **argv, const char *separators,
1092             int (*read_property)(char *, size_t, char *, size_t))
1094         struct io io = {};
1096         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1097                 ? io_load(&io, separators, read_property) : ERR;
1101 /*
1102  * User requests
1103  */
1105 #define REQ_INFO \
1106         /* XXX: Keep the view request first and in sync with views[]. */ \
1107         REQ_GROUP("View switching") \
1108         REQ_(VIEW_MAIN,         "Show main view"), \
1109         REQ_(VIEW_DIFF,         "Show diff view"), \
1110         REQ_(VIEW_LOG,          "Show log view"), \
1111         REQ_(VIEW_TREE,         "Show tree view"), \
1112         REQ_(VIEW_BLOB,         "Show blob view"), \
1113         REQ_(VIEW_BLAME,        "Show blame view"), \
1114         REQ_(VIEW_BRANCH,       "Show branch view"), \
1115         REQ_(VIEW_HELP,         "Show help page"), \
1116         REQ_(VIEW_PAGER,        "Show pager view"), \
1117         REQ_(VIEW_STATUS,       "Show status view"), \
1118         REQ_(VIEW_STAGE,        "Show stage view"), \
1119         \
1120         REQ_GROUP("View manipulation") \
1121         REQ_(ENTER,             "Enter current line and scroll"), \
1122         REQ_(NEXT,              "Move to next"), \
1123         REQ_(PREVIOUS,          "Move to previous"), \
1124         REQ_(PARENT,            "Move to parent"), \
1125         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1126         REQ_(REFRESH,           "Reload and refresh"), \
1127         REQ_(MAXIMIZE,          "Maximize the current view"), \
1128         REQ_(VIEW_CLOSE,        "Close the current view"), \
1129         REQ_(QUIT,              "Close all views and quit"), \
1130         \
1131         REQ_GROUP("View specific requests") \
1132         REQ_(STATUS_UPDATE,     "Update file status"), \
1133         REQ_(STATUS_REVERT,     "Revert file changes"), \
1134         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1135         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1136         \
1137         REQ_GROUP("Cursor navigation") \
1138         REQ_(MOVE_UP,           "Move cursor one line up"), \
1139         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1140         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1141         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1142         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1143         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1144         \
1145         REQ_GROUP("Scrolling") \
1146         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1147         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1148         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1149         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1150         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1151         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1152         \
1153         REQ_GROUP("Searching") \
1154         REQ_(SEARCH,            "Search the view"), \
1155         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1156         REQ_(FIND_NEXT,         "Find next search match"), \
1157         REQ_(FIND_PREV,         "Find previous search match"), \
1158         \
1159         REQ_GROUP("Option manipulation") \
1160         REQ_(OPTIONS,           "Open option menu"), \
1161         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1162         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1163         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1164         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1165         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1166         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1167         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1168         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1169         \
1170         REQ_GROUP("Misc") \
1171         REQ_(PROMPT,            "Bring up the prompt"), \
1172         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1173         REQ_(SHOW_VERSION,      "Show version information"), \
1174         REQ_(STOP_LOADING,      "Stop all loading views"), \
1175         REQ_(EDIT,              "Open in editor"), \
1176         REQ_(NONE,              "Do nothing")
1179 /* User action requests. */
1180 enum request {
1181 #define REQ_GROUP(help)
1182 #define REQ_(req, help) REQ_##req
1184         /* Offset all requests to avoid conflicts with ncurses getch values. */
1185         REQ_UNKNOWN = KEY_MAX + 1,
1186         REQ_OFFSET,
1187         REQ_INFO
1189 #undef  REQ_GROUP
1190 #undef  REQ_
1191 };
1193 struct request_info {
1194         enum request request;
1195         const char *name;
1196         int namelen;
1197         const char *help;
1198 };
1200 static const struct request_info req_info[] = {
1201 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1202 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1203         REQ_INFO
1204 #undef  REQ_GROUP
1205 #undef  REQ_
1206 };
1208 static enum request
1209 get_request(const char *name)
1211         int namelen = strlen(name);
1212         int i;
1214         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1215                 if (enum_equals(req_info[i], name, namelen))
1216                         return req_info[i].request;
1218         return REQ_UNKNOWN;
1222 /*
1223  * Options
1224  */
1226 /* Option and state variables. */
1227 static enum date opt_date               = DATE_DEFAULT;
1228 static enum author opt_author           = AUTHOR_DEFAULT;
1229 static bool opt_line_number             = FALSE;
1230 static bool opt_line_graphics           = TRUE;
1231 static bool opt_rev_graph               = FALSE;
1232 static bool opt_show_refs               = TRUE;
1233 static int opt_num_interval             = 5;
1234 static double opt_hscroll               = 0.50;
1235 static double opt_scale_split_view      = 2.0 / 3.0;
1236 static int opt_tab_size                 = 8;
1237 static int opt_author_cols              = AUTHOR_COLS;
1238 static char opt_path[SIZEOF_STR]        = "";
1239 static char opt_file[SIZEOF_STR]        = "";
1240 static char opt_ref[SIZEOF_REF]         = "";
1241 static char opt_head[SIZEOF_REF]        = "";
1242 static char opt_remote[SIZEOF_REF]      = "";
1243 static char opt_encoding[20]            = "UTF-8";
1244 static iconv_t opt_iconv_in             = ICONV_NONE;
1245 static iconv_t opt_iconv_out            = ICONV_NONE;
1246 static char opt_search[SIZEOF_STR]      = "";
1247 static char opt_cdup[SIZEOF_STR]        = "";
1248 static char opt_prefix[SIZEOF_STR]      = "";
1249 static char opt_git_dir[SIZEOF_STR]     = "";
1250 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1251 static char opt_editor[SIZEOF_STR]      = "";
1252 static FILE *opt_tty                    = NULL;
1254 #define is_initial_commit()     (!get_ref_head())
1255 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1258 /*
1259  * Line-oriented content detection.
1260  */
1262 #define LINE_INFO \
1263 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1264 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1265 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1266 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1267 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1268 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1269 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1270 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1272 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1273 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1274 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1275 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1276 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1277 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1278 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1279 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1280 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1281 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1282 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1283 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1284 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1285 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1286 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1287 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1288 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1289 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1290 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1291 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1292 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1293 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1294 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1295 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1296 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1297 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1298 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1299 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1300 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1301 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1302 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1303 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1304 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1305 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1306 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1307 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1308 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1309 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1310 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1311 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1312 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1313 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1314 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1315 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1316 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1317 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1318 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1319 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1320 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1321 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1323 enum line_type {
1324 #define LINE(type, line, fg, bg, attr) \
1325         LINE_##type
1326         LINE_INFO,
1327         LINE_NONE
1328 #undef  LINE
1329 };
1331 struct line_info {
1332         const char *name;       /* Option name. */
1333         int namelen;            /* Size of option name. */
1334         const char *line;       /* The start of line to match. */
1335         int linelen;            /* Size of string to match. */
1336         int fg, bg, attr;       /* Color and text attributes for the lines. */
1337 };
1339 static struct line_info line_info[] = {
1340 #define LINE(type, line, fg, bg, attr) \
1341         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1342         LINE_INFO
1343 #undef  LINE
1344 };
1346 static enum line_type
1347 get_line_type(const char *line)
1349         int linelen = strlen(line);
1350         enum line_type type;
1352         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1353                 /* Case insensitive search matches Signed-off-by lines better. */
1354                 if (linelen >= line_info[type].linelen &&
1355                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1356                         return type;
1358         return LINE_DEFAULT;
1361 static inline int
1362 get_line_attr(enum line_type type)
1364         assert(type < ARRAY_SIZE(line_info));
1365         return COLOR_PAIR(type) | line_info[type].attr;
1368 static struct line_info *
1369 get_line_info(const char *name)
1371         size_t namelen = strlen(name);
1372         enum line_type type;
1374         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1375                 if (enum_equals(line_info[type], name, namelen))
1376                         return &line_info[type];
1378         return NULL;
1381 static void
1382 init_colors(void)
1384         int default_bg = line_info[LINE_DEFAULT].bg;
1385         int default_fg = line_info[LINE_DEFAULT].fg;
1386         enum line_type type;
1388         start_color();
1390         if (assume_default_colors(default_fg, default_bg) == ERR) {
1391                 default_bg = COLOR_BLACK;
1392                 default_fg = COLOR_WHITE;
1393         }
1395         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1396                 struct line_info *info = &line_info[type];
1397                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1398                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1400                 init_pair(type, fg, bg);
1401         }
1404 struct line {
1405         enum line_type type;
1407         /* State flags */
1408         unsigned int selected:1;
1409         unsigned int dirty:1;
1410         unsigned int cleareol:1;
1411         unsigned int other:16;
1413         void *data;             /* User data */
1414 };
1417 /*
1418  * Keys
1419  */
1421 struct keybinding {
1422         int alias;
1423         enum request request;
1424 };
1426 static struct keybinding default_keybindings[] = {
1427         /* View switching */
1428         { 'm',          REQ_VIEW_MAIN },
1429         { 'd',          REQ_VIEW_DIFF },
1430         { 'l',          REQ_VIEW_LOG },
1431         { 't',          REQ_VIEW_TREE },
1432         { 'f',          REQ_VIEW_BLOB },
1433         { 'B',          REQ_VIEW_BLAME },
1434         { 'H',          REQ_VIEW_BRANCH },
1435         { 'p',          REQ_VIEW_PAGER },
1436         { 'h',          REQ_VIEW_HELP },
1437         { 'S',          REQ_VIEW_STATUS },
1438         { 'c',          REQ_VIEW_STAGE },
1440         /* View manipulation */
1441         { 'q',          REQ_VIEW_CLOSE },
1442         { KEY_TAB,      REQ_VIEW_NEXT },
1443         { KEY_RETURN,   REQ_ENTER },
1444         { KEY_UP,       REQ_PREVIOUS },
1445         { KEY_DOWN,     REQ_NEXT },
1446         { 'R',          REQ_REFRESH },
1447         { KEY_F(5),     REQ_REFRESH },
1448         { 'O',          REQ_MAXIMIZE },
1450         /* Cursor navigation */
1451         { 'k',          REQ_MOVE_UP },
1452         { 'j',          REQ_MOVE_DOWN },
1453         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1454         { KEY_END,      REQ_MOVE_LAST_LINE },
1455         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1456         { ' ',          REQ_MOVE_PAGE_DOWN },
1457         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1458         { 'b',          REQ_MOVE_PAGE_UP },
1459         { '-',          REQ_MOVE_PAGE_UP },
1461         /* Scrolling */
1462         { KEY_LEFT,     REQ_SCROLL_LEFT },
1463         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1464         { KEY_IC,       REQ_SCROLL_LINE_UP },
1465         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1466         { 'w',          REQ_SCROLL_PAGE_UP },
1467         { 's',          REQ_SCROLL_PAGE_DOWN },
1469         /* Searching */
1470         { '/',          REQ_SEARCH },
1471         { '?',          REQ_SEARCH_BACK },
1472         { 'n',          REQ_FIND_NEXT },
1473         { 'N',          REQ_FIND_PREV },
1475         /* Misc */
1476         { 'Q',          REQ_QUIT },
1477         { 'z',          REQ_STOP_LOADING },
1478         { 'v',          REQ_SHOW_VERSION },
1479         { 'r',          REQ_SCREEN_REDRAW },
1480         { 'o',          REQ_OPTIONS },
1481         { '.',          REQ_TOGGLE_LINENO },
1482         { 'D',          REQ_TOGGLE_DATE },
1483         { 'A',          REQ_TOGGLE_AUTHOR },
1484         { 'g',          REQ_TOGGLE_REV_GRAPH },
1485         { 'F',          REQ_TOGGLE_REFS },
1486         { 'I',          REQ_TOGGLE_SORT_ORDER },
1487         { 'i',          REQ_TOGGLE_SORT_FIELD },
1488         { ':',          REQ_PROMPT },
1489         { 'u',          REQ_STATUS_UPDATE },
1490         { '!',          REQ_STATUS_REVERT },
1491         { 'M',          REQ_STATUS_MERGE },
1492         { '@',          REQ_STAGE_NEXT },
1493         { ',',          REQ_PARENT },
1494         { 'e',          REQ_EDIT },
1495 };
1497 #define KEYMAP_INFO \
1498         KEYMAP_(GENERIC), \
1499         KEYMAP_(MAIN), \
1500         KEYMAP_(DIFF), \
1501         KEYMAP_(LOG), \
1502         KEYMAP_(TREE), \
1503         KEYMAP_(BLOB), \
1504         KEYMAP_(BLAME), \
1505         KEYMAP_(BRANCH), \
1506         KEYMAP_(PAGER), \
1507         KEYMAP_(HELP), \
1508         KEYMAP_(STATUS), \
1509         KEYMAP_(STAGE)
1511 enum keymap {
1512 #define KEYMAP_(name) KEYMAP_##name
1513         KEYMAP_INFO
1514 #undef  KEYMAP_
1515 };
1517 static const struct enum_map keymap_table[] = {
1518 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1519         KEYMAP_INFO
1520 #undef  KEYMAP_
1521 };
1523 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1525 struct keybinding_table {
1526         struct keybinding *data;
1527         size_t size;
1528 };
1530 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1532 static void
1533 add_keybinding(enum keymap keymap, enum request request, int key)
1535         struct keybinding_table *table = &keybindings[keymap];
1536         size_t i;
1538         for (i = 0; i < keybindings[keymap].size; i++) {
1539                 if (keybindings[keymap].data[i].alias == key) {
1540                         keybindings[keymap].data[i].request = request;
1541                         return;
1542                 }
1543         }
1545         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1546         if (!table->data)
1547                 die("Failed to allocate keybinding");
1548         table->data[table->size].alias = key;
1549         table->data[table->size++].request = request;
1551         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1552                 int i;
1554                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1555                         if (default_keybindings[i].alias == key)
1556                                 default_keybindings[i].request = REQ_NONE;
1557         }
1560 /* Looks for a key binding first in the given map, then in the generic map, and
1561  * lastly in the default keybindings. */
1562 static enum request
1563 get_keybinding(enum keymap keymap, int key)
1565         size_t i;
1567         for (i = 0; i < keybindings[keymap].size; i++)
1568                 if (keybindings[keymap].data[i].alias == key)
1569                         return keybindings[keymap].data[i].request;
1571         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1572                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1573                         return keybindings[KEYMAP_GENERIC].data[i].request;
1575         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1576                 if (default_keybindings[i].alias == key)
1577                         return default_keybindings[i].request;
1579         return (enum request) key;
1583 struct key {
1584         const char *name;
1585         int value;
1586 };
1588 static const struct key key_table[] = {
1589         { "Enter",      KEY_RETURN },
1590         { "Space",      ' ' },
1591         { "Backspace",  KEY_BACKSPACE },
1592         { "Tab",        KEY_TAB },
1593         { "Escape",     KEY_ESC },
1594         { "Left",       KEY_LEFT },
1595         { "Right",      KEY_RIGHT },
1596         { "Up",         KEY_UP },
1597         { "Down",       KEY_DOWN },
1598         { "Insert",     KEY_IC },
1599         { "Delete",     KEY_DC },
1600         { "Hash",       '#' },
1601         { "Home",       KEY_HOME },
1602         { "End",        KEY_END },
1603         { "PageUp",     KEY_PPAGE },
1604         { "PageDown",   KEY_NPAGE },
1605         { "F1",         KEY_F(1) },
1606         { "F2",         KEY_F(2) },
1607         { "F3",         KEY_F(3) },
1608         { "F4",         KEY_F(4) },
1609         { "F5",         KEY_F(5) },
1610         { "F6",         KEY_F(6) },
1611         { "F7",         KEY_F(7) },
1612         { "F8",         KEY_F(8) },
1613         { "F9",         KEY_F(9) },
1614         { "F10",        KEY_F(10) },
1615         { "F11",        KEY_F(11) },
1616         { "F12",        KEY_F(12) },
1617 };
1619 static int
1620 get_key_value(const char *name)
1622         int i;
1624         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1625                 if (!strcasecmp(key_table[i].name, name))
1626                         return key_table[i].value;
1628         if (strlen(name) == 1 && isprint(*name))
1629                 return (int) *name;
1631         return ERR;
1634 static const char *
1635 get_key_name(int key_value)
1637         static char key_char[] = "'X'";
1638         const char *seq = NULL;
1639         int key;
1641         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1642                 if (key_table[key].value == key_value)
1643                         seq = key_table[key].name;
1645         if (seq == NULL &&
1646             key_value < 127 &&
1647             isprint(key_value)) {
1648                 key_char[1] = (char) key_value;
1649                 seq = key_char;
1650         }
1652         return seq ? seq : "(no key)";
1655 static bool
1656 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1658         const char *sep = *pos > 0 ? ", " : "";
1659         const char *keyname = get_key_name(keybinding->alias);
1661         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1664 static bool
1665 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1666                            enum keymap keymap, bool all)
1668         int i;
1670         for (i = 0; i < keybindings[keymap].size; i++) {
1671                 if (keybindings[keymap].data[i].request == request) {
1672                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1673                                 return FALSE;
1674                         if (!all)
1675                                 break;
1676                 }
1677         }
1679         return TRUE;
1682 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1684 static const char *
1685 get_keys(enum keymap keymap, enum request request, bool all)
1687         static char buf[BUFSIZ];
1688         size_t pos = 0;
1689         int i;
1691         buf[pos] = 0;
1693         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1694                 return "Too many keybindings!";
1695         if (pos > 0 && !all)
1696                 return buf;
1698         if (keymap != KEYMAP_GENERIC) {
1699                 /* Only the generic keymap includes the default keybindings when
1700                  * listing all keys. */
1701                 if (all)
1702                         return buf;
1704                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1705                         return "Too many keybindings!";
1706                 if (pos)
1707                         return buf;
1708         }
1710         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1711                 if (default_keybindings[i].request == request) {
1712                         if (!append_key(buf, &pos, &default_keybindings[i]))
1713                                 return "Too many keybindings!";
1714                         if (!all)
1715                                 return buf;
1716                 }
1717         }
1719         return buf;
1722 struct run_request {
1723         enum keymap keymap;
1724         int key;
1725         const char *argv[SIZEOF_ARG];
1726 };
1728 static struct run_request *run_request;
1729 static size_t run_requests;
1731 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1733 static enum request
1734 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1736         struct run_request *req;
1738         if (argc >= ARRAY_SIZE(req->argv) - 1)
1739                 return REQ_NONE;
1741         if (!realloc_run_requests(&run_request, run_requests, 1))
1742                 return REQ_NONE;
1744         req = &run_request[run_requests];
1745         req->keymap = keymap;
1746         req->key = key;
1747         req->argv[0] = NULL;
1749         if (!format_argv(req->argv, argv, FORMAT_NONE))
1750                 return REQ_NONE;
1752         return REQ_NONE + ++run_requests;
1755 static struct run_request *
1756 get_run_request(enum request request)
1758         if (request <= REQ_NONE)
1759                 return NULL;
1760         return &run_request[request - REQ_NONE - 1];
1763 static void
1764 add_builtin_run_requests(void)
1766         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1767         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1768         const char *commit[] = { "git", "commit", NULL };
1769         const char *gc[] = { "git", "gc", NULL };
1770         struct {
1771                 enum keymap keymap;
1772                 int key;
1773                 int argc;
1774                 const char **argv;
1775         } reqs[] = {
1776                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1777                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1778                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1779                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1780         };
1781         int i;
1783         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1784                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1786                 if (req != reqs[i].key)
1787                         continue;
1788                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1789                 if (req != REQ_NONE)
1790                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1791         }
1794 /*
1795  * User config file handling.
1796  */
1798 static int   config_lineno;
1799 static bool  config_errors;
1800 static const char *config_msg;
1802 static const struct enum_map color_map[] = {
1803 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1804         COLOR_MAP(DEFAULT),
1805         COLOR_MAP(BLACK),
1806         COLOR_MAP(BLUE),
1807         COLOR_MAP(CYAN),
1808         COLOR_MAP(GREEN),
1809         COLOR_MAP(MAGENTA),
1810         COLOR_MAP(RED),
1811         COLOR_MAP(WHITE),
1812         COLOR_MAP(YELLOW),
1813 };
1815 static const struct enum_map attr_map[] = {
1816 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1817         ATTR_MAP(NORMAL),
1818         ATTR_MAP(BLINK),
1819         ATTR_MAP(BOLD),
1820         ATTR_MAP(DIM),
1821         ATTR_MAP(REVERSE),
1822         ATTR_MAP(STANDOUT),
1823         ATTR_MAP(UNDERLINE),
1824 };
1826 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1828 static int parse_step(double *opt, const char *arg)
1830         *opt = atoi(arg);
1831         if (!strchr(arg, '%'))
1832                 return OK;
1834         /* "Shift down" so 100% and 1 does not conflict. */
1835         *opt = (*opt - 1) / 100;
1836         if (*opt >= 1.0) {
1837                 *opt = 0.99;
1838                 config_msg = "Step value larger than 100%";
1839                 return ERR;
1840         }
1841         if (*opt < 0.0) {
1842                 *opt = 1;
1843                 config_msg = "Invalid step value";
1844                 return ERR;
1845         }
1846         return OK;
1849 static int
1850 parse_int(int *opt, const char *arg, int min, int max)
1852         int value = atoi(arg);
1854         if (min <= value && value <= max) {
1855                 *opt = value;
1856                 return OK;
1857         }
1859         config_msg = "Integer value out of bound";
1860         return ERR;
1863 static bool
1864 set_color(int *color, const char *name)
1866         if (map_enum(color, color_map, name))
1867                 return TRUE;
1868         if (!prefixcmp(name, "color"))
1869                 return parse_int(color, name + 5, 0, 255) == OK;
1870         return FALSE;
1873 /* Wants: object fgcolor bgcolor [attribute] */
1874 static int
1875 option_color_command(int argc, const char *argv[])
1877         struct line_info *info;
1879         if (argc < 3) {
1880                 config_msg = "Wrong number of arguments given to color command";
1881                 return ERR;
1882         }
1884         info = get_line_info(argv[0]);
1885         if (!info) {
1886                 static const struct enum_map obsolete[] = {
1887                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1888                         ENUM_MAP("main-date",   LINE_DATE),
1889                         ENUM_MAP("main-author", LINE_AUTHOR),
1890                 };
1891                 int index;
1893                 if (!map_enum(&index, obsolete, argv[0])) {
1894                         config_msg = "Unknown color name";
1895                         return ERR;
1896                 }
1897                 info = &line_info[index];
1898         }
1900         if (!set_color(&info->fg, argv[1]) ||
1901             !set_color(&info->bg, argv[2])) {
1902                 config_msg = "Unknown color";
1903                 return ERR;
1904         }
1906         info->attr = 0;
1907         while (argc-- > 3) {
1908                 int attr;
1910                 if (!set_attribute(&attr, argv[argc])) {
1911                         config_msg = "Unknown attribute";
1912                         return ERR;
1913                 }
1914                 info->attr |= attr;
1915         }
1917         return OK;
1920 static int parse_bool(bool *opt, const char *arg)
1922         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1923                 ? TRUE : FALSE;
1924         return OK;
1927 static int parse_enum_do(unsigned int *opt, const char *arg,
1928                          const struct enum_map *map, size_t map_size)
1930         bool is_true;
1932         assert(map_size > 1);
1934         if (map_enum_do(map, map_size, (int *) opt, arg))
1935                 return OK;
1937         if (parse_bool(&is_true, arg) != OK)
1938                 return ERR;
1940         *opt = is_true ? map[1].value : map[0].value;
1941         return OK;
1944 #define parse_enum(opt, arg, map) \
1945         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1947 static int
1948 parse_string(char *opt, const char *arg, size_t optsize)
1950         int arglen = strlen(arg);
1952         switch (arg[0]) {
1953         case '\"':
1954         case '\'':
1955                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1956                         config_msg = "Unmatched quotation";
1957                         return ERR;
1958                 }
1959                 arg += 1; arglen -= 2;
1960         default:
1961                 string_ncopy_do(opt, optsize, arg, arglen);
1962                 return OK;
1963         }
1966 /* Wants: name = value */
1967 static int
1968 option_set_command(int argc, const char *argv[])
1970         if (argc != 3) {
1971                 config_msg = "Wrong number of arguments given to set command";
1972                 return ERR;
1973         }
1975         if (strcmp(argv[1], "=")) {
1976                 config_msg = "No value assigned";
1977                 return ERR;
1978         }
1980         if (!strcmp(argv[0], "show-author"))
1981                 return parse_enum(&opt_author, argv[2], author_map);
1983         if (!strcmp(argv[0], "show-date"))
1984                 return parse_enum(&opt_date, argv[2], date_map);
1986         if (!strcmp(argv[0], "show-rev-graph"))
1987                 return parse_bool(&opt_rev_graph, argv[2]);
1989         if (!strcmp(argv[0], "show-refs"))
1990                 return parse_bool(&opt_show_refs, argv[2]);
1992         if (!strcmp(argv[0], "show-line-numbers"))
1993                 return parse_bool(&opt_line_number, argv[2]);
1995         if (!strcmp(argv[0], "line-graphics"))
1996                 return parse_bool(&opt_line_graphics, argv[2]);
1998         if (!strcmp(argv[0], "line-number-interval"))
1999                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2001         if (!strcmp(argv[0], "author-width"))
2002                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2004         if (!strcmp(argv[0], "horizontal-scroll"))
2005                 return parse_step(&opt_hscroll, argv[2]);
2007         if (!strcmp(argv[0], "split-view-height"))
2008                 return parse_step(&opt_scale_split_view, argv[2]);
2010         if (!strcmp(argv[0], "tab-size"))
2011                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2013         if (!strcmp(argv[0], "commit-encoding"))
2014                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2016         config_msg = "Unknown variable name";
2017         return ERR;
2020 /* Wants: mode request key */
2021 static int
2022 option_bind_command(int argc, const char *argv[])
2024         enum request request;
2025         int keymap = -1;
2026         int key;
2028         if (argc < 3) {
2029                 config_msg = "Wrong number of arguments given to bind command";
2030                 return ERR;
2031         }
2033         if (!set_keymap(&keymap, argv[0])) {
2034                 config_msg = "Unknown key map";
2035                 return ERR;
2036         }
2038         key = get_key_value(argv[1]);
2039         if (key == ERR) {
2040                 config_msg = "Unknown key";
2041                 return ERR;
2042         }
2044         request = get_request(argv[2]);
2045         if (request == REQ_UNKNOWN) {
2046                 static const struct enum_map obsolete[] = {
2047                         ENUM_MAP("cherry-pick",         REQ_NONE),
2048                         ENUM_MAP("screen-resize",       REQ_NONE),
2049                         ENUM_MAP("tree-parent",         REQ_PARENT),
2050                 };
2051                 int alias;
2053                 if (map_enum(&alias, obsolete, argv[2])) {
2054                         if (alias != REQ_NONE)
2055                                 add_keybinding(keymap, alias, key);
2056                         config_msg = "Obsolete request name";
2057                         return ERR;
2058                 }
2059         }
2060         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2061                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2062         if (request == REQ_UNKNOWN) {
2063                 config_msg = "Unknown request name";
2064                 return ERR;
2065         }
2067         add_keybinding(keymap, request, key);
2069         return OK;
2072 static int
2073 set_option(const char *opt, char *value)
2075         const char *argv[SIZEOF_ARG];
2076         int argc = 0;
2078         if (!argv_from_string(argv, &argc, value)) {
2079                 config_msg = "Too many option arguments";
2080                 return ERR;
2081         }
2083         if (!strcmp(opt, "color"))
2084                 return option_color_command(argc, argv);
2086         if (!strcmp(opt, "set"))
2087                 return option_set_command(argc, argv);
2089         if (!strcmp(opt, "bind"))
2090                 return option_bind_command(argc, argv);
2092         config_msg = "Unknown option command";
2093         return ERR;
2096 static int
2097 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2099         int status = OK;
2101         config_lineno++;
2102         config_msg = "Internal error";
2104         /* Check for comment markers, since read_properties() will
2105          * only ensure opt and value are split at first " \t". */
2106         optlen = strcspn(opt, "#");
2107         if (optlen == 0)
2108                 return OK;
2110         if (opt[optlen] != 0) {
2111                 config_msg = "No option value";
2112                 status = ERR;
2114         }  else {
2115                 /* Look for comment endings in the value. */
2116                 size_t len = strcspn(value, "#");
2118                 if (len < valuelen) {
2119                         valuelen = len;
2120                         value[valuelen] = 0;
2121                 }
2123                 status = set_option(opt, value);
2124         }
2126         if (status == ERR) {
2127                 warn("Error on line %d, near '%.*s': %s",
2128                      config_lineno, (int) optlen, opt, config_msg);
2129                 config_errors = TRUE;
2130         }
2132         /* Always keep going if errors are encountered. */
2133         return OK;
2136 static void
2137 load_option_file(const char *path)
2139         struct io io = {};
2141         /* It's OK that the file doesn't exist. */
2142         if (!io_open(&io, "%s", path))
2143                 return;
2145         config_lineno = 0;
2146         config_errors = FALSE;
2148         if (io_load(&io, " \t", read_option) == ERR ||
2149             config_errors == TRUE)
2150                 warn("Errors while loading %s.", path);
2153 static int
2154 load_options(void)
2156         const char *home = getenv("HOME");
2157         const char *tigrc_user = getenv("TIGRC_USER");
2158         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2159         char buf[SIZEOF_STR];
2161         if (!tigrc_system)
2162                 tigrc_system = SYSCONFDIR "/tigrc";
2163         load_option_file(tigrc_system);
2165         if (!tigrc_user) {
2166                 if (!home || !string_format(buf, "%s/.tigrc", home))
2167                         return ERR;
2168                 tigrc_user = buf;
2169         }
2170         load_option_file(tigrc_user);
2172         /* Add _after_ loading config files to avoid adding run requests
2173          * that conflict with keybindings. */
2174         add_builtin_run_requests();
2176         return OK;
2180 /*
2181  * The viewer
2182  */
2184 struct view;
2185 struct view_ops;
2187 /* The display array of active views and the index of the current view. */
2188 static struct view *display[2];
2189 static unsigned int current_view;
2191 #define foreach_displayed_view(view, i) \
2192         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2194 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2196 /* Current head and commit ID */
2197 static char ref_blob[SIZEOF_REF]        = "";
2198 static char ref_commit[SIZEOF_REF]      = "HEAD";
2199 static char ref_head[SIZEOF_REF]        = "HEAD";
2200 static char ref_branch[SIZEOF_REF]      = "";
2202 enum view_type {
2203         VIEW_MAIN,
2204         VIEW_DIFF,
2205         VIEW_LOG,
2206         VIEW_TREE,
2207         VIEW_BLOB,
2208         VIEW_BLAME,
2209         VIEW_BRANCH,
2210         VIEW_HELP,
2211         VIEW_PAGER,
2212         VIEW_STATUS,
2213         VIEW_STAGE,
2214 };
2216 struct view {
2217         enum view_type type;    /* View type */
2218         const char *name;       /* View name */
2219         const char *cmd_env;    /* Command line set via environment */
2220         const char *id;         /* Points to either of ref_{head,commit,blob} */
2222         struct view_ops *ops;   /* View operations */
2224         enum keymap keymap;     /* What keymap does this view have */
2225         bool git_dir;           /* Whether the view requires a git directory. */
2227         char ref[SIZEOF_REF];   /* Hovered commit reference */
2228         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2230         int height, width;      /* The width and height of the main window */
2231         WINDOW *win;            /* The main window */
2232         WINDOW *title;          /* The title window living below the main window */
2234         /* Navigation */
2235         unsigned long offset;   /* Offset of the window top */
2236         unsigned long yoffset;  /* Offset from the window side. */
2237         unsigned long lineno;   /* Current line number */
2238         unsigned long p_offset; /* Previous offset of the window top */
2239         unsigned long p_yoffset;/* Previous offset from the window side */
2240         unsigned long p_lineno; /* Previous current line number */
2241         bool p_restore;         /* Should the previous position be restored. */
2243         /* Searching */
2244         char grep[SIZEOF_STR];  /* Search string */
2245         regex_t *regex;         /* Pre-compiled regexp */
2247         /* If non-NULL, points to the view that opened this view. If this view
2248          * is closed tig will switch back to the parent view. */
2249         struct view *parent;
2250         struct view *prev;
2252         /* Buffering */
2253         size_t lines;           /* Total number of lines */
2254         struct line *line;      /* Line index */
2255         unsigned int digits;    /* Number of digits in the lines member. */
2257         /* Drawing */
2258         struct line *curline;   /* Line currently being drawn. */
2259         enum line_type curtype; /* Attribute currently used for drawing. */
2260         unsigned long col;      /* Column when drawing. */
2261         bool has_scrolled;      /* View was scrolled. */
2263         /* Loading */
2264         struct io io;
2265         struct io *pipe;
2266         time_t start_time;
2267         time_t update_secs;
2268 };
2270 struct view_ops {
2271         /* What type of content being displayed. Used in the title bar. */
2272         const char *type;
2273         /* Default command arguments. */
2274         const char **argv;
2275         /* Open and reads in all view content. */
2276         bool (*open)(struct view *view);
2277         /* Read one line; updates view->line. */
2278         bool (*read)(struct view *view, char *data);
2279         /* Draw one line; @lineno must be < view->height. */
2280         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2281         /* Depending on view handle a special requests. */
2282         enum request (*request)(struct view *view, enum request request, struct line *line);
2283         /* Search for regexp in a line. */
2284         bool (*grep)(struct view *view, struct line *line);
2285         /* Select line */
2286         void (*select)(struct view *view, struct line *line);
2287         /* Prepare view for loading */
2288         bool (*prepare)(struct view *view);
2289 };
2291 static struct view_ops blame_ops;
2292 static struct view_ops blob_ops;
2293 static struct view_ops diff_ops;
2294 static struct view_ops help_ops;
2295 static struct view_ops log_ops;
2296 static struct view_ops main_ops;
2297 static struct view_ops pager_ops;
2298 static struct view_ops stage_ops;
2299 static struct view_ops status_ops;
2300 static struct view_ops tree_ops;
2301 static struct view_ops branch_ops;
2303 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2304         { type, name, #env, ref, ops, map, git }
2306 #define VIEW_(id, name, ops, git, ref) \
2307         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2309 static struct view views[] = {
2310         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2311         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2312         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2313         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2314         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2315         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2316         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2317         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2318         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2319         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2320         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2321 };
2323 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2325 #define foreach_view(view, i) \
2326         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2328 #define view_is_displayed(view) \
2329         (view == display[0] || view == display[1])
2331 static enum request
2332 view_request(struct view *view, enum request request)
2334         if (!view || !view->lines)
2335                 return request;
2336         return view->ops->request(view, request, &view->line[view->lineno]);
2340 /*
2341  * View drawing.
2342  */
2344 static inline void
2345 set_view_attr(struct view *view, enum line_type type)
2347         if (!view->curline->selected && view->curtype != type) {
2348                 (void) wattrset(view->win, get_line_attr(type));
2349                 wchgat(view->win, -1, 0, type, NULL);
2350                 view->curtype = type;
2351         }
2354 static int
2355 draw_chars(struct view *view, enum line_type type, const char *string,
2356            int max_len, bool use_tilde)
2358         static char out_buffer[BUFSIZ * 2];
2359         int len = 0;
2360         int col = 0;
2361         int trimmed = FALSE;
2362         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2364         if (max_len <= 0)
2365                 return 0;
2367         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2369         set_view_attr(view, type);
2370         if (len > 0) {
2371                 if (opt_iconv_out != ICONV_NONE) {
2372                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2373                         size_t inlen = len + 1;
2375                         char *outbuf = out_buffer;
2376                         size_t outlen = sizeof(out_buffer);
2378                         size_t ret;
2380                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2381                         if (ret != (size_t) -1) {
2382                                 string = out_buffer;
2383                                 len = sizeof(out_buffer) - outlen;
2384                         }
2385                 }
2387                 waddnstr(view->win, string, len);
2388         }
2389         if (trimmed && use_tilde) {
2390                 set_view_attr(view, LINE_DELIMITER);
2391                 waddch(view->win, '~');
2392                 col++;
2393         }
2395         return col;
2398 static int
2399 draw_space(struct view *view, enum line_type type, int max, int spaces)
2401         static char space[] = "                    ";
2402         int col = 0;
2404         spaces = MIN(max, spaces);
2406         while (spaces > 0) {
2407                 int len = MIN(spaces, sizeof(space) - 1);
2409                 col += draw_chars(view, type, space, len, FALSE);
2410                 spaces -= len;
2411         }
2413         return col;
2416 static bool
2417 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2419         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2420         return view->width + view->yoffset <= view->col;
2423 static bool
2424 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2426         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2427         int max = view->width + view->yoffset - view->col;
2428         int i;
2430         if (max < size)
2431                 size = max;
2433         set_view_attr(view, type);
2434         /* Using waddch() instead of waddnstr() ensures that
2435          * they'll be rendered correctly for the cursor line. */
2436         for (i = skip; i < size; i++)
2437                 waddch(view->win, graphic[i]);
2439         view->col += size;
2440         if (size < max && skip <= size)
2441                 waddch(view->win, ' ');
2442         view->col++;
2444         return view->width + view->yoffset <= view->col;
2447 static bool
2448 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2450         int max = MIN(view->width + view->yoffset - view->col, len);
2451         int col;
2453         if (text)
2454                 col = draw_chars(view, type, text, max - 1, trim);
2455         else
2456                 col = draw_space(view, type, max - 1, max - 1);
2458         view->col += col;
2459         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2460         return view->width + view->yoffset <= view->col;
2463 static bool
2464 draw_date(struct view *view, struct time *time)
2466         const char *date = mkdate(time, opt_date);
2467         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2469         return draw_field(view, LINE_DATE, date, cols, FALSE);
2472 static bool
2473 draw_author(struct view *view, const char *author)
2475         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2476         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2478         if (abbreviate && author)
2479                 author = get_author_initials(author);
2481         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2484 static bool
2485 draw_mode(struct view *view, mode_t mode)
2487         const char *str;
2489         if (S_ISDIR(mode))
2490                 str = "drwxr-xr-x";
2491         else if (S_ISLNK(mode))
2492                 str = "lrwxrwxrwx";
2493         else if (S_ISGITLINK(mode))
2494                 str = "m---------";
2495         else if (S_ISREG(mode) && mode & S_IXUSR)
2496                 str = "-rwxr-xr-x";
2497         else if (S_ISREG(mode))
2498                 str = "-rw-r--r--";
2499         else
2500                 str = "----------";
2502         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2505 static bool
2506 draw_lineno(struct view *view, unsigned int lineno)
2508         char number[10];
2509         int digits3 = view->digits < 3 ? 3 : view->digits;
2510         int max = MIN(view->width + view->yoffset - view->col, digits3);
2511         char *text = NULL;
2512         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2514         lineno += view->offset + 1;
2515         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2516                 static char fmt[] = "%1ld";
2518                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2519                 if (string_format(number, fmt, lineno))
2520                         text = number;
2521         }
2522         if (text)
2523                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2524         else
2525                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2526         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2529 static bool
2530 draw_view_line(struct view *view, unsigned int lineno)
2532         struct line *line;
2533         bool selected = (view->offset + lineno == view->lineno);
2535         assert(view_is_displayed(view));
2537         if (view->offset + lineno >= view->lines)
2538                 return FALSE;
2540         line = &view->line[view->offset + lineno];
2542         wmove(view->win, lineno, 0);
2543         if (line->cleareol)
2544                 wclrtoeol(view->win);
2545         view->col = 0;
2546         view->curline = line;
2547         view->curtype = LINE_NONE;
2548         line->selected = FALSE;
2549         line->dirty = line->cleareol = 0;
2551         if (selected) {
2552                 set_view_attr(view, LINE_CURSOR);
2553                 line->selected = TRUE;
2554                 view->ops->select(view, line);
2555         }
2557         return view->ops->draw(view, line, lineno);
2560 static void
2561 redraw_view_dirty(struct view *view)
2563         bool dirty = FALSE;
2564         int lineno;
2566         for (lineno = 0; lineno < view->height; lineno++) {
2567                 if (view->offset + lineno >= view->lines)
2568                         break;
2569                 if (!view->line[view->offset + lineno].dirty)
2570                         continue;
2571                 dirty = TRUE;
2572                 if (!draw_view_line(view, lineno))
2573                         break;
2574         }
2576         if (!dirty)
2577                 return;
2578         wnoutrefresh(view->win);
2581 static void
2582 redraw_view_from(struct view *view, int lineno)
2584         assert(0 <= lineno && lineno < view->height);
2586         for (; lineno < view->height; lineno++) {
2587                 if (!draw_view_line(view, lineno))
2588                         break;
2589         }
2591         wnoutrefresh(view->win);
2594 static void
2595 redraw_view(struct view *view)
2597         werase(view->win);
2598         redraw_view_from(view, 0);
2602 static void
2603 update_view_title(struct view *view)
2605         char buf[SIZEOF_STR];
2606         char state[SIZEOF_STR];
2607         size_t bufpos = 0, statelen = 0;
2609         assert(view_is_displayed(view));
2611         if (view->type != VIEW_STATUS && view->lines) {
2612                 unsigned int view_lines = view->offset + view->height;
2613                 unsigned int lines = view->lines
2614                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2615                                    : 0;
2617                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2618                                    view->ops->type,
2619                                    view->lineno + 1,
2620                                    view->lines,
2621                                    lines);
2623         }
2625         if (view->pipe) {
2626                 time_t secs = time(NULL) - view->start_time;
2628                 /* Three git seconds are a long time ... */
2629                 if (secs > 2)
2630                         string_format_from(state, &statelen, " loading %lds", secs);
2631         }
2633         string_format_from(buf, &bufpos, "[%s]", view->name);
2634         if (*view->ref && bufpos < view->width) {
2635                 size_t refsize = strlen(view->ref);
2636                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2638                 if (minsize < view->width)
2639                         refsize = view->width - minsize + 7;
2640                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2641         }
2643         if (statelen && bufpos < view->width) {
2644                 string_format_from(buf, &bufpos, "%s", state);
2645         }
2647         if (view == display[current_view])
2648                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2649         else
2650                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2652         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2653         wclrtoeol(view->title);
2654         wnoutrefresh(view->title);
2657 static int
2658 apply_step(double step, int value)
2660         if (step >= 1)
2661                 return (int) step;
2662         value *= step + 0.01;
2663         return value ? value : 1;
2666 static void
2667 resize_display(void)
2669         int offset, i;
2670         struct view *base = display[0];
2671         struct view *view = display[1] ? display[1] : display[0];
2673         /* Setup window dimensions */
2675         getmaxyx(stdscr, base->height, base->width);
2677         /* Make room for the status window. */
2678         base->height -= 1;
2680         if (view != base) {
2681                 /* Horizontal split. */
2682                 view->width   = base->width;
2683                 view->height  = apply_step(opt_scale_split_view, base->height);
2684                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2685                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2686                 base->height -= view->height;
2688                 /* Make room for the title bar. */
2689                 view->height -= 1;
2690         }
2692         /* Make room for the title bar. */
2693         base->height -= 1;
2695         offset = 0;
2697         foreach_displayed_view (view, i) {
2698                 if (!view->win) {
2699                         view->win = newwin(view->height, 0, offset, 0);
2700                         if (!view->win)
2701                                 die("Failed to create %s view", view->name);
2703                         scrollok(view->win, FALSE);
2705                         view->title = newwin(1, 0, offset + view->height, 0);
2706                         if (!view->title)
2707                                 die("Failed to create title window");
2709                 } else {
2710                         wresize(view->win, view->height, view->width);
2711                         mvwin(view->win,   offset, 0);
2712                         mvwin(view->title, offset + view->height, 0);
2713                 }
2715                 offset += view->height + 1;
2716         }
2719 static void
2720 redraw_display(bool clear)
2722         struct view *view;
2723         int i;
2725         foreach_displayed_view (view, i) {
2726                 if (clear)
2727                         wclear(view->win);
2728                 redraw_view(view);
2729                 update_view_title(view);
2730         }
2734 /*
2735  * Option management
2736  */
2738 static void
2739 toggle_enum_option_do(unsigned int *opt, const char *help,
2740                       const struct enum_map *map, size_t size)
2742         *opt = (*opt + 1) % size;
2743         redraw_display(FALSE);
2744         report("Displaying %s %s", enum_name(map[*opt]), help);
2747 #define toggle_enum_option(opt, help, map) \
2748         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2750 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2751 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2753 static void
2754 toggle_view_option(bool *option, const char *help)
2756         *option = !*option;
2757         redraw_display(FALSE);
2758         report("%sabling %s", *option ? "En" : "Dis", help);
2761 static void
2762 open_option_menu(void)
2764         const struct menu_item menu[] = {
2765                 { '.', "line numbers", &opt_line_number },
2766                 { 'D', "date display", &opt_date },
2767                 { 'A', "author display", &opt_author },
2768                 { 'g', "revision graph display", &opt_rev_graph },
2769                 { 'F', "reference display", &opt_show_refs },
2770                 { 0 }
2771         };
2772         int selected = 0;
2774         if (prompt_menu("Toggle option", menu, &selected)) {
2775                 if (menu[selected].data == &opt_date)
2776                         toggle_date();
2777                 else if (menu[selected].data == &opt_author)
2778                         toggle_author();
2779                 else
2780                         toggle_view_option(menu[selected].data, menu[selected].text);
2781         }
2784 static void
2785 maximize_view(struct view *view)
2787         memset(display, 0, sizeof(display));
2788         current_view = 0;
2789         display[current_view] = view;
2790         resize_display();
2791         redraw_display(FALSE);
2792         report("");
2796 /*
2797  * Navigation
2798  */
2800 static bool
2801 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2803         if (lineno >= view->lines)
2804                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2806         if (offset > lineno || offset + view->height <= lineno) {
2807                 unsigned long half = view->height / 2;
2809                 if (lineno > half)
2810                         offset = lineno - half;
2811                 else
2812                         offset = 0;
2813         }
2815         if (offset != view->offset || lineno != view->lineno) {
2816                 view->offset = offset;
2817                 view->lineno = lineno;
2818                 return TRUE;
2819         }
2821         return FALSE;
2824 /* Scrolling backend */
2825 static void
2826 do_scroll_view(struct view *view, int lines)
2828         bool redraw_current_line = FALSE;
2830         /* The rendering expects the new offset. */
2831         view->offset += lines;
2833         assert(0 <= view->offset && view->offset < view->lines);
2834         assert(lines);
2836         /* Move current line into the view. */
2837         if (view->lineno < view->offset) {
2838                 view->lineno = view->offset;
2839                 redraw_current_line = TRUE;
2840         } else if (view->lineno >= view->offset + view->height) {
2841                 view->lineno = view->offset + view->height - 1;
2842                 redraw_current_line = TRUE;
2843         }
2845         assert(view->offset <= view->lineno && view->lineno < view->lines);
2847         /* Redraw the whole screen if scrolling is pointless. */
2848         if (view->height < ABS(lines)) {
2849                 redraw_view(view);
2851         } else {
2852                 int line = lines > 0 ? view->height - lines : 0;
2853                 int end = line + ABS(lines);
2855                 scrollok(view->win, TRUE);
2856                 wscrl(view->win, lines);
2857                 scrollok(view->win, FALSE);
2859                 while (line < end && draw_view_line(view, line))
2860                         line++;
2862                 if (redraw_current_line)
2863                         draw_view_line(view, view->lineno - view->offset);
2864                 wnoutrefresh(view->win);
2865         }
2867         view->has_scrolled = TRUE;
2868         report("");
2871 /* Scroll frontend */
2872 static void
2873 scroll_view(struct view *view, enum request request)
2875         int lines = 1;
2877         assert(view_is_displayed(view));
2879         switch (request) {
2880         case REQ_SCROLL_LEFT:
2881                 if (view->yoffset == 0) {
2882                         report("Cannot scroll beyond the first column");
2883                         return;
2884                 }
2885                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2886                         view->yoffset = 0;
2887                 else
2888                         view->yoffset -= apply_step(opt_hscroll, view->width);
2889                 redraw_view_from(view, 0);
2890                 report("");
2891                 return;
2892         case REQ_SCROLL_RIGHT:
2893                 view->yoffset += apply_step(opt_hscroll, view->width);
2894                 redraw_view(view);
2895                 report("");
2896                 return;
2897         case REQ_SCROLL_PAGE_DOWN:
2898                 lines = view->height;
2899         case REQ_SCROLL_LINE_DOWN:
2900                 if (view->offset + lines > view->lines)
2901                         lines = view->lines - view->offset;
2903                 if (lines == 0 || view->offset + view->height >= view->lines) {
2904                         report("Cannot scroll beyond the last line");
2905                         return;
2906                 }
2907                 break;
2909         case REQ_SCROLL_PAGE_UP:
2910                 lines = view->height;
2911         case REQ_SCROLL_LINE_UP:
2912                 if (lines > view->offset)
2913                         lines = view->offset;
2915                 if (lines == 0) {
2916                         report("Cannot scroll beyond the first line");
2917                         return;
2918                 }
2920                 lines = -lines;
2921                 break;
2923         default:
2924                 die("request %d not handled in switch", request);
2925         }
2927         do_scroll_view(view, lines);
2930 /* Cursor moving */
2931 static void
2932 move_view(struct view *view, enum request request)
2934         int scroll_steps = 0;
2935         int steps;
2937         switch (request) {
2938         case REQ_MOVE_FIRST_LINE:
2939                 steps = -view->lineno;
2940                 break;
2942         case REQ_MOVE_LAST_LINE:
2943                 steps = view->lines - view->lineno - 1;
2944                 break;
2946         case REQ_MOVE_PAGE_UP:
2947                 steps = view->height > view->lineno
2948                       ? -view->lineno : -view->height;
2949                 break;
2951         case REQ_MOVE_PAGE_DOWN:
2952                 steps = view->lineno + view->height >= view->lines
2953                       ? view->lines - view->lineno - 1 : view->height;
2954                 break;
2956         case REQ_MOVE_UP:
2957                 steps = -1;
2958                 break;
2960         case REQ_MOVE_DOWN:
2961                 steps = 1;
2962                 break;
2964         default:
2965                 die("request %d not handled in switch", request);
2966         }
2968         if (steps <= 0 && view->lineno == 0) {
2969                 report("Cannot move beyond the first line");
2970                 return;
2972         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2973                 report("Cannot move beyond the last line");
2974                 return;
2975         }
2977         /* Move the current line */
2978         view->lineno += steps;
2979         assert(0 <= view->lineno && view->lineno < view->lines);
2981         /* Check whether the view needs to be scrolled */
2982         if (view->lineno < view->offset ||
2983             view->lineno >= view->offset + view->height) {
2984                 scroll_steps = steps;
2985                 if (steps < 0 && -steps > view->offset) {
2986                         scroll_steps = -view->offset;
2988                 } else if (steps > 0) {
2989                         if (view->lineno == view->lines - 1 &&
2990                             view->lines > view->height) {
2991                                 scroll_steps = view->lines - view->offset - 1;
2992                                 if (scroll_steps >= view->height)
2993                                         scroll_steps -= view->height - 1;
2994                         }
2995                 }
2996         }
2998         if (!view_is_displayed(view)) {
2999                 view->offset += scroll_steps;
3000                 assert(0 <= view->offset && view->offset < view->lines);
3001                 view->ops->select(view, &view->line[view->lineno]);
3002                 return;
3003         }
3005         /* Repaint the old "current" line if we be scrolling */
3006         if (ABS(steps) < view->height)
3007                 draw_view_line(view, view->lineno - steps - view->offset);
3009         if (scroll_steps) {
3010                 do_scroll_view(view, scroll_steps);
3011                 return;
3012         }
3014         /* Draw the current line */
3015         draw_view_line(view, view->lineno - view->offset);
3017         wnoutrefresh(view->win);
3018         report("");
3022 /*
3023  * Searching
3024  */
3026 static void search_view(struct view *view, enum request request);
3028 static bool
3029 grep_text(struct view *view, const char *text[])
3031         regmatch_t pmatch;
3032         size_t i;
3034         for (i = 0; text[i]; i++)
3035                 if (*text[i] &&
3036                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3037                         return TRUE;
3038         return FALSE;
3041 static void
3042 select_view_line(struct view *view, unsigned long lineno)
3044         unsigned long old_lineno = view->lineno;
3045         unsigned long old_offset = view->offset;
3047         if (goto_view_line(view, view->offset, lineno)) {
3048                 if (view_is_displayed(view)) {
3049                         if (old_offset != view->offset) {
3050                                 redraw_view(view);
3051                         } else {
3052                                 draw_view_line(view, old_lineno - view->offset);
3053                                 draw_view_line(view, view->lineno - view->offset);
3054                                 wnoutrefresh(view->win);
3055                         }
3056                 } else {
3057                         view->ops->select(view, &view->line[view->lineno]);
3058                 }
3059         }
3062 static void
3063 find_next(struct view *view, enum request request)
3065         unsigned long lineno = view->lineno;
3066         int direction;
3068         if (!*view->grep) {
3069                 if (!*opt_search)
3070                         report("No previous search");
3071                 else
3072                         search_view(view, request);
3073                 return;
3074         }
3076         switch (request) {
3077         case REQ_SEARCH:
3078         case REQ_FIND_NEXT:
3079                 direction = 1;
3080                 break;
3082         case REQ_SEARCH_BACK:
3083         case REQ_FIND_PREV:
3084                 direction = -1;
3085                 break;
3087         default:
3088                 return;
3089         }
3091         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3092                 lineno += direction;
3094         /* Note, lineno is unsigned long so will wrap around in which case it
3095          * will become bigger than view->lines. */
3096         for (; lineno < view->lines; lineno += direction) {
3097                 if (view->ops->grep(view, &view->line[lineno])) {
3098                         select_view_line(view, lineno);
3099                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3100                         return;
3101                 }
3102         }
3104         report("No match found for '%s'", view->grep);
3107 static void
3108 search_view(struct view *view, enum request request)
3110         int regex_err;
3112         if (view->regex) {
3113                 regfree(view->regex);
3114                 *view->grep = 0;
3115         } else {
3116                 view->regex = calloc(1, sizeof(*view->regex));
3117                 if (!view->regex)
3118                         return;
3119         }
3121         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3122         if (regex_err != 0) {
3123                 char buf[SIZEOF_STR] = "unknown error";
3125                 regerror(regex_err, view->regex, buf, sizeof(buf));
3126                 report("Search failed: %s", buf);
3127                 return;
3128         }
3130         string_copy(view->grep, opt_search);
3132         find_next(view, request);
3135 /*
3136  * Incremental updating
3137  */
3139 static void
3140 reset_view(struct view *view)
3142         int i;
3144         for (i = 0; i < view->lines; i++)
3145                 free(view->line[i].data);
3146         free(view->line);
3148         view->p_offset = view->offset;
3149         view->p_yoffset = view->yoffset;
3150         view->p_lineno = view->lineno;
3152         view->line = NULL;
3153         view->offset = 0;
3154         view->yoffset = 0;
3155         view->lines  = 0;
3156         view->lineno = 0;
3157         view->vid[0] = 0;
3158         view->update_secs = 0;
3161 static const char *
3162 format_arg(const char *name)
3164         static struct {
3165                 const char *name;
3166                 size_t namelen;
3167                 const char *value;
3168                 const char *value_if_empty;
3169         } vars[] = {
3170 #define FORMAT_VAR(name, value, value_if_empty) \
3171         { name, STRING_SIZE(name), value, value_if_empty }
3172                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3173                 FORMAT_VAR("%(file)",           opt_file,       ""),
3174                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3175                 FORMAT_VAR("%(head)",           ref_head,       ""),
3176                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3177                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3178                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3179         };
3180         int i;
3182         for (i = 0; i < ARRAY_SIZE(vars); i++)
3183                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3184                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3186         report("Unknown replacement: `%s`", name);
3187         return NULL;
3190 static bool
3191 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3193         char buf[SIZEOF_STR];
3194         int argc;
3195         bool noreplace = flags == FORMAT_NONE;
3197         argv_free(dst_argv);
3199         for (argc = 0; src_argv[argc]; argc++) {
3200                 const char *arg = src_argv[argc];
3201                 size_t bufpos = 0;
3203                 while (arg) {
3204                         char *next = strstr(arg, "%(");
3205                         int len = next - arg;
3206                         const char *value;
3208                         if (!next || noreplace) {
3209                                 len = strlen(arg);
3210                                 value = "";
3212                         } else {
3213                                 value = format_arg(next);
3215                                 if (!value) {
3216                                         return FALSE;
3217                                 }
3218                         }
3220                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3221                                 return FALSE;
3223                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3224                 }
3226                 dst_argv[argc] = strdup(buf);
3227                 if (!dst_argv[argc])
3228                         break;
3229         }
3231         dst_argv[argc] = NULL;
3233         return src_argv[argc] == NULL;
3236 static bool
3237 restore_view_position(struct view *view)
3239         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3240                 return FALSE;
3242         /* Changing the view position cancels the restoring. */
3243         /* FIXME: Changing back to the first line is not detected. */
3244         if (view->offset != 0 || view->lineno != 0) {
3245                 view->p_restore = FALSE;
3246                 return FALSE;
3247         }
3249         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3250             view_is_displayed(view))
3251                 werase(view->win);
3253         view->yoffset = view->p_yoffset;
3254         view->p_restore = FALSE;
3256         return TRUE;
3259 static void
3260 end_update(struct view *view, bool force)
3262         if (!view->pipe)
3263                 return;
3264         while (!view->ops->read(view, NULL))
3265                 if (!force)
3266                         return;
3267         if (force)
3268                 io_kill(view->pipe);
3269         io_done(view->pipe);
3270         view->pipe = NULL;
3273 static void
3274 setup_update(struct view *view, const char *vid)
3276         reset_view(view);
3277         string_copy_rev(view->vid, vid);
3278         view->pipe = &view->io;
3279         view->start_time = time(NULL);
3282 static bool
3283 prepare_update(struct view *view, const char *argv[], const char *dir)
3285         if (view->pipe)
3286                 end_update(view, TRUE);
3287         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3290 static bool
3291 prepare_update_file(struct view *view, const char *name)
3293         if (view->pipe)
3294                 end_update(view, TRUE);
3295         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3298 static bool
3299 begin_update(struct view *view, bool refresh)
3301         if (view->pipe)
3302                 end_update(view, TRUE);
3304         if (!refresh) {
3305                 if (view->ops->prepare) {
3306                         if (!view->ops->prepare(view))
3307                                 return FALSE;
3308                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3309                         return FALSE;
3310                 }
3312                 /* Put the current ref_* value to the view title ref
3313                  * member. This is needed by the blob view. Most other
3314                  * views sets it automatically after loading because the
3315                  * first line is a commit line. */
3316                 string_copy_rev(view->ref, view->id);
3317         }
3319         if (!io_start(&view->io))
3320                 return FALSE;
3322         setup_update(view, view->id);
3324         return TRUE;
3327 static bool
3328 update_view(struct view *view)
3330         char out_buffer[BUFSIZ * 2];
3331         char *line;
3332         /* Clear the view and redraw everything since the tree sorting
3333          * might have rearranged things. */
3334         bool redraw = view->lines == 0;
3335         bool can_read = TRUE;
3337         if (!view->pipe)
3338                 return TRUE;
3340         if (!io_can_read(view->pipe)) {
3341                 if (view->lines == 0 && view_is_displayed(view)) {
3342                         time_t secs = time(NULL) - view->start_time;
3344                         if (secs > 1 && secs > view->update_secs) {
3345                                 if (view->update_secs == 0)
3346                                         redraw_view(view);
3347                                 update_view_title(view);
3348                                 view->update_secs = secs;
3349                         }
3350                 }
3351                 return TRUE;
3352         }
3354         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3355                 if (opt_iconv_in != ICONV_NONE) {
3356                         ICONV_CONST char *inbuf = line;
3357                         size_t inlen = strlen(line) + 1;
3359                         char *outbuf = out_buffer;
3360                         size_t outlen = sizeof(out_buffer);
3362                         size_t ret;
3364                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3365                         if (ret != (size_t) -1)
3366                                 line = out_buffer;
3367                 }
3369                 if (!view->ops->read(view, line)) {
3370                         report("Allocation failure");
3371                         end_update(view, TRUE);
3372                         return FALSE;
3373                 }
3374         }
3376         {
3377                 unsigned long lines = view->lines;
3378                 int digits;
3380                 for (digits = 0; lines; digits++)
3381                         lines /= 10;
3383                 /* Keep the displayed view in sync with line number scaling. */
3384                 if (digits != view->digits) {
3385                         view->digits = digits;
3386                         if (opt_line_number || view->type == VIEW_BLAME)
3387                                 redraw = TRUE;
3388                 }
3389         }
3391         if (io_error(view->pipe)) {
3392                 report("Failed to read: %s", io_strerror(view->pipe));
3393                 end_update(view, TRUE);
3395         } else if (io_eof(view->pipe)) {
3396                 if (view_is_displayed(view))
3397                         report("");
3398                 end_update(view, FALSE);
3399         }
3401         if (restore_view_position(view))
3402                 redraw = TRUE;
3404         if (!view_is_displayed(view))
3405                 return TRUE;
3407         if (redraw)
3408                 redraw_view_from(view, 0);
3409         else
3410                 redraw_view_dirty(view);
3412         /* Update the title _after_ the redraw so that if the redraw picks up a
3413          * commit reference in view->ref it'll be available here. */
3414         update_view_title(view);
3415         return TRUE;
3418 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3420 static struct line *
3421 add_line_data(struct view *view, void *data, enum line_type type)
3423         struct line *line;
3425         if (!realloc_lines(&view->line, view->lines, 1))
3426                 return NULL;
3428         line = &view->line[view->lines++];
3429         memset(line, 0, sizeof(*line));
3430         line->type = type;
3431         line->data = data;
3432         line->dirty = 1;
3434         return line;
3437 static struct line *
3438 add_line_text(struct view *view, const char *text, enum line_type type)
3440         char *data = text ? strdup(text) : NULL;
3442         return data ? add_line_data(view, data, type) : NULL;
3445 static struct line *
3446 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3448         char buf[SIZEOF_STR];
3449         va_list args;
3451         va_start(args, fmt);
3452         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3453                 buf[0] = 0;
3454         va_end(args);
3456         return buf[0] ? add_line_text(view, buf, type) : NULL;
3459 /*
3460  * View opening
3461  */
3463 enum open_flags {
3464         OPEN_DEFAULT = 0,       /* Use default view switching. */
3465         OPEN_SPLIT = 1,         /* Split current view. */
3466         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3467         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3468         OPEN_PREPARED = 32,     /* Open already prepared command. */
3469 };
3471 static void
3472 open_view(struct view *prev, enum request request, enum open_flags flags)
3474         bool split = !!(flags & OPEN_SPLIT);
3475         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3476         bool nomaximize = !!(flags & OPEN_REFRESH);
3477         struct view *view = VIEW(request);
3478         int nviews = displayed_views();
3479         struct view *base_view = display[0];
3481         if (view == prev && nviews == 1 && !reload) {
3482                 report("Already in %s view", view->name);
3483                 return;
3484         }
3486         if (view->git_dir && !opt_git_dir[0]) {
3487                 report("The %s view is disabled in pager view", view->name);
3488                 return;
3489         }
3491         if (split) {
3492                 display[1] = view;
3493                 current_view = 1;
3494                 view->parent = prev;
3495         } else if (!nomaximize) {
3496                 /* Maximize the current view. */
3497                 memset(display, 0, sizeof(display));
3498                 current_view = 0;
3499                 display[current_view] = view;
3500         }
3502         /* No prev signals that this is the first loaded view. */
3503         if (prev && view != prev) {
3504                 view->prev = prev;
3505         }
3507         /* Resize the view when switching between split- and full-screen,
3508          * or when switching between two different full-screen views. */
3509         if (nviews != displayed_views() ||
3510             (nviews == 1 && base_view != display[0]))
3511                 resize_display();
3513         if (view->ops->open) {
3514                 if (view->pipe)
3515                         end_update(view, TRUE);
3516                 if (!view->ops->open(view)) {
3517                         report("Failed to load %s view", view->name);
3518                         return;
3519                 }
3520                 restore_view_position(view);
3522         } else if ((reload || strcmp(view->vid, view->id)) &&
3523                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3524                 report("Failed to load %s view", view->name);
3525                 return;
3526         }
3528         if (split && prev->lineno - prev->offset >= prev->height) {
3529                 /* Take the title line into account. */
3530                 int lines = prev->lineno - prev->offset - prev->height + 1;
3532                 /* Scroll the view that was split if the current line is
3533                  * outside the new limited view. */
3534                 do_scroll_view(prev, lines);
3535         }
3537         if (prev && view != prev && split && view_is_displayed(prev)) {
3538                 /* "Blur" the previous view. */
3539                 update_view_title(prev);
3540         }
3542         if (view->pipe && view->lines == 0) {
3543                 /* Clear the old view and let the incremental updating refill
3544                  * the screen. */
3545                 werase(view->win);
3546                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3547                 report("");
3548         } else if (view_is_displayed(view)) {
3549                 redraw_view(view);
3550                 report("");
3551         }
3554 static void
3555 open_external_viewer(const char *argv[], const char *dir)
3557         def_prog_mode();           /* save current tty modes */
3558         endwin();                  /* restore original tty modes */
3559         io_run_fg(argv, dir);
3560         fprintf(stderr, "Press Enter to continue");
3561         getc(opt_tty);
3562         reset_prog_mode();
3563         redraw_display(TRUE);
3566 static void
3567 open_mergetool(const char *file)
3569         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3571         open_external_viewer(mergetool_argv, opt_cdup);
3574 static void
3575 open_editor(const char *file)
3577         const char *editor_argv[] = { "vi", file, NULL };
3578         const char *editor;
3580         editor = getenv("GIT_EDITOR");
3581         if (!editor && *opt_editor)
3582                 editor = opt_editor;
3583         if (!editor)
3584                 editor = getenv("VISUAL");
3585         if (!editor)
3586                 editor = getenv("EDITOR");
3587         if (!editor)
3588                 editor = "vi";
3590         editor_argv[0] = editor;
3591         open_external_viewer(editor_argv, opt_cdup);
3594 static void
3595 open_run_request(enum request request)
3597         struct run_request *req = get_run_request(request);
3598         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3600         if (!req) {
3601                 report("Unknown run request");
3602                 return;
3603         }
3605         if (format_argv(argv, req->argv, FORMAT_ALL))
3606                 open_external_viewer(argv, NULL);
3607         argv_free(argv);
3610 /*
3611  * User request switch noodle
3612  */
3614 static int
3615 view_driver(struct view *view, enum request request)
3617         int i;
3619         if (request == REQ_NONE)
3620                 return TRUE;
3622         if (request > REQ_NONE) {
3623                 open_run_request(request);
3624                 view_request(view, REQ_REFRESH);
3625                 return TRUE;
3626         }
3628         request = view_request(view, request);
3629         if (request == REQ_NONE)
3630                 return TRUE;
3632         switch (request) {
3633         case REQ_MOVE_UP:
3634         case REQ_MOVE_DOWN:
3635         case REQ_MOVE_PAGE_UP:
3636         case REQ_MOVE_PAGE_DOWN:
3637         case REQ_MOVE_FIRST_LINE:
3638         case REQ_MOVE_LAST_LINE:
3639                 move_view(view, request);
3640                 break;
3642         case REQ_SCROLL_LEFT:
3643         case REQ_SCROLL_RIGHT:
3644         case REQ_SCROLL_LINE_DOWN:
3645         case REQ_SCROLL_LINE_UP:
3646         case REQ_SCROLL_PAGE_DOWN:
3647         case REQ_SCROLL_PAGE_UP:
3648                 scroll_view(view, request);
3649                 break;
3651         case REQ_VIEW_BLAME:
3652                 if (!opt_file[0]) {
3653                         report("No file chosen, press %s to open tree view",
3654                                get_key(view->keymap, REQ_VIEW_TREE));
3655                         break;
3656                 }
3657                 open_view(view, request, OPEN_DEFAULT);
3658                 break;
3660         case REQ_VIEW_BLOB:
3661                 if (!ref_blob[0]) {
3662                         report("No file chosen, press %s to open tree view",
3663                                get_key(view->keymap, REQ_VIEW_TREE));
3664                         break;
3665                 }
3666                 open_view(view, request, OPEN_DEFAULT);
3667                 break;
3669         case REQ_VIEW_PAGER:
3670                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3671                         report("No pager content, press %s to run command from prompt",
3672                                get_key(view->keymap, REQ_PROMPT));
3673                         break;
3674                 }
3675                 open_view(view, request, OPEN_DEFAULT);
3676                 break;
3678         case REQ_VIEW_STAGE:
3679                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3680                         report("No stage content, press %s to open the status view and choose file",
3681                                get_key(view->keymap, REQ_VIEW_STATUS));
3682                         break;
3683                 }
3684                 open_view(view, request, OPEN_DEFAULT);
3685                 break;
3687         case REQ_VIEW_STATUS:
3688                 if (opt_is_inside_work_tree == FALSE) {
3689                         report("The status view requires a working tree");
3690                         break;
3691                 }
3692                 open_view(view, request, OPEN_DEFAULT);
3693                 break;
3695         case REQ_VIEW_MAIN:
3696         case REQ_VIEW_DIFF:
3697         case REQ_VIEW_LOG:
3698         case REQ_VIEW_TREE:
3699         case REQ_VIEW_HELP:
3700         case REQ_VIEW_BRANCH:
3701                 open_view(view, request, OPEN_DEFAULT);
3702                 break;
3704         case REQ_NEXT:
3705         case REQ_PREVIOUS:
3706                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3708                 if (view->parent) {
3709                         int line;
3711                         view = view->parent;
3712                         line = view->lineno;
3713                         move_view(view, request);
3714                         if (view_is_displayed(view))
3715                                 update_view_title(view);
3716                         if (line != view->lineno)
3717                                 view_request(view, REQ_ENTER);
3718                 } else {
3719                         move_view(view, request);
3720                 }
3721                 break;
3723         case REQ_VIEW_NEXT:
3724         {
3725                 int nviews = displayed_views();
3726                 int next_view = (current_view + 1) % nviews;
3728                 if (next_view == current_view) {
3729                         report("Only one view is displayed");
3730                         break;
3731                 }
3733                 current_view = next_view;
3734                 /* Blur out the title of the previous view. */
3735                 update_view_title(view);
3736                 report("");
3737                 break;
3738         }
3739         case REQ_REFRESH:
3740                 report("Refreshing is not yet supported for the %s view", view->name);
3741                 break;
3743         case REQ_MAXIMIZE:
3744                 if (displayed_views() == 2)
3745                         maximize_view(view);
3746                 break;
3748         case REQ_OPTIONS:
3749                 open_option_menu();
3750                 break;
3752         case REQ_TOGGLE_LINENO:
3753                 toggle_view_option(&opt_line_number, "line numbers");
3754                 break;
3756         case REQ_TOGGLE_DATE:
3757                 toggle_date();
3758                 break;
3760         case REQ_TOGGLE_AUTHOR:
3761                 toggle_author();
3762                 break;
3764         case REQ_TOGGLE_REV_GRAPH:
3765                 toggle_view_option(&opt_rev_graph, "revision graph display");
3766                 break;
3768         case REQ_TOGGLE_REFS:
3769                 toggle_view_option(&opt_show_refs, "reference display");
3770                 break;
3772         case REQ_TOGGLE_SORT_FIELD:
3773         case REQ_TOGGLE_SORT_ORDER:
3774                 report("Sorting is not yet supported for the %s view", view->name);
3775                 break;
3777         case REQ_SEARCH:
3778         case REQ_SEARCH_BACK:
3779                 search_view(view, request);
3780                 break;
3782         case REQ_FIND_NEXT:
3783         case REQ_FIND_PREV:
3784                 find_next(view, request);
3785                 break;
3787         case REQ_STOP_LOADING:
3788                 foreach_view(view, i) {
3789                         if (view->pipe)
3790                                 report("Stopped loading the %s view", view->name),
3791                         end_update(view, TRUE);
3792                 }
3793                 break;
3795         case REQ_SHOW_VERSION:
3796                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3797                 return TRUE;
3799         case REQ_SCREEN_REDRAW:
3800                 redraw_display(TRUE);
3801                 break;
3803         case REQ_EDIT:
3804                 report("Nothing to edit");
3805                 break;
3807         case REQ_ENTER:
3808                 report("Nothing to enter");
3809                 break;
3811         case REQ_VIEW_CLOSE:
3812                 /* XXX: Mark closed views by letting view->prev point to the
3813                  * view itself. Parents to closed view should never be
3814                  * followed. */
3815                 if (view->prev && view->prev != view) {
3816                         maximize_view(view->prev);
3817                         view->prev = view;
3818                         break;
3819                 }
3820                 /* Fall-through */
3821         case REQ_QUIT:
3822                 return FALSE;
3824         default:
3825                 report("Unknown key, press %s for help",
3826                        get_key(view->keymap, REQ_VIEW_HELP));
3827                 return TRUE;
3828         }
3830         return TRUE;
3834 /*
3835  * View backend utilities
3836  */
3838 enum sort_field {
3839         ORDERBY_NAME,
3840         ORDERBY_DATE,
3841         ORDERBY_AUTHOR,
3842 };
3844 struct sort_state {
3845         const enum sort_field *fields;
3846         size_t size, current;
3847         bool reverse;
3848 };
3850 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3851 #define get_sort_field(state) ((state).fields[(state).current])
3852 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3854 static void
3855 sort_view(struct view *view, enum request request, struct sort_state *state,
3856           int (*compare)(const void *, const void *))
3858         switch (request) {
3859         case REQ_TOGGLE_SORT_FIELD:
3860                 state->current = (state->current + 1) % state->size;
3861                 break;
3863         case REQ_TOGGLE_SORT_ORDER:
3864                 state->reverse = !state->reverse;
3865                 break;
3866         default:
3867                 die("Not a sort request");
3868         }
3870         qsort(view->line, view->lines, sizeof(*view->line), compare);
3871         redraw_view(view);
3874 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3876 /* Small author cache to reduce memory consumption. It uses binary
3877  * search to lookup or find place to position new entries. No entries
3878  * are ever freed. */
3879 static const char *
3880 get_author(const char *name)
3882         static const char **authors;
3883         static size_t authors_size;
3884         int from = 0, to = authors_size - 1;
3886         while (from <= to) {
3887                 size_t pos = (to + from) / 2;
3888                 int cmp = strcmp(name, authors[pos]);
3890                 if (!cmp)
3891                         return authors[pos];
3893                 if (cmp < 0)
3894                         to = pos - 1;
3895                 else
3896                         from = pos + 1;
3897         }
3899         if (!realloc_authors(&authors, authors_size, 1))
3900                 return NULL;
3901         name = strdup(name);
3902         if (!name)
3903                 return NULL;
3905         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3906         authors[from] = name;
3907         authors_size++;
3909         return name;
3912 static void
3913 parse_timesec(struct time *time, const char *sec)
3915         time->sec = (time_t) atol(sec);
3918 static void
3919 parse_timezone(struct time *time, const char *zone)
3921         long tz;
3923         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3924         tz += ('0' - zone[2]) * 60 * 60;
3925         tz += ('0' - zone[3]) * 60 * 10;
3926         tz += ('0' - zone[4]) * 60;
3928         if (zone[0] == '-')
3929                 tz = -tz;
3931         time->tz = tz;
3932         time->sec -= tz;
3935 /* Parse author lines where the name may be empty:
3936  *      author  <email@address.tld> 1138474660 +0100
3937  */
3938 static void
3939 parse_author_line(char *ident, const char **author, struct time *time)
3941         char *nameend = strchr(ident, '<');
3942         char *emailend = strchr(ident, '>');
3944         if (nameend && emailend)
3945                 *nameend = *emailend = 0;
3946         ident = chomp_string(ident);
3947         if (!*ident) {
3948                 if (nameend)
3949                         ident = chomp_string(nameend + 1);
3950                 if (!*ident)
3951                         ident = "Unknown";
3952         }
3954         *author = get_author(ident);
3956         /* Parse epoch and timezone */
3957         if (emailend && emailend[1] == ' ') {
3958                 char *secs = emailend + 2;
3959                 char *zone = strchr(secs, ' ');
3961                 parse_timesec(time, secs);
3963                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3964                         parse_timezone(time, zone + 1);
3965         }
3968 static bool
3969 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3971         char rev[SIZEOF_REV];
3972         const char *revlist_argv[] = {
3973                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3974         };
3975         struct menu_item *items;
3976         char text[SIZEOF_STR];
3977         bool ok = TRUE;
3978         int i;
3980         items = calloc(*parents + 1, sizeof(*items));
3981         if (!items)
3982                 return FALSE;
3984         for (i = 0; i < *parents; i++) {
3985                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3986                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3987                     !(items[i].text = strdup(text))) {
3988                         ok = FALSE;
3989                         break;
3990                 }
3991         }
3993         if (ok) {
3994                 *parents = 0;
3995                 ok = prompt_menu("Select parent", items, parents);
3996         }
3997         for (i = 0; items[i].text; i++)
3998                 free((char *) items[i].text);
3999         free(items);
4000         return ok;
4003 static bool
4004 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4006         char buf[SIZEOF_STR * 4];
4007         const char *revlist_argv[] = {
4008                 "git", "log", "--no-color", "-1",
4009                         "--pretty=format:%P", id, "--", path, NULL
4010         };
4011         int parents;
4013         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4014             (parents = strlen(buf) / 40) < 0) {
4015                 report("Failed to get parent information");
4016                 return FALSE;
4018         } else if (parents == 0) {
4019                 if (path)
4020                         report("Path '%s' does not exist in the parent", path);
4021                 else
4022                         report("The selected commit has no parents");
4023                 return FALSE;
4024         }
4026         if (parents == 1)
4027                 parents = 0;
4028         else if (!open_commit_parent_menu(buf, &parents))
4029                 return FALSE;
4031         string_copy_rev(rev, &buf[41 * parents]);
4032         return TRUE;
4035 /*
4036  * Pager backend
4037  */
4039 static bool
4040 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4042         char text[SIZEOF_STR];
4044         if (opt_line_number && draw_lineno(view, lineno))
4045                 return TRUE;
4047         string_expand(text, sizeof(text), line->data, opt_tab_size);
4048         draw_text(view, line->type, text, TRUE);
4049         return TRUE;
4052 static bool
4053 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4055         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4056         char ref[SIZEOF_STR];
4058         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4059                 return TRUE;
4061         /* This is the only fatal call, since it can "corrupt" the buffer. */
4062         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4063                 return FALSE;
4065         return TRUE;
4068 static void
4069 add_pager_refs(struct view *view, struct line *line)
4071         char buf[SIZEOF_STR];
4072         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4073         struct ref_list *list;
4074         size_t bufpos = 0, i;
4075         const char *sep = "Refs: ";
4076         bool is_tag = FALSE;
4078         assert(line->type == LINE_COMMIT);
4080         list = get_ref_list(commit_id);
4081         if (!list) {
4082                 if (view->type == VIEW_DIFF)
4083                         goto try_add_describe_ref;
4084                 return;
4085         }
4087         for (i = 0; i < list->size; i++) {
4088                 struct ref *ref = list->refs[i];
4089                 const char *fmt = ref->tag    ? "%s[%s]" :
4090                                   ref->remote ? "%s<%s>" : "%s%s";
4092                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4093                         return;
4094                 sep = ", ";
4095                 if (ref->tag)
4096                         is_tag = TRUE;
4097         }
4099         if (!is_tag && view->type == VIEW_DIFF) {
4100 try_add_describe_ref:
4101                 /* Add <tag>-g<commit_id> "fake" reference. */
4102                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4103                         return;
4104         }
4106         if (bufpos == 0)
4107                 return;
4109         add_line_text(view, buf, LINE_PP_REFS);
4112 static bool
4113 pager_read(struct view *view, char *data)
4115         struct line *line;
4117         if (!data)
4118                 return TRUE;
4120         line = add_line_text(view, data, get_line_type(data));
4121         if (!line)
4122                 return FALSE;
4124         if (line->type == LINE_COMMIT &&
4125             (view->type == VIEW_DIFF ||
4126              view->type == VIEW_LOG))
4127                 add_pager_refs(view, line);
4129         return TRUE;
4132 static enum request
4133 pager_request(struct view *view, enum request request, struct line *line)
4135         int split = 0;
4137         if (request != REQ_ENTER)
4138                 return request;
4140         if (line->type == LINE_COMMIT &&
4141            (view->type == VIEW_LOG ||
4142             view->type == VIEW_PAGER)) {
4143                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4144                 split = 1;
4145         }
4147         /* Always scroll the view even if it was split. That way
4148          * you can use Enter to scroll through the log view and
4149          * split open each commit diff. */
4150         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4152         /* FIXME: A minor workaround. Scrolling the view will call report("")
4153          * but if we are scrolling a non-current view this won't properly
4154          * update the view title. */
4155         if (split)
4156                 update_view_title(view);
4158         return REQ_NONE;
4161 static bool
4162 pager_grep(struct view *view, struct line *line)
4164         const char *text[] = { line->data, NULL };
4166         return grep_text(view, text);
4169 static void
4170 pager_select(struct view *view, struct line *line)
4172         if (line->type == LINE_COMMIT) {
4173                 char *text = (char *)line->data + STRING_SIZE("commit ");
4175                 if (view->type != VIEW_PAGER)
4176                         string_copy_rev(view->ref, text);
4177                 string_copy_rev(ref_commit, text);
4178         }
4181 static struct view_ops pager_ops = {
4182         "line",
4183         NULL,
4184         NULL,
4185         pager_read,
4186         pager_draw,
4187         pager_request,
4188         pager_grep,
4189         pager_select,
4190 };
4192 static const char *log_argv[SIZEOF_ARG] = {
4193         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4194 };
4196 static enum request
4197 log_request(struct view *view, enum request request, struct line *line)
4199         switch (request) {
4200         case REQ_REFRESH:
4201                 load_refs();
4202                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4203                 return REQ_NONE;
4204         default:
4205                 return pager_request(view, request, line);
4206         }
4209 static struct view_ops log_ops = {
4210         "line",
4211         log_argv,
4212         NULL,
4213         pager_read,
4214         pager_draw,
4215         log_request,
4216         pager_grep,
4217         pager_select,
4218 };
4220 static const char *diff_argv[SIZEOF_ARG] = {
4221         "git", "show", "--pretty=fuller", "--no-color", "--root",
4222                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4223 };
4225 static struct view_ops diff_ops = {
4226         "line",
4227         diff_argv,
4228         NULL,
4229         pager_read,
4230         pager_draw,
4231         pager_request,
4232         pager_grep,
4233         pager_select,
4234 };
4236 /*
4237  * Help backend
4238  */
4240 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4242 static bool
4243 help_open_keymap_title(struct view *view, enum keymap keymap)
4245         struct line *line;
4247         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4248                                help_keymap_hidden[keymap] ? '+' : '-',
4249                                enum_name(keymap_table[keymap]));
4250         if (line)
4251                 line->other = keymap;
4253         return help_keymap_hidden[keymap];
4256 static void
4257 help_open_keymap(struct view *view, enum keymap keymap)
4259         const char *group = NULL;
4260         char buf[SIZEOF_STR];
4261         size_t bufpos;
4262         bool add_title = TRUE;
4263         int i;
4265         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4266                 const char *key = NULL;
4268                 if (req_info[i].request == REQ_NONE)
4269                         continue;
4271                 if (!req_info[i].request) {
4272                         group = req_info[i].help;
4273                         continue;
4274                 }
4276                 key = get_keys(keymap, req_info[i].request, TRUE);
4277                 if (!key || !*key)
4278                         continue;
4280                 if (add_title && help_open_keymap_title(view, keymap))
4281                         return;
4282                 add_title = FALSE;
4284                 if (group) {
4285                         add_line_text(view, group, LINE_HELP_GROUP);
4286                         group = NULL;
4287                 }
4289                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4290                                 enum_name(req_info[i]), req_info[i].help);
4291         }
4293         group = "External commands:";
4295         for (i = 0; i < run_requests; i++) {
4296                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4297                 const char *key;
4298                 int argc;
4300                 if (!req || req->keymap != keymap)
4301                         continue;
4303                 key = get_key_name(req->key);
4304                 if (!*key)
4305                         key = "(no key defined)";
4307                 if (add_title && help_open_keymap_title(view, keymap))
4308                         return;
4309                 if (group) {
4310                         add_line_text(view, group, LINE_HELP_GROUP);
4311                         group = NULL;
4312                 }
4314                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4315                         if (!string_format_from(buf, &bufpos, "%s%s",
4316                                                 argc ? " " : "", req->argv[argc]))
4317                                 return;
4319                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4320         }
4323 static bool
4324 help_open(struct view *view)
4326         enum keymap keymap;
4328         reset_view(view);
4329         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4330         add_line_text(view, "", LINE_DEFAULT);
4332         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4333                 help_open_keymap(view, keymap);
4335         return TRUE;
4338 static enum request
4339 help_request(struct view *view, enum request request, struct line *line)
4341         switch (request) {
4342         case REQ_ENTER:
4343                 if (line->type == LINE_HELP_KEYMAP) {
4344                         help_keymap_hidden[line->other] =
4345                                 !help_keymap_hidden[line->other];
4346                         view->p_restore = TRUE;
4347                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4348                 }
4350                 return REQ_NONE;
4351         default:
4352                 return pager_request(view, request, line);
4353         }
4356 static struct view_ops help_ops = {
4357         "line",
4358         NULL,
4359         help_open,
4360         NULL,
4361         pager_draw,
4362         help_request,
4363         pager_grep,
4364         pager_select,
4365 };
4368 /*
4369  * Tree backend
4370  */
4372 struct tree_stack_entry {
4373         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4374         unsigned long lineno;           /* Line number to restore */
4375         char *name;                     /* Position of name in opt_path */
4376 };
4378 /* The top of the path stack. */
4379 static struct tree_stack_entry *tree_stack = NULL;
4380 unsigned long tree_lineno = 0;
4382 static void
4383 pop_tree_stack_entry(void)
4385         struct tree_stack_entry *entry = tree_stack;
4387         tree_lineno = entry->lineno;
4388         entry->name[0] = 0;
4389         tree_stack = entry->prev;
4390         free(entry);
4393 static void
4394 push_tree_stack_entry(const char *name, unsigned long lineno)
4396         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4397         size_t pathlen = strlen(opt_path);
4399         if (!entry)
4400                 return;
4402         entry->prev = tree_stack;
4403         entry->name = opt_path + pathlen;
4404         tree_stack = entry;
4406         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4407                 pop_tree_stack_entry();
4408                 return;
4409         }
4411         /* Move the current line to the first tree entry. */
4412         tree_lineno = 1;
4413         entry->lineno = lineno;
4416 /* Parse output from git-ls-tree(1):
4417  *
4418  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4419  */
4421 #define SIZEOF_TREE_ATTR \
4422         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4424 #define SIZEOF_TREE_MODE \
4425         STRING_SIZE("100644 ")
4427 #define TREE_ID_OFFSET \
4428         STRING_SIZE("100644 blob ")
4430 struct tree_entry {
4431         char id[SIZEOF_REV];
4432         mode_t mode;
4433         struct time time;               /* Date from the author ident. */
4434         const char *author;             /* Author of the commit. */
4435         char name[1];
4436 };
4438 static const char *
4439 tree_path(const struct line *line)
4441         return ((struct tree_entry *) line->data)->name;
4444 static int
4445 tree_compare_entry(const struct line *line1, const struct line *line2)
4447         if (line1->type != line2->type)
4448                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4449         return strcmp(tree_path(line1), tree_path(line2));
4452 static const enum sort_field tree_sort_fields[] = {
4453         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4454 };
4455 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4457 static int
4458 tree_compare(const void *l1, const void *l2)
4460         const struct line *line1 = (const struct line *) l1;
4461         const struct line *line2 = (const struct line *) l2;
4462         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4463         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4465         if (line1->type == LINE_TREE_HEAD)
4466                 return -1;
4467         if (line2->type == LINE_TREE_HEAD)
4468                 return 1;
4470         switch (get_sort_field(tree_sort_state)) {
4471         case ORDERBY_DATE:
4472                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4474         case ORDERBY_AUTHOR:
4475                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4477         case ORDERBY_NAME:
4478         default:
4479                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4480         }
4484 static struct line *
4485 tree_entry(struct view *view, enum line_type type, const char *path,
4486            const char *mode, const char *id)
4488         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4489         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4491         if (!entry || !line) {
4492                 free(entry);
4493                 return NULL;
4494         }
4496         strncpy(entry->name, path, strlen(path));
4497         if (mode)
4498                 entry->mode = strtoul(mode, NULL, 8);
4499         if (id)
4500                 string_copy_rev(entry->id, id);
4502         return line;
4505 static bool
4506 tree_read_date(struct view *view, char *text, bool *read_date)
4508         static const char *author_name;
4509         static struct time author_time;
4511         if (!text && *read_date) {
4512                 *read_date = FALSE;
4513                 return TRUE;
4515         } else if (!text) {
4516                 char *path = *opt_path ? opt_path : ".";
4517                 /* Find next entry to process */
4518                 const char *log_file[] = {
4519                         "git", "log", "--no-color", "--pretty=raw",
4520                                 "--cc", "--raw", view->id, "--", path, NULL
4521                 };
4522                 struct io io = {};
4524                 if (!view->lines) {
4525                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4526                         report("Tree is empty");
4527                         return TRUE;
4528                 }
4530                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4531                         report("Failed to load tree data");
4532                         return TRUE;
4533                 }
4535                 io_done(view->pipe);
4536                 view->io = io;
4537                 *read_date = TRUE;
4538                 return FALSE;
4540         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4541                 parse_author_line(text + STRING_SIZE("author "),
4542                                   &author_name, &author_time);
4544         } else if (*text == ':') {
4545                 char *pos;
4546                 size_t annotated = 1;
4547                 size_t i;
4549                 pos = strchr(text, '\t');
4550                 if (!pos)
4551                         return TRUE;
4552                 text = pos + 1;
4553                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4554                         text += strlen(opt_path);
4555                 pos = strchr(text, '/');
4556                 if (pos)
4557                         *pos = 0;
4559                 for (i = 1; i < view->lines; i++) {
4560                         struct line *line = &view->line[i];
4561                         struct tree_entry *entry = line->data;
4563                         annotated += !!entry->author;
4564                         if (entry->author || strcmp(entry->name, text))
4565                                 continue;
4567                         entry->author = author_name;
4568                         entry->time = author_time;
4569                         line->dirty = 1;
4570                         break;
4571                 }
4573                 if (annotated == view->lines)
4574                         io_kill(view->pipe);
4575         }
4576         return TRUE;
4579 static bool
4580 tree_read(struct view *view, char *text)
4582         static bool read_date = FALSE;
4583         struct tree_entry *data;
4584         struct line *entry, *line;
4585         enum line_type type;
4586         size_t textlen = text ? strlen(text) : 0;
4587         char *path = text + SIZEOF_TREE_ATTR;
4589         if (read_date || !text)
4590                 return tree_read_date(view, text, &read_date);
4592         if (textlen <= SIZEOF_TREE_ATTR)
4593                 return FALSE;
4594         if (view->lines == 0 &&
4595             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4596                 return FALSE;
4598         /* Strip the path part ... */
4599         if (*opt_path) {
4600                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4601                 size_t striplen = strlen(opt_path);
4603                 if (pathlen > striplen)
4604                         memmove(path, path + striplen,
4605                                 pathlen - striplen + 1);
4607                 /* Insert "link" to parent directory. */
4608                 if (view->lines == 1 &&
4609                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4610                         return FALSE;
4611         }
4613         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4614         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4615         if (!entry)
4616                 return FALSE;
4617         data = entry->data;
4619         /* Skip "Directory ..." and ".." line. */
4620         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4621                 if (tree_compare_entry(line, entry) <= 0)
4622                         continue;
4624                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4626                 line->data = data;
4627                 line->type = type;
4628                 for (; line <= entry; line++)
4629                         line->dirty = line->cleareol = 1;
4630                 return TRUE;
4631         }
4633         if (tree_lineno > view->lineno) {
4634                 view->lineno = tree_lineno;
4635                 tree_lineno = 0;
4636         }
4638         return TRUE;
4641 static bool
4642 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4644         struct tree_entry *entry = line->data;
4646         if (line->type == LINE_TREE_HEAD) {
4647                 if (draw_text(view, line->type, "Directory path /", TRUE))
4648                         return TRUE;
4649         } else {
4650                 if (draw_mode(view, entry->mode))
4651                         return TRUE;
4653                 if (opt_author && draw_author(view, entry->author))
4654                         return TRUE;
4656                 if (opt_date && draw_date(view, &entry->time))
4657                         return TRUE;
4658         }
4659         if (draw_text(view, line->type, entry->name, TRUE))
4660                 return TRUE;
4661         return TRUE;
4664 static void
4665 open_blob_editor(const char *id)
4667         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4668         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4669         int fd = mkstemp(file);
4671         if (fd == -1)
4672                 report("Failed to create temporary file");
4673         else if (!io_run_append(blob_argv, FORMAT_NONE, fd))
4674                 report("Failed to save blob data to file");
4675         else
4676                 open_editor(file);
4677         if (fd != -1)
4678                 unlink(file);
4681 static enum request
4682 tree_request(struct view *view, enum request request, struct line *line)
4684         enum open_flags flags;
4685         struct tree_entry *entry = line->data;
4687         switch (request) {
4688         case REQ_VIEW_BLAME:
4689                 if (line->type != LINE_TREE_FILE) {
4690                         report("Blame only supported for files");
4691                         return REQ_NONE;
4692                 }
4694                 string_copy(opt_ref, view->vid);
4695                 return request;
4697         case REQ_EDIT:
4698                 if (line->type != LINE_TREE_FILE) {
4699                         report("Edit only supported for files");
4700                 } else if (!is_head_commit(view->vid)) {
4701                         open_blob_editor(entry->id);
4702                 } else {
4703                         open_editor(opt_file);
4704                 }
4705                 return REQ_NONE;
4707         case REQ_TOGGLE_SORT_FIELD:
4708         case REQ_TOGGLE_SORT_ORDER:
4709                 sort_view(view, request, &tree_sort_state, tree_compare);
4710                 return REQ_NONE;
4712         case REQ_PARENT:
4713                 if (!*opt_path) {
4714                         /* quit view if at top of tree */
4715                         return REQ_VIEW_CLOSE;
4716                 }
4717                 /* fake 'cd  ..' */
4718                 line = &view->line[1];
4719                 break;
4721         case REQ_ENTER:
4722                 break;
4724         default:
4725                 return request;
4726         }
4728         /* Cleanup the stack if the tree view is at a different tree. */
4729         while (!*opt_path && tree_stack)
4730                 pop_tree_stack_entry();
4732         switch (line->type) {
4733         case LINE_TREE_DIR:
4734                 /* Depending on whether it is a subdirectory or parent link
4735                  * mangle the path buffer. */
4736                 if (line == &view->line[1] && *opt_path) {
4737                         pop_tree_stack_entry();
4739                 } else {
4740                         const char *basename = tree_path(line);
4742                         push_tree_stack_entry(basename, view->lineno);
4743                 }
4745                 /* Trees and subtrees share the same ID, so they are not not
4746                  * unique like blobs. */
4747                 flags = OPEN_RELOAD;
4748                 request = REQ_VIEW_TREE;
4749                 break;
4751         case LINE_TREE_FILE:
4752                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4753                 request = REQ_VIEW_BLOB;
4754                 break;
4756         default:
4757                 return REQ_NONE;
4758         }
4760         open_view(view, request, flags);
4761         if (request == REQ_VIEW_TREE)
4762                 view->lineno = tree_lineno;
4764         return REQ_NONE;
4767 static bool
4768 tree_grep(struct view *view, struct line *line)
4770         struct tree_entry *entry = line->data;
4771         const char *text[] = {
4772                 entry->name,
4773                 opt_author ? entry->author : "",
4774                 mkdate(&entry->time, opt_date),
4775                 NULL
4776         };
4778         return grep_text(view, text);
4781 static void
4782 tree_select(struct view *view, struct line *line)
4784         struct tree_entry *entry = line->data;
4786         if (line->type == LINE_TREE_FILE) {
4787                 string_copy_rev(ref_blob, entry->id);
4788                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4790         } else if (line->type != LINE_TREE_DIR) {
4791                 return;
4792         }
4794         string_copy_rev(view->ref, entry->id);
4797 static bool
4798 tree_prepare(struct view *view)
4800         if (view->lines == 0 && opt_prefix[0]) {
4801                 char *pos = opt_prefix;
4803                 while (pos && *pos) {
4804                         char *end = strchr(pos, '/');
4806                         if (end)
4807                                 *end = 0;
4808                         push_tree_stack_entry(pos, 0);
4809                         pos = end;
4810                         if (end) {
4811                                 *end = '/';
4812                                 pos++;
4813                         }
4814                 }
4816         } else if (strcmp(view->vid, view->id)) {
4817                 opt_path[0] = 0;
4818         }
4820         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4823 static const char *tree_argv[SIZEOF_ARG] = {
4824         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4825 };
4827 static struct view_ops tree_ops = {
4828         "file",
4829         tree_argv,
4830         NULL,
4831         tree_read,
4832         tree_draw,
4833         tree_request,
4834         tree_grep,
4835         tree_select,
4836         tree_prepare,
4837 };
4839 static bool
4840 blob_read(struct view *view, char *line)
4842         if (!line)
4843                 return TRUE;
4844         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4847 static enum request
4848 blob_request(struct view *view, enum request request, struct line *line)
4850         switch (request) {
4851         case REQ_EDIT:
4852                 open_blob_editor(view->vid);
4853                 return REQ_NONE;
4854         default:
4855                 return pager_request(view, request, line);
4856         }
4859 static const char *blob_argv[SIZEOF_ARG] = {
4860         "git", "cat-file", "blob", "%(blob)", NULL
4861 };
4863 static struct view_ops blob_ops = {
4864         "line",
4865         blob_argv,
4866         NULL,
4867         blob_read,
4868         pager_draw,
4869         blob_request,
4870         pager_grep,
4871         pager_select,
4872 };
4874 /*
4875  * Blame backend
4876  *
4877  * Loading the blame view is a two phase job:
4878  *
4879  *  1. File content is read either using opt_file from the
4880  *     filesystem or using git-cat-file.
4881  *  2. Then blame information is incrementally added by
4882  *     reading output from git-blame.
4883  */
4885 struct blame_commit {
4886         char id[SIZEOF_REV];            /* SHA1 ID. */
4887         char title[128];                /* First line of the commit message. */
4888         const char *author;             /* Author of the commit. */
4889         struct time time;               /* Date from the author ident. */
4890         char filename[128];             /* Name of file. */
4891         bool has_previous;              /* Was a "previous" line detected. */
4892 };
4894 struct blame {
4895         struct blame_commit *commit;
4896         unsigned long lineno;
4897         char text[1];
4898 };
4900 static bool
4901 blame_open(struct view *view)
4903         char path[SIZEOF_STR];
4905         if (!view->prev && *opt_prefix) {
4906                 string_copy(path, opt_file);
4907                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4908                         return FALSE;
4909         }
4911         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4912                 const char *blame_cat_file_argv[] = {
4913                         "git", "cat-file", "blob", path, NULL
4914                 };
4916                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4917                     !io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_NONE))
4918                         return FALSE;
4919         }
4921         setup_update(view, opt_file);
4922         string_format(view->ref, "%s ...", opt_file);
4924         return TRUE;
4927 static struct blame_commit *
4928 get_blame_commit(struct view *view, const char *id)
4930         size_t i;
4932         for (i = 0; i < view->lines; i++) {
4933                 struct blame *blame = view->line[i].data;
4935                 if (!blame->commit)
4936                         continue;
4938                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4939                         return blame->commit;
4940         }
4942         {
4943                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4945                 if (commit)
4946                         string_ncopy(commit->id, id, SIZEOF_REV);
4947                 return commit;
4948         }
4951 static bool
4952 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4954         const char *pos = *posref;
4956         *posref = NULL;
4957         pos = strchr(pos + 1, ' ');
4958         if (!pos || !isdigit(pos[1]))
4959                 return FALSE;
4960         *number = atoi(pos + 1);
4961         if (*number < min || *number > max)
4962                 return FALSE;
4964         *posref = pos;
4965         return TRUE;
4968 static struct blame_commit *
4969 parse_blame_commit(struct view *view, const char *text, int *blamed)
4971         struct blame_commit *commit;
4972         struct blame *blame;
4973         const char *pos = text + SIZEOF_REV - 2;
4974         size_t orig_lineno = 0;
4975         size_t lineno;
4976         size_t group;
4978         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4979                 return NULL;
4981         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4982             !parse_number(&pos, &lineno, 1, view->lines) ||
4983             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4984                 return NULL;
4986         commit = get_blame_commit(view, text);
4987         if (!commit)
4988                 return NULL;
4990         *blamed += group;
4991         while (group--) {
4992                 struct line *line = &view->line[lineno + group - 1];
4994                 blame = line->data;
4995                 blame->commit = commit;
4996                 blame->lineno = orig_lineno + group - 1;
4997                 line->dirty = 1;
4998         }
5000         return commit;
5003 static bool
5004 blame_read_file(struct view *view, const char *line, bool *read_file)
5006         if (!line) {
5007                 const char *blame_argv[] = {
5008                         "git", "blame", "--incremental",
5009                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5010                 };
5011                 struct io io = {};
5013                 if (view->lines == 0 && !view->prev)
5014                         die("No blame exist for %s", view->vid);
5016                 if (view->lines == 0 || !io_run_rd(&io, blame_argv, opt_cdup, FORMAT_NONE)) {
5017                         report("Failed to load blame data");
5018                         return TRUE;
5019                 }
5021                 io_done(view->pipe);
5022                 view->io = io;
5023                 *read_file = FALSE;
5024                 return FALSE;
5026         } else {
5027                 size_t linelen = strlen(line);
5028                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5030                 if (!blame)
5031                         return FALSE;
5033                 blame->commit = NULL;
5034                 strncpy(blame->text, line, linelen);
5035                 blame->text[linelen] = 0;
5036                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5037         }
5040 static bool
5041 match_blame_header(const char *name, char **line)
5043         size_t namelen = strlen(name);
5044         bool matched = !strncmp(name, *line, namelen);
5046         if (matched)
5047                 *line += namelen;
5049         return matched;
5052 static bool
5053 blame_read(struct view *view, char *line)
5055         static struct blame_commit *commit = NULL;
5056         static int blamed = 0;
5057         static bool read_file = TRUE;
5059         if (read_file)
5060                 return blame_read_file(view, line, &read_file);
5062         if (!line) {
5063                 /* Reset all! */
5064                 commit = NULL;
5065                 blamed = 0;
5066                 read_file = TRUE;
5067                 string_format(view->ref, "%s", view->vid);
5068                 if (view_is_displayed(view)) {
5069                         update_view_title(view);
5070                         redraw_view_from(view, 0);
5071                 }
5072                 return TRUE;
5073         }
5075         if (!commit) {
5076                 commit = parse_blame_commit(view, line, &blamed);
5077                 string_format(view->ref, "%s %2d%%", view->vid,
5078                               view->lines ? blamed * 100 / view->lines : 0);
5080         } else if (match_blame_header("author ", &line)) {
5081                 commit->author = get_author(line);
5083         } else if (match_blame_header("author-time ", &line)) {
5084                 parse_timesec(&commit->time, line);
5086         } else if (match_blame_header("author-tz ", &line)) {
5087                 parse_timezone(&commit->time, line);
5089         } else if (match_blame_header("summary ", &line)) {
5090                 string_ncopy(commit->title, line, strlen(line));
5092         } else if (match_blame_header("previous ", &line)) {
5093                 commit->has_previous = TRUE;
5095         } else if (match_blame_header("filename ", &line)) {
5096                 string_ncopy(commit->filename, line, strlen(line));
5097                 commit = NULL;
5098         }
5100         return TRUE;
5103 static bool
5104 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5106         struct blame *blame = line->data;
5107         struct time *time = NULL;
5108         const char *id = NULL, *author = NULL;
5109         char text[SIZEOF_STR];
5111         if (blame->commit && *blame->commit->filename) {
5112                 id = blame->commit->id;
5113                 author = blame->commit->author;
5114                 time = &blame->commit->time;
5115         }
5117         if (opt_date && draw_date(view, time))
5118                 return TRUE;
5120         if (opt_author && draw_author(view, author))
5121                 return TRUE;
5123         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5124                 return TRUE;
5126         if (draw_lineno(view, lineno))
5127                 return TRUE;
5129         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5130         draw_text(view, LINE_DEFAULT, text, TRUE);
5131         return TRUE;
5134 static bool
5135 check_blame_commit(struct blame *blame, bool check_null_id)
5137         if (!blame->commit)
5138                 report("Commit data not loaded yet");
5139         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5140                 report("No commit exist for the selected line");
5141         else
5142                 return TRUE;
5143         return FALSE;
5146 static void
5147 setup_blame_parent_line(struct view *view, struct blame *blame)
5149         const char *diff_tree_argv[] = {
5150                 "git", "diff-tree", "-U0", blame->commit->id,
5151                         "--", blame->commit->filename, NULL
5152         };
5153         struct io io = {};
5154         int parent_lineno = -1;
5155         int blamed_lineno = -1;
5156         char *line;
5158         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5159                 return;
5161         while ((line = io_get(&io, '\n', TRUE))) {
5162                 if (*line == '@') {
5163                         char *pos = strchr(line, '+');
5165                         parent_lineno = atoi(line + 4);
5166                         if (pos)
5167                                 blamed_lineno = atoi(pos + 1);
5169                 } else if (*line == '+' && parent_lineno != -1) {
5170                         if (blame->lineno == blamed_lineno - 1 &&
5171                             !strcmp(blame->text, line + 1)) {
5172                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5173                                 break;
5174                         }
5175                         blamed_lineno++;
5176                 }
5177         }
5179         io_done(&io);
5182 static enum request
5183 blame_request(struct view *view, enum request request, struct line *line)
5185         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5186         struct blame *blame = line->data;
5188         switch (request) {
5189         case REQ_VIEW_BLAME:
5190                 if (check_blame_commit(blame, TRUE)) {
5191                         string_copy(opt_ref, blame->commit->id);
5192                         string_copy(opt_file, blame->commit->filename);
5193                         if (blame->lineno)
5194                                 view->lineno = blame->lineno;
5195                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5196                 }
5197                 break;
5199         case REQ_PARENT:
5200                 if (check_blame_commit(blame, TRUE) &&
5201                     select_commit_parent(blame->commit->id, opt_ref,
5202                                          blame->commit->filename)) {
5203                         string_copy(opt_file, blame->commit->filename);
5204                         setup_blame_parent_line(view, blame);
5205                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5206                 }
5207                 break;
5209         case REQ_ENTER:
5210                 if (!check_blame_commit(blame, FALSE))
5211                         break;
5213                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5214                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5215                         break;
5217                 if (!strcmp(blame->commit->id, NULL_ID)) {
5218                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5219                         const char *diff_index_argv[] = {
5220                                 "git", "diff-index", "--root", "--patch-with-stat",
5221                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5222                         };
5224                         if (!blame->commit->has_previous) {
5225                                 diff_index_argv[1] = "diff";
5226                                 diff_index_argv[2] = "--no-color";
5227                                 diff_index_argv[6] = "--";
5228                                 diff_index_argv[7] = "/dev/null";
5229                         }
5231                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5232                                 report("Failed to allocate diff command");
5233                                 break;
5234                         }
5235                         flags |= OPEN_PREPARED;
5236                 }
5238                 open_view(view, REQ_VIEW_DIFF, flags);
5239                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5240                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5241                 break;
5243         default:
5244                 return request;
5245         }
5247         return REQ_NONE;
5250 static bool
5251 blame_grep(struct view *view, struct line *line)
5253         struct blame *blame = line->data;
5254         struct blame_commit *commit = blame->commit;
5255         const char *text[] = {
5256                 blame->text,
5257                 commit ? commit->title : "",
5258                 commit ? commit->id : "",
5259                 commit && opt_author ? commit->author : "",
5260                 commit ? mkdate(&commit->time, opt_date) : "",
5261                 NULL
5262         };
5264         return grep_text(view, text);
5267 static void
5268 blame_select(struct view *view, struct line *line)
5270         struct blame *blame = line->data;
5271         struct blame_commit *commit = blame->commit;
5273         if (!commit)
5274                 return;
5276         if (!strcmp(commit->id, NULL_ID))
5277                 string_ncopy(ref_commit, "HEAD", 4);
5278         else
5279                 string_copy_rev(ref_commit, commit->id);
5282 static struct view_ops blame_ops = {
5283         "line",
5284         NULL,
5285         blame_open,
5286         blame_read,
5287         blame_draw,
5288         blame_request,
5289         blame_grep,
5290         blame_select,
5291 };
5293 /*
5294  * Branch backend
5295  */
5297 struct branch {
5298         const char *author;             /* Author of the last commit. */
5299         struct time time;               /* Date of the last activity. */
5300         const struct ref *ref;          /* Name and commit ID information. */
5301 };
5303 static const struct ref branch_all;
5305 static const enum sort_field branch_sort_fields[] = {
5306         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5307 };
5308 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5310 static int
5311 branch_compare(const void *l1, const void *l2)
5313         const struct branch *branch1 = ((const struct line *) l1)->data;
5314         const struct branch *branch2 = ((const struct line *) l2)->data;
5316         switch (get_sort_field(branch_sort_state)) {
5317         case ORDERBY_DATE:
5318                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5320         case ORDERBY_AUTHOR:
5321                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5323         case ORDERBY_NAME:
5324         default:
5325                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5326         }
5329 static bool
5330 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5332         struct branch *branch = line->data;
5333         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5335         if (opt_date && draw_date(view, &branch->time))
5336                 return TRUE;
5338         if (opt_author && draw_author(view, branch->author))
5339                 return TRUE;
5341         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5342         return TRUE;
5345 static enum request
5346 branch_request(struct view *view, enum request request, struct line *line)
5348         struct branch *branch = line->data;
5350         switch (request) {
5351         case REQ_REFRESH:
5352                 load_refs();
5353                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5354                 return REQ_NONE;
5356         case REQ_TOGGLE_SORT_FIELD:
5357         case REQ_TOGGLE_SORT_ORDER:
5358                 sort_view(view, request, &branch_sort_state, branch_compare);
5359                 return REQ_NONE;
5361         case REQ_ENTER:
5362                 if (branch->ref == &branch_all) {
5363                         const char *all_branches_argv[] = {
5364                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5365                                       "--topo-order", "--all", NULL
5366                         };
5367                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5369                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5370                                 report("Failed to load view of all branches");
5371                                 return REQ_NONE;
5372                         }
5373                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5374                 } else {
5375                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5376                 }
5377                 return REQ_NONE;
5379         default:
5380                 return request;
5381         }
5384 static bool
5385 branch_read(struct view *view, char *line)
5387         static char id[SIZEOF_REV];
5388         struct branch *reference;
5389         size_t i;
5391         if (!line)
5392                 return TRUE;
5394         switch (get_line_type(line)) {
5395         case LINE_COMMIT:
5396                 string_copy_rev(id, line + STRING_SIZE("commit "));
5397                 return TRUE;
5399         case LINE_AUTHOR:
5400                 for (i = 0, reference = NULL; i < view->lines; i++) {
5401                         struct branch *branch = view->line[i].data;
5403                         if (strcmp(branch->ref->id, id))
5404                                 continue;
5406                         view->line[i].dirty = TRUE;
5407                         if (reference) {
5408                                 branch->author = reference->author;
5409                                 branch->time = reference->time;
5410                                 continue;
5411                         }
5413                         parse_author_line(line + STRING_SIZE("author "),
5414                                           &branch->author, &branch->time);
5415                         reference = branch;
5416                 }
5417                 return TRUE;
5419         default:
5420                 return TRUE;
5421         }
5425 static bool
5426 branch_open_visitor(void *data, const struct ref *ref)
5428         struct view *view = data;
5429         struct branch *branch;
5431         if (ref->tag || ref->ltag || ref->remote)
5432                 return TRUE;
5434         branch = calloc(1, sizeof(*branch));
5435         if (!branch)
5436                 return FALSE;
5438         branch->ref = ref;
5439         return !!add_line_data(view, branch, LINE_DEFAULT);
5442 static bool
5443 branch_open(struct view *view)
5445         const char *branch_log[] = {
5446                 "git", "log", "--no-color", "--pretty=raw",
5447                         "--simplify-by-decoration", "--all", NULL
5448         };
5450         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5451                 report("Failed to load branch data");
5452                 return TRUE;
5453         }
5455         setup_update(view, view->id);
5456         branch_open_visitor(view, &branch_all);
5457         foreach_ref(branch_open_visitor, view);
5458         view->p_restore = TRUE;
5460         return TRUE;
5463 static bool
5464 branch_grep(struct view *view, struct line *line)
5466         struct branch *branch = line->data;
5467         const char *text[] = {
5468                 branch->ref->name,
5469                 branch->author,
5470                 NULL
5471         };
5473         return grep_text(view, text);
5476 static void
5477 branch_select(struct view *view, struct line *line)
5479         struct branch *branch = line->data;
5481         string_copy_rev(view->ref, branch->ref->id);
5482         string_copy_rev(ref_commit, branch->ref->id);
5483         string_copy_rev(ref_head, branch->ref->id);
5484         string_copy_rev(ref_branch, branch->ref->name);
5487 static struct view_ops branch_ops = {
5488         "branch",
5489         NULL,
5490         branch_open,
5491         branch_read,
5492         branch_draw,
5493         branch_request,
5494         branch_grep,
5495         branch_select,
5496 };
5498 /*
5499  * Status backend
5500  */
5502 struct status {
5503         char status;
5504         struct {
5505                 mode_t mode;
5506                 char rev[SIZEOF_REV];
5507                 char name[SIZEOF_STR];
5508         } old;
5509         struct {
5510                 mode_t mode;
5511                 char rev[SIZEOF_REV];
5512                 char name[SIZEOF_STR];
5513         } new;
5514 };
5516 static char status_onbranch[SIZEOF_STR];
5517 static struct status stage_status;
5518 static enum line_type stage_line_type;
5519 static size_t stage_chunks;
5520 static int *stage_chunk;
5522 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5524 /* This should work even for the "On branch" line. */
5525 static inline bool
5526 status_has_none(struct view *view, struct line *line)
5528         return line < view->line + view->lines && !line[1].data;
5531 /* Get fields from the diff line:
5532  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5533  */
5534 static inline bool
5535 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5537         const char *old_mode = buf +  1;
5538         const char *new_mode = buf +  8;
5539         const char *old_rev  = buf + 15;
5540         const char *new_rev  = buf + 56;
5541         const char *status   = buf + 97;
5543         if (bufsize < 98 ||
5544             old_mode[-1] != ':' ||
5545             new_mode[-1] != ' ' ||
5546             old_rev[-1]  != ' ' ||
5547             new_rev[-1]  != ' ' ||
5548             status[-1]   != ' ')
5549                 return FALSE;
5551         file->status = *status;
5553         string_copy_rev(file->old.rev, old_rev);
5554         string_copy_rev(file->new.rev, new_rev);
5556         file->old.mode = strtoul(old_mode, NULL, 8);
5557         file->new.mode = strtoul(new_mode, NULL, 8);
5559         file->old.name[0] = file->new.name[0] = 0;
5561         return TRUE;
5564 static bool
5565 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5567         struct status *unmerged = NULL;
5568         char *buf;
5569         struct io io = {};
5571         if (!io_run(&io, argv, opt_cdup, IO_RD))
5572                 return FALSE;
5574         add_line_data(view, NULL, type);
5576         while ((buf = io_get(&io, 0, TRUE))) {
5577                 struct status *file = unmerged;
5579                 if (!file) {
5580                         file = calloc(1, sizeof(*file));
5581                         if (!file || !add_line_data(view, file, type))
5582                                 goto error_out;
5583                 }
5585                 /* Parse diff info part. */
5586                 if (status) {
5587                         file->status = status;
5588                         if (status == 'A')
5589                                 string_copy(file->old.rev, NULL_ID);
5591                 } else if (!file->status || file == unmerged) {
5592                         if (!status_get_diff(file, buf, strlen(buf)))
5593                                 goto error_out;
5595                         buf = io_get(&io, 0, TRUE);
5596                         if (!buf)
5597                                 break;
5599                         /* Collapse all modified entries that follow an
5600                          * associated unmerged entry. */
5601                         if (unmerged == file) {
5602                                 unmerged->status = 'U';
5603                                 unmerged = NULL;
5604                         } else if (file->status == 'U') {
5605                                 unmerged = file;
5606                         }
5607                 }
5609                 /* Grab the old name for rename/copy. */
5610                 if (!*file->old.name &&
5611                     (file->status == 'R' || file->status == 'C')) {
5612                         string_ncopy(file->old.name, buf, strlen(buf));
5614                         buf = io_get(&io, 0, TRUE);
5615                         if (!buf)
5616                                 break;
5617                 }
5619                 /* git-ls-files just delivers a NUL separated list of
5620                  * file names similar to the second half of the
5621                  * git-diff-* output. */
5622                 string_ncopy(file->new.name, buf, strlen(buf));
5623                 if (!*file->old.name)
5624                         string_copy(file->old.name, file->new.name);
5625                 file = NULL;
5626         }
5628         if (io_error(&io)) {
5629 error_out:
5630                 io_done(&io);
5631                 return FALSE;
5632         }
5634         if (!view->line[view->lines - 1].data)
5635                 add_line_data(view, NULL, LINE_STAT_NONE);
5637         io_done(&io);
5638         return TRUE;
5641 /* Don't show unmerged entries in the staged section. */
5642 static const char *status_diff_index_argv[] = {
5643         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5644                              "--cached", "-M", "HEAD", NULL
5645 };
5647 static const char *status_diff_files_argv[] = {
5648         "git", "diff-files", "-z", NULL
5649 };
5651 static const char *status_list_other_argv[] = {
5652         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5653 };
5655 static const char *status_list_no_head_argv[] = {
5656         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5657 };
5659 static const char *update_index_argv[] = {
5660         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5661 };
5663 /* Restore the previous line number to stay in the context or select a
5664  * line with something that can be updated. */
5665 static void
5666 status_restore(struct view *view)
5668         if (view->p_lineno >= view->lines)
5669                 view->p_lineno = view->lines - 1;
5670         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5671                 view->p_lineno++;
5672         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5673                 view->p_lineno--;
5675         /* If the above fails, always skip the "On branch" line. */
5676         if (view->p_lineno < view->lines)
5677                 view->lineno = view->p_lineno;
5678         else
5679                 view->lineno = 1;
5681         if (view->lineno < view->offset)
5682                 view->offset = view->lineno;
5683         else if (view->offset + view->height <= view->lineno)
5684                 view->offset = view->lineno - view->height + 1;
5686         view->p_restore = FALSE;
5689 static void
5690 status_update_onbranch(void)
5692         static const char *paths[][2] = {
5693                 { "rebase-apply/rebasing",      "Rebasing" },
5694                 { "rebase-apply/applying",      "Applying mailbox" },
5695                 { "rebase-apply/",              "Rebasing mailbox" },
5696                 { "rebase-merge/interactive",   "Interactive rebase" },
5697                 { "rebase-merge/",              "Rebase merge" },
5698                 { "MERGE_HEAD",                 "Merging" },
5699                 { "BISECT_LOG",                 "Bisecting" },
5700                 { "HEAD",                       "On branch" },
5701         };
5702         char buf[SIZEOF_STR];
5703         struct stat stat;
5704         int i;
5706         if (is_initial_commit()) {
5707                 string_copy(status_onbranch, "Initial commit");
5708                 return;
5709         }
5711         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5712                 char *head = opt_head;
5714                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5715                     lstat(buf, &stat) < 0)
5716                         continue;
5718                 if (!*opt_head) {
5719                         struct io io = {};
5721                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5722                             io_read_buf(&io, buf, sizeof(buf))) {
5723                                 head = buf;
5724                                 if (!prefixcmp(head, "refs/heads/"))
5725                                         head += STRING_SIZE("refs/heads/");
5726                         }
5727                 }
5729                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5730                         string_copy(status_onbranch, opt_head);
5731                 return;
5732         }
5734         string_copy(status_onbranch, "Not currently on any branch");
5737 /* First parse staged info using git-diff-index(1), then parse unstaged
5738  * info using git-diff-files(1), and finally untracked files using
5739  * git-ls-files(1). */
5740 static bool
5741 status_open(struct view *view)
5743         reset_view(view);
5745         add_line_data(view, NULL, LINE_STAT_HEAD);
5746         status_update_onbranch();
5748         io_run_bg(update_index_argv);
5750         if (is_initial_commit()) {
5751                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5752                         return FALSE;
5753         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5754                 return FALSE;
5755         }
5757         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5758             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5759                 return FALSE;
5761         /* Restore the exact position or use the specialized restore
5762          * mode? */
5763         if (!view->p_restore)
5764                 status_restore(view);
5765         return TRUE;
5768 static bool
5769 status_draw(struct view *view, struct line *line, unsigned int lineno)
5771         struct status *status = line->data;
5772         enum line_type type;
5773         const char *text;
5775         if (!status) {
5776                 switch (line->type) {
5777                 case LINE_STAT_STAGED:
5778                         type = LINE_STAT_SECTION;
5779                         text = "Changes to be committed:";
5780                         break;
5782                 case LINE_STAT_UNSTAGED:
5783                         type = LINE_STAT_SECTION;
5784                         text = "Changed but not updated:";
5785                         break;
5787                 case LINE_STAT_UNTRACKED:
5788                         type = LINE_STAT_SECTION;
5789                         text = "Untracked files:";
5790                         break;
5792                 case LINE_STAT_NONE:
5793                         type = LINE_DEFAULT;
5794                         text = "  (no files)";
5795                         break;
5797                 case LINE_STAT_HEAD:
5798                         type = LINE_STAT_HEAD;
5799                         text = status_onbranch;
5800                         break;
5802                 default:
5803                         return FALSE;
5804                 }
5805         } else {
5806                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5808                 buf[0] = status->status;
5809                 if (draw_text(view, line->type, buf, TRUE))
5810                         return TRUE;
5811                 type = LINE_DEFAULT;
5812                 text = status->new.name;
5813         }
5815         draw_text(view, type, text, TRUE);
5816         return TRUE;
5819 static enum request
5820 status_load_error(struct view *view, struct view *stage, const char *path)
5822         if (displayed_views() == 2 || display[current_view] != view)
5823                 maximize_view(view);
5824         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5825         return REQ_NONE;
5828 static enum request
5829 status_enter(struct view *view, struct line *line)
5831         struct status *status = line->data;
5832         const char *oldpath = status ? status->old.name : NULL;
5833         /* Diffs for unmerged entries are empty when passing the new
5834          * path, so leave it empty. */
5835         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5836         const char *info;
5837         enum open_flags split;
5838         struct view *stage = VIEW(REQ_VIEW_STAGE);
5840         if (line->type == LINE_STAT_NONE ||
5841             (!status && line[1].type == LINE_STAT_NONE)) {
5842                 report("No file to diff");
5843                 return REQ_NONE;
5844         }
5846         switch (line->type) {
5847         case LINE_STAT_STAGED:
5848                 if (is_initial_commit()) {
5849                         const char *no_head_diff_argv[] = {
5850                                 "git", "diff", "--no-color", "--patch-with-stat",
5851                                         "--", "/dev/null", newpath, NULL
5852                         };
5854                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5855                                 return status_load_error(view, stage, newpath);
5856                 } else {
5857                         const char *index_show_argv[] = {
5858                                 "git", "diff-index", "--root", "--patch-with-stat",
5859                                         "-C", "-M", "--cached", "HEAD", "--",
5860                                         oldpath, newpath, NULL
5861                         };
5863                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5864                                 return status_load_error(view, stage, newpath);
5865                 }
5867                 if (status)
5868                         info = "Staged changes to %s";
5869                 else
5870                         info = "Staged changes";
5871                 break;
5873         case LINE_STAT_UNSTAGED:
5874         {
5875                 const char *files_show_argv[] = {
5876                         "git", "diff-files", "--root", "--patch-with-stat",
5877                                 "-C", "-M", "--", oldpath, newpath, NULL
5878                 };
5880                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5881                         return status_load_error(view, stage, newpath);
5882                 if (status)
5883                         info = "Unstaged changes to %s";
5884                 else
5885                         info = "Unstaged changes";
5886                 break;
5887         }
5888         case LINE_STAT_UNTRACKED:
5889                 if (!newpath) {
5890                         report("No file to show");
5891                         return REQ_NONE;
5892                 }
5894                 if (!suffixcmp(status->new.name, -1, "/")) {
5895                         report("Cannot display a directory");
5896                         return REQ_NONE;
5897                 }
5899                 if (!prepare_update_file(stage, newpath))
5900                         return status_load_error(view, stage, newpath);
5901                 info = "Untracked file %s";
5902                 break;
5904         case LINE_STAT_HEAD:
5905                 return REQ_NONE;
5907         default:
5908                 die("line type %d not handled in switch", line->type);
5909         }
5911         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5912         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5913         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5914                 if (status) {
5915                         stage_status = *status;
5916                 } else {
5917                         memset(&stage_status, 0, sizeof(stage_status));
5918                 }
5920                 stage_line_type = line->type;
5921                 stage_chunks = 0;
5922                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5923         }
5925         return REQ_NONE;
5928 static bool
5929 status_exists(struct status *status, enum line_type type)
5931         struct view *view = VIEW(REQ_VIEW_STATUS);
5932         unsigned long lineno;
5934         for (lineno = 0; lineno < view->lines; lineno++) {
5935                 struct line *line = &view->line[lineno];
5936                 struct status *pos = line->data;
5938                 if (line->type != type)
5939                         continue;
5940                 if (!pos && (!status || !status->status) && line[1].data) {
5941                         select_view_line(view, lineno);
5942                         return TRUE;
5943                 }
5944                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5945                         select_view_line(view, lineno);
5946                         return TRUE;
5947                 }
5948         }
5950         return FALSE;
5954 static bool
5955 status_update_prepare(struct io *io, enum line_type type)
5957         const char *staged_argv[] = {
5958                 "git", "update-index", "-z", "--index-info", NULL
5959         };
5960         const char *others_argv[] = {
5961                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5962         };
5964         switch (type) {
5965         case LINE_STAT_STAGED:
5966                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5968         case LINE_STAT_UNSTAGED:
5969         case LINE_STAT_UNTRACKED:
5970                 return io_run(io, others_argv, opt_cdup, IO_WR);
5972         default:
5973                 die("line type %d not handled in switch", type);
5974                 return FALSE;
5975         }
5978 static bool
5979 status_update_write(struct io *io, struct status *status, enum line_type type)
5981         char buf[SIZEOF_STR];
5982         size_t bufsize = 0;
5984         switch (type) {
5985         case LINE_STAT_STAGED:
5986                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5987                                         status->old.mode,
5988                                         status->old.rev,
5989                                         status->old.name, 0))
5990                         return FALSE;
5991                 break;
5993         case LINE_STAT_UNSTAGED:
5994         case LINE_STAT_UNTRACKED:
5995                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5996                         return FALSE;
5997                 break;
5999         default:
6000                 die("line type %d not handled in switch", type);
6001         }
6003         return io_write(io, buf, bufsize);
6006 static bool
6007 status_update_file(struct status *status, enum line_type type)
6009         struct io io = {};
6010         bool result;
6012         if (!status_update_prepare(&io, type))
6013                 return FALSE;
6015         result = status_update_write(&io, status, type);
6016         return io_done(&io) && result;
6019 static bool
6020 status_update_files(struct view *view, struct line *line)
6022         char buf[sizeof(view->ref)];
6023         struct io io = {};
6024         bool result = TRUE;
6025         struct line *pos = view->line + view->lines;
6026         int files = 0;
6027         int file, done;
6028         int cursor_y = -1, cursor_x = -1;
6030         if (!status_update_prepare(&io, line->type))
6031                 return FALSE;
6033         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6034                 files++;
6036         string_copy(buf, view->ref);
6037         getsyx(cursor_y, cursor_x);
6038         for (file = 0, done = 5; result && file < files; line++, file++) {
6039                 int almost_done = file * 100 / files;
6041                 if (almost_done > done) {
6042                         done = almost_done;
6043                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6044                                       file, files, done);
6045                         update_view_title(view);
6046                         setsyx(cursor_y, cursor_x);
6047                         doupdate();
6048                 }
6049                 result = status_update_write(&io, line->data, line->type);
6050         }
6051         string_copy(view->ref, buf);
6053         return io_done(&io) && result;
6056 static bool
6057 status_update(struct view *view)
6059         struct line *line = &view->line[view->lineno];
6061         assert(view->lines);
6063         if (!line->data) {
6064                 /* This should work even for the "On branch" line. */
6065                 if (line < view->line + view->lines && !line[1].data) {
6066                         report("Nothing to update");
6067                         return FALSE;
6068                 }
6070                 if (!status_update_files(view, line + 1)) {
6071                         report("Failed to update file status");
6072                         return FALSE;
6073                 }
6075         } else if (!status_update_file(line->data, line->type)) {
6076                 report("Failed to update file status");
6077                 return FALSE;
6078         }
6080         return TRUE;
6083 static bool
6084 status_revert(struct status *status, enum line_type type, bool has_none)
6086         if (!status || type != LINE_STAT_UNSTAGED) {
6087                 if (type == LINE_STAT_STAGED) {
6088                         report("Cannot revert changes to staged files");
6089                 } else if (type == LINE_STAT_UNTRACKED) {
6090                         report("Cannot revert changes to untracked files");
6091                 } else if (has_none) {
6092                         report("Nothing to revert");
6093                 } else {
6094                         report("Cannot revert changes to multiple files");
6095                 }
6097         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6098                 char mode[10] = "100644";
6099                 const char *reset_argv[] = {
6100                         "git", "update-index", "--cacheinfo", mode,
6101                                 status->old.rev, status->old.name, NULL
6102                 };
6103                 const char *checkout_argv[] = {
6104                         "git", "checkout", "--", status->old.name, NULL
6105                 };
6107                 if (status->status == 'U') {
6108                         string_format(mode, "%5o", status->old.mode);
6110                         if (status->old.mode == 0 && status->new.mode == 0) {
6111                                 reset_argv[2] = "--force-remove";
6112                                 reset_argv[3] = status->old.name;
6113                                 reset_argv[4] = NULL;
6114                         }
6116                         if (!io_run_fg(reset_argv, opt_cdup))
6117                                 return FALSE;
6118                         if (status->old.mode == 0 && status->new.mode == 0)
6119                                 return TRUE;
6120                 }
6122                 return io_run_fg(checkout_argv, opt_cdup);
6123         }
6125         return FALSE;
6128 static enum request
6129 status_request(struct view *view, enum request request, struct line *line)
6131         struct status *status = line->data;
6133         switch (request) {
6134         case REQ_STATUS_UPDATE:
6135                 if (!status_update(view))
6136                         return REQ_NONE;
6137                 break;
6139         case REQ_STATUS_REVERT:
6140                 if (!status_revert(status, line->type, status_has_none(view, line)))
6141                         return REQ_NONE;
6142                 break;
6144         case REQ_STATUS_MERGE:
6145                 if (!status || status->status != 'U') {
6146                         report("Merging only possible for files with unmerged status ('U').");
6147                         return REQ_NONE;
6148                 }
6149                 open_mergetool(status->new.name);
6150                 break;
6152         case REQ_EDIT:
6153                 if (!status)
6154                         return request;
6155                 if (status->status == 'D') {
6156                         report("File has been deleted.");
6157                         return REQ_NONE;
6158                 }
6160                 open_editor(status->new.name);
6161                 break;
6163         case REQ_VIEW_BLAME:
6164                 if (status)
6165                         opt_ref[0] = 0;
6166                 return request;
6168         case REQ_ENTER:
6169                 /* After returning the status view has been split to
6170                  * show the stage view. No further reloading is
6171                  * necessary. */
6172                 return status_enter(view, line);
6174         case REQ_REFRESH:
6175                 /* Simply reload the view. */
6176                 break;
6178         default:
6179                 return request;
6180         }
6182         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6184         return REQ_NONE;
6187 static void
6188 status_select(struct view *view, struct line *line)
6190         struct status *status = line->data;
6191         char file[SIZEOF_STR] = "all files";
6192         const char *text;
6193         const char *key;
6195         if (status && !string_format(file, "'%s'", status->new.name))
6196                 return;
6198         if (!status && line[1].type == LINE_STAT_NONE)
6199                 line++;
6201         switch (line->type) {
6202         case LINE_STAT_STAGED:
6203                 text = "Press %s to unstage %s for commit";
6204                 break;
6206         case LINE_STAT_UNSTAGED:
6207                 text = "Press %s to stage %s for commit";
6208                 break;
6210         case LINE_STAT_UNTRACKED:
6211                 text = "Press %s to stage %s for addition";
6212                 break;
6214         case LINE_STAT_HEAD:
6215         case LINE_STAT_NONE:
6216                 text = "Nothing to update";
6217                 break;
6219         default:
6220                 die("line type %d not handled in switch", line->type);
6221         }
6223         if (status && status->status == 'U') {
6224                 text = "Press %s to resolve conflict in %s";
6225                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6227         } else {
6228                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6229         }
6231         string_format(view->ref, text, key, file);
6232         if (status)
6233                 string_copy(opt_file, status->new.name);
6236 static bool
6237 status_grep(struct view *view, struct line *line)
6239         struct status *status = line->data;
6241         if (status) {
6242                 const char buf[2] = { status->status, 0 };
6243                 const char *text[] = { status->new.name, buf, NULL };
6245                 return grep_text(view, text);
6246         }
6248         return FALSE;
6251 static struct view_ops status_ops = {
6252         "file",
6253         NULL,
6254         status_open,
6255         NULL,
6256         status_draw,
6257         status_request,
6258         status_grep,
6259         status_select,
6260 };
6263 static bool
6264 stage_diff_write(struct io *io, struct line *line, struct line *end)
6266         while (line < end) {
6267                 if (!io_write(io, line->data, strlen(line->data)) ||
6268                     !io_write(io, "\n", 1))
6269                         return FALSE;
6270                 line++;
6271                 if (line->type == LINE_DIFF_CHUNK ||
6272                     line->type == LINE_DIFF_HEADER)
6273                         break;
6274         }
6276         return TRUE;
6279 static struct line *
6280 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6282         for (; view->line < line; line--)
6283                 if (line->type == type)
6284                         return line;
6286         return NULL;
6289 static bool
6290 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6292         const char *apply_argv[SIZEOF_ARG] = {
6293                 "git", "apply", "--whitespace=nowarn", NULL
6294         };
6295         struct line *diff_hdr;
6296         struct io io = {};
6297         int argc = 3;
6299         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6300         if (!diff_hdr)
6301                 return FALSE;
6303         if (!revert)
6304                 apply_argv[argc++] = "--cached";
6305         if (revert || stage_line_type == LINE_STAT_STAGED)
6306                 apply_argv[argc++] = "-R";
6307         apply_argv[argc++] = "-";
6308         apply_argv[argc++] = NULL;
6309         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6310                 return FALSE;
6312         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6313             !stage_diff_write(&io, chunk, view->line + view->lines))
6314                 chunk = NULL;
6316         io_done(&io);
6317         io_run_bg(update_index_argv);
6319         return chunk ? TRUE : FALSE;
6322 static bool
6323 stage_update(struct view *view, struct line *line)
6325         struct line *chunk = NULL;
6327         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6328                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6330         if (chunk) {
6331                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6332                         report("Failed to apply chunk");
6333                         return FALSE;
6334                 }
6336         } else if (!stage_status.status) {
6337                 view = VIEW(REQ_VIEW_STATUS);
6339                 for (line = view->line; line < view->line + view->lines; line++)
6340                         if (line->type == stage_line_type)
6341                                 break;
6343                 if (!status_update_files(view, line + 1)) {
6344                         report("Failed to update files");
6345                         return FALSE;
6346                 }
6348         } else if (!status_update_file(&stage_status, stage_line_type)) {
6349                 report("Failed to update file");
6350                 return FALSE;
6351         }
6353         return TRUE;
6356 static bool
6357 stage_revert(struct view *view, struct line *line)
6359         struct line *chunk = NULL;
6361         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6362                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6364         if (chunk) {
6365                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6366                         return FALSE;
6368                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6369                         report("Failed to revert chunk");
6370                         return FALSE;
6371                 }
6372                 return TRUE;
6374         } else {
6375                 return status_revert(stage_status.status ? &stage_status : NULL,
6376                                      stage_line_type, FALSE);
6377         }
6381 static void
6382 stage_next(struct view *view, struct line *line)
6384         int i;
6386         if (!stage_chunks) {
6387                 for (line = view->line; line < view->line + view->lines; line++) {
6388                         if (line->type != LINE_DIFF_CHUNK)
6389                                 continue;
6391                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6392                                 report("Allocation failure");
6393                                 return;
6394                         }
6396                         stage_chunk[stage_chunks++] = line - view->line;
6397                 }
6398         }
6400         for (i = 0; i < stage_chunks; i++) {
6401                 if (stage_chunk[i] > view->lineno) {
6402                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6403                         report("Chunk %d of %d", i + 1, stage_chunks);
6404                         return;
6405                 }
6406         }
6408         report("No next chunk found");
6411 static enum request
6412 stage_request(struct view *view, enum request request, struct line *line)
6414         switch (request) {
6415         case REQ_STATUS_UPDATE:
6416                 if (!stage_update(view, line))
6417                         return REQ_NONE;
6418                 break;
6420         case REQ_STATUS_REVERT:
6421                 if (!stage_revert(view, line))
6422                         return REQ_NONE;
6423                 break;
6425         case REQ_STAGE_NEXT:
6426                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6427                         report("File is untracked; press %s to add",
6428                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6429                         return REQ_NONE;
6430                 }
6431                 stage_next(view, line);
6432                 return REQ_NONE;
6434         case REQ_EDIT:
6435                 if (!stage_status.new.name[0])
6436                         return request;
6437                 if (stage_status.status == 'D') {
6438                         report("File has been deleted.");
6439                         return REQ_NONE;
6440                 }
6442                 open_editor(stage_status.new.name);
6443                 break;
6445         case REQ_REFRESH:
6446                 /* Reload everything ... */
6447                 break;
6449         case REQ_VIEW_BLAME:
6450                 if (stage_status.new.name[0]) {
6451                         string_copy(opt_file, stage_status.new.name);
6452                         opt_ref[0] = 0;
6453                 }
6454                 return request;
6456         case REQ_ENTER:
6457                 return pager_request(view, request, line);
6459         default:
6460                 return request;
6461         }
6463         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6464         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6466         /* Check whether the staged entry still exists, and close the
6467          * stage view if it doesn't. */
6468         if (!status_exists(&stage_status, stage_line_type)) {
6469                 status_restore(VIEW(REQ_VIEW_STATUS));
6470                 return REQ_VIEW_CLOSE;
6471         }
6473         if (stage_line_type == LINE_STAT_UNTRACKED) {
6474                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6475                         report("Cannot display a directory");
6476                         return REQ_NONE;
6477                 }
6479                 if (!prepare_update_file(view, stage_status.new.name)) {
6480                         report("Failed to open file: %s", strerror(errno));
6481                         return REQ_NONE;
6482                 }
6483         }
6484         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6486         return REQ_NONE;
6489 static struct view_ops stage_ops = {
6490         "line",
6491         NULL,
6492         NULL,
6493         pager_read,
6494         pager_draw,
6495         stage_request,
6496         pager_grep,
6497         pager_select,
6498 };
6501 /*
6502  * Revision graph
6503  */
6505 struct commit {
6506         char id[SIZEOF_REV];            /* SHA1 ID. */
6507         char title[128];                /* First line of the commit message. */
6508         const char *author;             /* Author of the commit. */
6509         struct time time;               /* Date from the author ident. */
6510         struct ref_list *refs;          /* Repository references. */
6511         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6512         size_t graph_size;              /* The width of the graph array. */
6513         bool has_parents;               /* Rewritten --parents seen. */
6514 };
6516 /* Size of rev graph with no  "padding" columns */
6517 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6519 struct rev_graph {
6520         struct rev_graph *prev, *next, *parents;
6521         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6522         size_t size;
6523         struct commit *commit;
6524         size_t pos;
6525         unsigned int boundary:1;
6526 };
6528 /* Parents of the commit being visualized. */
6529 static struct rev_graph graph_parents[4];
6531 /* The current stack of revisions on the graph. */
6532 static struct rev_graph graph_stacks[4] = {
6533         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6534         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6535         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6536         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6537 };
6539 static inline bool
6540 graph_parent_is_merge(struct rev_graph *graph)
6542         return graph->parents->size > 1;
6545 static inline void
6546 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6548         struct commit *commit = graph->commit;
6550         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6551                 commit->graph[commit->graph_size++] = symbol;
6554 static void
6555 clear_rev_graph(struct rev_graph *graph)
6557         graph->boundary = 0;
6558         graph->size = graph->pos = 0;
6559         graph->commit = NULL;
6560         memset(graph->parents, 0, sizeof(*graph->parents));
6563 static void
6564 done_rev_graph(struct rev_graph *graph)
6566         if (graph_parent_is_merge(graph) &&
6567             graph->pos < graph->size - 1 &&
6568             graph->next->size == graph->size + graph->parents->size - 1) {
6569                 size_t i = graph->pos + graph->parents->size - 1;
6571                 graph->commit->graph_size = i * 2;
6572                 while (i < graph->next->size - 1) {
6573                         append_to_rev_graph(graph, ' ');
6574                         append_to_rev_graph(graph, '\\');
6575                         i++;
6576                 }
6577         }
6579         clear_rev_graph(graph);
6582 static void
6583 push_rev_graph(struct rev_graph *graph, const char *parent)
6585         int i;
6587         /* "Collapse" duplicate parents lines.
6588          *
6589          * FIXME: This needs to also update update the drawn graph but
6590          * for now it just serves as a method for pruning graph lines. */
6591         for (i = 0; i < graph->size; i++)
6592                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6593                         return;
6595         if (graph->size < SIZEOF_REVITEMS) {
6596                 string_copy_rev(graph->rev[graph->size++], parent);
6597         }
6600 static chtype
6601 get_rev_graph_symbol(struct rev_graph *graph)
6603         chtype symbol;
6605         if (graph->boundary)
6606                 symbol = REVGRAPH_BOUND;
6607         else if (graph->parents->size == 0)
6608                 symbol = REVGRAPH_INIT;
6609         else if (graph_parent_is_merge(graph))
6610                 symbol = REVGRAPH_MERGE;
6611         else if (graph->pos >= graph->size)
6612                 symbol = REVGRAPH_BRANCH;
6613         else
6614                 symbol = REVGRAPH_COMMIT;
6616         return symbol;
6619 static void
6620 draw_rev_graph(struct rev_graph *graph)
6622         struct rev_filler {
6623                 chtype separator, line;
6624         };
6625         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6626         static struct rev_filler fillers[] = {
6627                 { ' ',  '|' },
6628                 { '`',  '.' },
6629                 { '\'', ' ' },
6630                 { '/',  ' ' },
6631         };
6632         chtype symbol = get_rev_graph_symbol(graph);
6633         struct rev_filler *filler;
6634         size_t i;
6636         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6637         filler = &fillers[DEFAULT];
6639         for (i = 0; i < graph->pos; i++) {
6640                 append_to_rev_graph(graph, filler->line);
6641                 if (graph_parent_is_merge(graph->prev) &&
6642                     graph->prev->pos == i)
6643                         filler = &fillers[RSHARP];
6645                 append_to_rev_graph(graph, filler->separator);
6646         }
6648         /* Place the symbol for this revision. */
6649         append_to_rev_graph(graph, symbol);
6651         if (graph->prev->size > graph->size)
6652                 filler = &fillers[RDIAG];
6653         else
6654                 filler = &fillers[DEFAULT];
6656         i++;
6658         for (; i < graph->size; i++) {
6659                 append_to_rev_graph(graph, filler->separator);
6660                 append_to_rev_graph(graph, filler->line);
6661                 if (graph_parent_is_merge(graph->prev) &&
6662                     i < graph->prev->pos + graph->parents->size)
6663                         filler = &fillers[RSHARP];
6664                 if (graph->prev->size > graph->size)
6665                         filler = &fillers[LDIAG];
6666         }
6668         if (graph->prev->size > graph->size) {
6669                 append_to_rev_graph(graph, filler->separator);
6670                 if (filler->line != ' ')
6671                         append_to_rev_graph(graph, filler->line);
6672         }
6675 /* Prepare the next rev graph */
6676 static void
6677 prepare_rev_graph(struct rev_graph *graph)
6679         size_t i;
6681         /* First, traverse all lines of revisions up to the active one. */
6682         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6683                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6684                         break;
6686                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6687         }
6689         /* Interleave the new revision parent(s). */
6690         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6691                 push_rev_graph(graph->next, graph->parents->rev[i]);
6693         /* Lastly, put any remaining revisions. */
6694         for (i = graph->pos + 1; i < graph->size; i++)
6695                 push_rev_graph(graph->next, graph->rev[i]);
6698 static void
6699 update_rev_graph(struct view *view, struct rev_graph *graph)
6701         /* If this is the finalizing update ... */
6702         if (graph->commit)
6703                 prepare_rev_graph(graph);
6705         /* Graph visualization needs a one rev look-ahead,
6706          * so the first update doesn't visualize anything. */
6707         if (!graph->prev->commit)
6708                 return;
6710         if (view->lines > 2)
6711                 view->line[view->lines - 3].dirty = 1;
6712         if (view->lines > 1)
6713                 view->line[view->lines - 2].dirty = 1;
6714         draw_rev_graph(graph->prev);
6715         done_rev_graph(graph->prev->prev);
6719 /*
6720  * Main view backend
6721  */
6723 static const char *main_argv[SIZEOF_ARG] = {
6724         "git", "log", "--no-color", "--pretty=raw", "--parents",
6725                       "--topo-order", "%(head)", NULL
6726 };
6728 static bool
6729 main_draw(struct view *view, struct line *line, unsigned int lineno)
6731         struct commit *commit = line->data;
6733         if (!commit->author)
6734                 return FALSE;
6736         if (opt_date && draw_date(view, &commit->time))
6737                 return TRUE;
6739         if (opt_author && draw_author(view, commit->author))
6740                 return TRUE;
6742         if (opt_rev_graph && commit->graph_size &&
6743             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6744                 return TRUE;
6746         if (opt_show_refs && commit->refs) {
6747                 size_t i;
6749                 for (i = 0; i < commit->refs->size; i++) {
6750                         struct ref *ref = commit->refs->refs[i];
6751                         enum line_type type;
6753                         if (ref->head)
6754                                 type = LINE_MAIN_HEAD;
6755                         else if (ref->ltag)
6756                                 type = LINE_MAIN_LOCAL_TAG;
6757                         else if (ref->tag)
6758                                 type = LINE_MAIN_TAG;
6759                         else if (ref->tracked)
6760                                 type = LINE_MAIN_TRACKED;
6761                         else if (ref->remote)
6762                                 type = LINE_MAIN_REMOTE;
6763                         else
6764                                 type = LINE_MAIN_REF;
6766                         if (draw_text(view, type, "[", TRUE) ||
6767                             draw_text(view, type, ref->name, TRUE) ||
6768                             draw_text(view, type, "]", TRUE))
6769                                 return TRUE;
6771                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6772                                 return TRUE;
6773                 }
6774         }
6776         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6777         return TRUE;
6780 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6781 static bool
6782 main_read(struct view *view, char *line)
6784         static struct rev_graph *graph = graph_stacks;
6785         enum line_type type;
6786         struct commit *commit;
6788         if (!line) {
6789                 int i;
6791                 if (!view->lines && !view->prev)
6792                         die("No revisions match the given arguments.");
6793                 if (view->lines > 0) {
6794                         commit = view->line[view->lines - 1].data;
6795                         view->line[view->lines - 1].dirty = 1;
6796                         if (!commit->author) {
6797                                 view->lines--;
6798                                 free(commit);
6799                                 graph->commit = NULL;
6800                         }
6801                 }
6802                 update_rev_graph(view, graph);
6804                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6805                         clear_rev_graph(&graph_stacks[i]);
6806                 return TRUE;
6807         }
6809         type = get_line_type(line);
6810         if (type == LINE_COMMIT) {
6811                 commit = calloc(1, sizeof(struct commit));
6812                 if (!commit)
6813                         return FALSE;
6815                 line += STRING_SIZE("commit ");
6816                 if (*line == '-') {
6817                         graph->boundary = 1;
6818                         line++;
6819                 }
6821                 string_copy_rev(commit->id, line);
6822                 commit->refs = get_ref_list(commit->id);
6823                 graph->commit = commit;
6824                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6826                 while ((line = strchr(line, ' '))) {
6827                         line++;
6828                         push_rev_graph(graph->parents, line);
6829                         commit->has_parents = TRUE;
6830                 }
6831                 return TRUE;
6832         }
6834         if (!view->lines)
6835                 return TRUE;
6836         commit = view->line[view->lines - 1].data;
6838         switch (type) {
6839         case LINE_PARENT:
6840                 if (commit->has_parents)
6841                         break;
6842                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6843                 break;
6845         case LINE_AUTHOR:
6846                 parse_author_line(line + STRING_SIZE("author "),
6847                                   &commit->author, &commit->time);
6848                 update_rev_graph(view, graph);
6849                 graph = graph->next;
6850                 break;
6852         default:
6853                 /* Fill in the commit title if it has not already been set. */
6854                 if (commit->title[0])
6855                         break;
6857                 /* Require titles to start with a non-space character at the
6858                  * offset used by git log. */
6859                 if (strncmp(line, "    ", 4))
6860                         break;
6861                 line += 4;
6862                 /* Well, if the title starts with a whitespace character,
6863                  * try to be forgiving.  Otherwise we end up with no title. */
6864                 while (isspace(*line))
6865                         line++;
6866                 if (*line == '\0')
6867                         break;
6868                 /* FIXME: More graceful handling of titles; append "..." to
6869                  * shortened titles, etc. */
6871                 string_expand(commit->title, sizeof(commit->title), line, 1);
6872                 view->line[view->lines - 1].dirty = 1;
6873         }
6875         return TRUE;
6878 static enum request
6879 main_request(struct view *view, enum request request, struct line *line)
6881         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6883         switch (request) {
6884         case REQ_ENTER:
6885                 open_view(view, REQ_VIEW_DIFF, flags);
6886                 break;
6887         case REQ_REFRESH:
6888                 load_refs();
6889                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6890                 break;
6891         default:
6892                 return request;
6893         }
6895         return REQ_NONE;
6898 static bool
6899 grep_refs(struct ref_list *list, regex_t *regex)
6901         regmatch_t pmatch;
6902         size_t i;
6904         if (!opt_show_refs || !list)
6905                 return FALSE;
6907         for (i = 0; i < list->size; i++) {
6908                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6909                         return TRUE;
6910         }
6912         return FALSE;
6915 static bool
6916 main_grep(struct view *view, struct line *line)
6918         struct commit *commit = line->data;
6919         const char *text[] = {
6920                 commit->title,
6921                 opt_author ? commit->author : "",
6922                 mkdate(&commit->time, opt_date),
6923                 NULL
6924         };
6926         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6929 static void
6930 main_select(struct view *view, struct line *line)
6932         struct commit *commit = line->data;
6934         string_copy_rev(view->ref, commit->id);
6935         string_copy_rev(ref_commit, view->ref);
6938 static struct view_ops main_ops = {
6939         "commit",
6940         main_argv,
6941         NULL,
6942         main_read,
6943         main_draw,
6944         main_request,
6945         main_grep,
6946         main_select,
6947 };
6950 /*
6951  * Status management
6952  */
6954 /* Whether or not the curses interface has been initialized. */
6955 static bool cursed = FALSE;
6957 /* Terminal hacks and workarounds. */
6958 static bool use_scroll_redrawwin;
6959 static bool use_scroll_status_wclear;
6961 /* The status window is used for polling keystrokes. */
6962 static WINDOW *status_win;
6964 /* Reading from the prompt? */
6965 static bool input_mode = FALSE;
6967 static bool status_empty = FALSE;
6969 /* Update status and title window. */
6970 static void
6971 report(const char *msg, ...)
6973         struct view *view = display[current_view];
6975         if (input_mode)
6976                 return;
6978         if (!view) {
6979                 char buf[SIZEOF_STR];
6980                 va_list args;
6982                 va_start(args, msg);
6983                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6984                         buf[sizeof(buf) - 1] = 0;
6985                         buf[sizeof(buf) - 2] = '.';
6986                         buf[sizeof(buf) - 3] = '.';
6987                         buf[sizeof(buf) - 4] = '.';
6988                 }
6989                 va_end(args);
6990                 die("%s", buf);
6991         }
6993         if (!status_empty || *msg) {
6994                 va_list args;
6996                 va_start(args, msg);
6998                 wmove(status_win, 0, 0);
6999                 if (view->has_scrolled && use_scroll_status_wclear)
7000                         wclear(status_win);
7001                 if (*msg) {
7002                         vwprintw(status_win, msg, args);
7003                         status_empty = FALSE;
7004                 } else {
7005                         status_empty = TRUE;
7006                 }
7007                 wclrtoeol(status_win);
7008                 wnoutrefresh(status_win);
7010                 va_end(args);
7011         }
7013         update_view_title(view);
7016 static void
7017 init_display(void)
7019         const char *term;
7020         int x, y;
7022         /* Initialize the curses library */
7023         if (isatty(STDIN_FILENO)) {
7024                 cursed = !!initscr();
7025                 opt_tty = stdin;
7026         } else {
7027                 /* Leave stdin and stdout alone when acting as a pager. */
7028                 opt_tty = fopen("/dev/tty", "r+");
7029                 if (!opt_tty)
7030                         die("Failed to open /dev/tty");
7031                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7032         }
7034         if (!cursed)
7035                 die("Failed to initialize curses");
7037         nonl();         /* Disable conversion and detect newlines from input. */
7038         cbreak();       /* Take input chars one at a time, no wait for \n */
7039         noecho();       /* Don't echo input */
7040         leaveok(stdscr, FALSE);
7042         if (has_colors())
7043                 init_colors();
7045         getmaxyx(stdscr, y, x);
7046         status_win = newwin(1, 0, y - 1, 0);
7047         if (!status_win)
7048                 die("Failed to create status window");
7050         /* Enable keyboard mapping */
7051         keypad(status_win, TRUE);
7052         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7054         TABSIZE = opt_tab_size;
7056         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7057         if (term && !strcmp(term, "gnome-terminal")) {
7058                 /* In the gnome-terminal-emulator, the message from
7059                  * scrolling up one line when impossible followed by
7060                  * scrolling down one line causes corruption of the
7061                  * status line. This is fixed by calling wclear. */
7062                 use_scroll_status_wclear = TRUE;
7063                 use_scroll_redrawwin = FALSE;
7065         } else if (term && !strcmp(term, "xrvt-xpm")) {
7066                 /* No problems with full optimizations in xrvt-(unicode)
7067                  * and aterm. */
7068                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7070         } else {
7071                 /* When scrolling in (u)xterm the last line in the
7072                  * scrolling direction will update slowly. */
7073                 use_scroll_redrawwin = TRUE;
7074                 use_scroll_status_wclear = FALSE;
7075         }
7078 static int
7079 get_input(int prompt_position)
7081         struct view *view;
7082         int i, key, cursor_y, cursor_x;
7083         bool loading = FALSE;
7085         if (prompt_position)
7086                 input_mode = TRUE;
7088         while (TRUE) {
7089                 foreach_view (view, i) {
7090                         update_view(view);
7091                         if (view_is_displayed(view) && view->has_scrolled &&
7092                             use_scroll_redrawwin)
7093                                 redrawwin(view->win);
7094                         view->has_scrolled = FALSE;
7095                         if (view->pipe)
7096                                 loading = TRUE;
7097                 }
7099                 /* Update the cursor position. */
7100                 if (prompt_position) {
7101                         getbegyx(status_win, cursor_y, cursor_x);
7102                         cursor_x = prompt_position;
7103                 } else {
7104                         view = display[current_view];
7105                         getbegyx(view->win, cursor_y, cursor_x);
7106                         cursor_x = view->width - 1;
7107                         cursor_y += view->lineno - view->offset;
7108                 }
7109                 setsyx(cursor_y, cursor_x);
7111                 /* Refresh, accept single keystroke of input */
7112                 doupdate();
7113                 nodelay(status_win, loading);
7114                 key = wgetch(status_win);
7116                 /* wgetch() with nodelay() enabled returns ERR when
7117                  * there's no input. */
7118                 if (key == ERR) {
7120                 } else if (key == KEY_RESIZE) {
7121                         int height, width;
7123                         getmaxyx(stdscr, height, width);
7125                         wresize(status_win, 1, width);
7126                         mvwin(status_win, height - 1, 0);
7127                         wnoutrefresh(status_win);
7128                         resize_display();
7129                         redraw_display(TRUE);
7131                 } else {
7132                         input_mode = FALSE;
7133                         return key;
7134                 }
7135         }
7138 static char *
7139 prompt_input(const char *prompt, input_handler handler, void *data)
7141         enum input_status status = INPUT_OK;
7142         static char buf[SIZEOF_STR];
7143         size_t pos = 0;
7145         buf[pos] = 0;
7147         while (status == INPUT_OK || status == INPUT_SKIP) {
7148                 int key;
7150                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7151                 wclrtoeol(status_win);
7153                 key = get_input(pos + 1);
7154                 switch (key) {
7155                 case KEY_RETURN:
7156                 case KEY_ENTER:
7157                 case '\n':
7158                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7159                         break;
7161                 case KEY_BACKSPACE:
7162                         if (pos > 0)
7163                                 buf[--pos] = 0;
7164                         else
7165                                 status = INPUT_CANCEL;
7166                         break;
7168                 case KEY_ESC:
7169                         status = INPUT_CANCEL;
7170                         break;
7172                 default:
7173                         if (pos >= sizeof(buf)) {
7174                                 report("Input string too long");
7175                                 return NULL;
7176                         }
7178                         status = handler(data, buf, key);
7179                         if (status == INPUT_OK)
7180                                 buf[pos++] = (char) key;
7181                 }
7182         }
7184         /* Clear the status window */
7185         status_empty = FALSE;
7186         report("");
7188         if (status == INPUT_CANCEL)
7189                 return NULL;
7191         buf[pos++] = 0;
7193         return buf;
7196 static enum input_status
7197 prompt_yesno_handler(void *data, char *buf, int c)
7199         if (c == 'y' || c == 'Y')
7200                 return INPUT_STOP;
7201         if (c == 'n' || c == 'N')
7202                 return INPUT_CANCEL;
7203         return INPUT_SKIP;
7206 static bool
7207 prompt_yesno(const char *prompt)
7209         char prompt2[SIZEOF_STR];
7211         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7212                 return FALSE;
7214         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7217 static enum input_status
7218 read_prompt_handler(void *data, char *buf, int c)
7220         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7223 static char *
7224 read_prompt(const char *prompt)
7226         return prompt_input(prompt, read_prompt_handler, NULL);
7229 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7231         enum input_status status = INPUT_OK;
7232         int size = 0;
7234         while (items[size].text)
7235                 size++;
7237         while (status == INPUT_OK) {
7238                 const struct menu_item *item = &items[*selected];
7239                 int key;
7240                 int i;
7242                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7243                           prompt, *selected + 1, size);
7244                 if (item->hotkey)
7245                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7246                 wprintw(status_win, "%s", item->text);
7247                 wclrtoeol(status_win);
7249                 key = get_input(COLS - 1);
7250                 switch (key) {
7251                 case KEY_RETURN:
7252                 case KEY_ENTER:
7253                 case '\n':
7254                         status = INPUT_STOP;
7255                         break;
7257                 case KEY_LEFT:
7258                 case KEY_UP:
7259                         *selected = *selected - 1;
7260                         if (*selected < 0)
7261                                 *selected = size - 1;
7262                         break;
7264                 case KEY_RIGHT:
7265                 case KEY_DOWN:
7266                         *selected = (*selected + 1) % size;
7267                         break;
7269                 case KEY_ESC:
7270                         status = INPUT_CANCEL;
7271                         break;
7273                 default:
7274                         for (i = 0; items[i].text; i++)
7275                                 if (items[i].hotkey == key) {
7276                                         *selected = i;
7277                                         status = INPUT_STOP;
7278                                         break;
7279                                 }
7280                 }
7281         }
7283         /* Clear the status window */
7284         status_empty = FALSE;
7285         report("");
7287         return status != INPUT_CANCEL;
7290 /*
7291  * Repository properties
7292  */
7294 static struct ref **refs = NULL;
7295 static size_t refs_size = 0;
7296 static struct ref *refs_head = NULL;
7298 static struct ref_list **ref_lists = NULL;
7299 static size_t ref_lists_size = 0;
7301 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7302 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7303 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7305 static int
7306 compare_refs(const void *ref1_, const void *ref2_)
7308         const struct ref *ref1 = *(const struct ref **)ref1_;
7309         const struct ref *ref2 = *(const struct ref **)ref2_;
7311         if (ref1->tag != ref2->tag)
7312                 return ref2->tag - ref1->tag;
7313         if (ref1->ltag != ref2->ltag)
7314                 return ref2->ltag - ref2->ltag;
7315         if (ref1->head != ref2->head)
7316                 return ref2->head - ref1->head;
7317         if (ref1->tracked != ref2->tracked)
7318                 return ref2->tracked - ref1->tracked;
7319         if (ref1->remote != ref2->remote)
7320                 return ref2->remote - ref1->remote;
7321         return strcmp(ref1->name, ref2->name);
7324 static void
7325 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7327         size_t i;
7329         for (i = 0; i < refs_size; i++)
7330                 if (!visitor(data, refs[i]))
7331                         break;
7334 static struct ref *
7335 get_ref_head()
7337         return refs_head;
7340 static struct ref_list *
7341 get_ref_list(const char *id)
7343         struct ref_list *list;
7344         size_t i;
7346         for (i = 0; i < ref_lists_size; i++)
7347                 if (!strcmp(id, ref_lists[i]->id))
7348                         return ref_lists[i];
7350         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7351                 return NULL;
7352         list = calloc(1, sizeof(*list));
7353         if (!list)
7354                 return NULL;
7356         for (i = 0; i < refs_size; i++) {
7357                 if (!strcmp(id, refs[i]->id) &&
7358                     realloc_refs_list(&list->refs, list->size, 1))
7359                         list->refs[list->size++] = refs[i];
7360         }
7362         if (!list->refs) {
7363                 free(list);
7364                 return NULL;
7365         }
7367         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7368         ref_lists[ref_lists_size++] = list;
7369         return list;
7372 static int
7373 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7375         struct ref *ref = NULL;
7376         bool tag = FALSE;
7377         bool ltag = FALSE;
7378         bool remote = FALSE;
7379         bool tracked = FALSE;
7380         bool head = FALSE;
7381         int from = 0, to = refs_size - 1;
7383         if (!prefixcmp(name, "refs/tags/")) {
7384                 if (!suffixcmp(name, namelen, "^{}")) {
7385                         namelen -= 3;
7386                         name[namelen] = 0;
7387                 } else {
7388                         ltag = TRUE;
7389                 }
7391                 tag = TRUE;
7392                 namelen -= STRING_SIZE("refs/tags/");
7393                 name    += STRING_SIZE("refs/tags/");
7395         } else if (!prefixcmp(name, "refs/remotes/")) {
7396                 remote = TRUE;
7397                 namelen -= STRING_SIZE("refs/remotes/");
7398                 name    += STRING_SIZE("refs/remotes/");
7399                 tracked  = !strcmp(opt_remote, name);
7401         } else if (!prefixcmp(name, "refs/heads/")) {
7402                 namelen -= STRING_SIZE("refs/heads/");
7403                 name    += STRING_SIZE("refs/heads/");
7404                 if (!strncmp(opt_head, name, namelen))
7405                         return OK;
7407         } else if (!strcmp(name, "HEAD")) {
7408                 head     = TRUE;
7409                 if (*opt_head) {
7410                         namelen  = strlen(opt_head);
7411                         name     = opt_head;
7412                 }
7413         }
7415         /* If we are reloading or it's an annotated tag, replace the
7416          * previous SHA1 with the resolved commit id; relies on the fact
7417          * git-ls-remote lists the commit id of an annotated tag right
7418          * before the commit id it points to. */
7419         while (from <= to) {
7420                 size_t pos = (to + from) / 2;
7421                 int cmp = strcmp(name, refs[pos]->name);
7423                 if (!cmp) {
7424                         ref = refs[pos];
7425                         break;
7426                 }
7428                 if (cmp < 0)
7429                         to = pos - 1;
7430                 else
7431                         from = pos + 1;
7432         }
7434         if (!ref) {
7435                 if (!realloc_refs(&refs, refs_size, 1))
7436                         return ERR;
7437                 ref = calloc(1, sizeof(*ref) + namelen);
7438                 if (!ref)
7439                         return ERR;
7440                 memmove(refs + from + 1, refs + from,
7441                         (refs_size - from) * sizeof(*refs));
7442                 refs[from] = ref;
7443                 strncpy(ref->name, name, namelen);
7444                 refs_size++;
7445         }
7447         ref->head = head;
7448         ref->tag = tag;
7449         ref->ltag = ltag;
7450         ref->remote = remote;
7451         ref->tracked = tracked;
7452         string_copy_rev(ref->id, id);
7454         if (head)
7455                 refs_head = ref;
7456         return OK;
7459 static int
7460 load_refs(void)
7462         const char *head_argv[] = {
7463                 "git", "symbolic-ref", "HEAD", NULL
7464         };
7465         static const char *ls_remote_argv[SIZEOF_ARG] = {
7466                 "git", "ls-remote", opt_git_dir, NULL
7467         };
7468         static bool init = FALSE;
7469         size_t i;
7471         if (!init) {
7472                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7473                         die("TIG_LS_REMOTE contains too many arguments");
7474                 init = TRUE;
7475         }
7477         if (!*opt_git_dir)
7478                 return OK;
7480         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7481             !prefixcmp(opt_head, "refs/heads/")) {
7482                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7484                 memmove(opt_head, offset, strlen(offset) + 1);
7485         }
7487         refs_head = NULL;
7488         for (i = 0; i < refs_size; i++)
7489                 refs[i]->id[0] = 0;
7491         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7492                 return ERR;
7494         /* Update the ref lists to reflect changes. */
7495         for (i = 0; i < ref_lists_size; i++) {
7496                 struct ref_list *list = ref_lists[i];
7497                 size_t old, new;
7499                 for (old = new = 0; old < list->size; old++)
7500                         if (!strcmp(list->id, list->refs[old]->id))
7501                                 list->refs[new++] = list->refs[old];
7502                 list->size = new;
7503         }
7505         return OK;
7508 static void
7509 set_remote_branch(const char *name, const char *value, size_t valuelen)
7511         if (!strcmp(name, ".remote")) {
7512                 string_ncopy(opt_remote, value, valuelen);
7514         } else if (*opt_remote && !strcmp(name, ".merge")) {
7515                 size_t from = strlen(opt_remote);
7517                 if (!prefixcmp(value, "refs/heads/"))
7518                         value += STRING_SIZE("refs/heads/");
7520                 if (!string_format_from(opt_remote, &from, "/%s", value))
7521                         opt_remote[0] = 0;
7522         }
7525 static void
7526 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7528         const char *argv[SIZEOF_ARG] = { name, "=" };
7529         int argc = 1 + (cmd == option_set_command);
7530         int error = ERR;
7532         if (!argv_from_string(argv, &argc, value))
7533                 config_msg = "Too many option arguments";
7534         else
7535                 error = cmd(argc, argv);
7537         if (error == ERR)
7538                 warn("Option 'tig.%s': %s", name, config_msg);
7541 static bool
7542 set_environment_variable(const char *name, const char *value)
7544         size_t len = strlen(name) + 1 + strlen(value) + 1;
7545         char *env = malloc(len);
7547         if (env &&
7548             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7549             putenv(env) == 0)
7550                 return TRUE;
7551         free(env);
7552         return FALSE;
7555 static void
7556 set_work_tree(const char *value)
7558         char cwd[SIZEOF_STR];
7560         if (!getcwd(cwd, sizeof(cwd)))
7561                 die("Failed to get cwd path: %s", strerror(errno));
7562         if (chdir(opt_git_dir) < 0)
7563                 die("Failed to chdir(%s): %s", strerror(errno));
7564         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7565                 die("Failed to get git path: %s", strerror(errno));
7566         if (chdir(cwd) < 0)
7567                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7568         if (chdir(value) < 0)
7569                 die("Failed to chdir(%s): %s", value, strerror(errno));
7570         if (!getcwd(cwd, sizeof(cwd)))
7571                 die("Failed to get cwd path: %s", strerror(errno));
7572         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7573                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7574         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7575                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7576         opt_is_inside_work_tree = TRUE;
7579 static int
7580 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7582         if (!strcmp(name, "i18n.commitencoding"))
7583                 string_ncopy(opt_encoding, value, valuelen);
7585         else if (!strcmp(name, "core.editor"))
7586                 string_ncopy(opt_editor, value, valuelen);
7588         else if (!strcmp(name, "core.worktree"))
7589                 set_work_tree(value);
7591         else if (!prefixcmp(name, "tig.color."))
7592                 set_repo_config_option(name + 10, value, option_color_command);
7594         else if (!prefixcmp(name, "tig.bind."))
7595                 set_repo_config_option(name + 9, value, option_bind_command);
7597         else if (!prefixcmp(name, "tig."))
7598                 set_repo_config_option(name + 4, value, option_set_command);
7600         else if (*opt_head && !prefixcmp(name, "branch.") &&
7601                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7602                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7604         return OK;
7607 static int
7608 load_git_config(void)
7610         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7612         return io_run_load(config_list_argv, "=", read_repo_config_option);
7615 static int
7616 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7618         if (!opt_git_dir[0]) {
7619                 string_ncopy(opt_git_dir, name, namelen);
7621         } else if (opt_is_inside_work_tree == -1) {
7622                 /* This can be 3 different values depending on the
7623                  * version of git being used. If git-rev-parse does not
7624                  * understand --is-inside-work-tree it will simply echo
7625                  * the option else either "true" or "false" is printed.
7626                  * Default to true for the unknown case. */
7627                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7629         } else if (*name == '.') {
7630                 string_ncopy(opt_cdup, name, namelen);
7632         } else {
7633                 string_ncopy(opt_prefix, name, namelen);
7634         }
7636         return OK;
7639 static int
7640 load_repo_info(void)
7642         const char *rev_parse_argv[] = {
7643                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7644                         "--show-cdup", "--show-prefix", NULL
7645         };
7647         return io_run_load(rev_parse_argv, "=", read_repo_info);
7651 /*
7652  * Main
7653  */
7655 static const char usage[] =
7656 "tig " TIG_VERSION " (" __DATE__ ")\n"
7657 "\n"
7658 "Usage: tig        [options] [revs] [--] [paths]\n"
7659 "   or: tig show   [options] [revs] [--] [paths]\n"
7660 "   or: tig blame  [rev] path\n"
7661 "   or: tig status\n"
7662 "   or: tig <      [git command output]\n"
7663 "\n"
7664 "Options:\n"
7665 "  -v, --version   Show version and exit\n"
7666 "  -h, --help      Show help message and exit";
7668 static void __NORETURN
7669 quit(int sig)
7671         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7672         if (cursed)
7673                 endwin();
7674         exit(0);
7677 static void __NORETURN
7678 die(const char *err, ...)
7680         va_list args;
7682         endwin();
7684         va_start(args, err);
7685         fputs("tig: ", stderr);
7686         vfprintf(stderr, err, args);
7687         fputs("\n", stderr);
7688         va_end(args);
7690         exit(1);
7693 static void
7694 warn(const char *msg, ...)
7696         va_list args;
7698         va_start(args, msg);
7699         fputs("tig warning: ", stderr);
7700         vfprintf(stderr, msg, args);
7701         fputs("\n", stderr);
7702         va_end(args);
7705 static enum request
7706 parse_options(int argc, const char *argv[])
7708         enum request request = REQ_VIEW_MAIN;
7709         const char *subcommand;
7710         bool seen_dashdash = FALSE;
7711         /* XXX: This is vulnerable to the user overriding options
7712          * required for the main view parser. */
7713         const char *custom_argv[SIZEOF_ARG] = {
7714                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7715                         "--topo-order", NULL
7716         };
7717         int i, j = 6;
7719         if (!isatty(STDIN_FILENO)) {
7720                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7721                 return REQ_VIEW_PAGER;
7722         }
7724         if (argc <= 1)
7725                 return REQ_NONE;
7727         subcommand = argv[1];
7728         if (!strcmp(subcommand, "status")) {
7729                 if (argc > 2)
7730                         warn("ignoring arguments after `%s'", subcommand);
7731                 return REQ_VIEW_STATUS;
7733         } else if (!strcmp(subcommand, "blame")) {
7734                 if (argc <= 2 || argc > 4)
7735                         die("invalid number of options to blame\n\n%s", usage);
7737                 i = 2;
7738                 if (argc == 4) {
7739                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7740                         i++;
7741                 }
7743                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7744                 return REQ_VIEW_BLAME;
7746         } else if (!strcmp(subcommand, "show")) {
7747                 request = REQ_VIEW_DIFF;
7749         } else {
7750                 subcommand = NULL;
7751         }
7753         if (subcommand) {
7754                 custom_argv[1] = subcommand;
7755                 j = 2;
7756         }
7758         for (i = 1 + !!subcommand; i < argc; i++) {
7759                 const char *opt = argv[i];
7761                 if (seen_dashdash || !strcmp(opt, "--")) {
7762                         seen_dashdash = TRUE;
7764                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7765                         printf("tig version %s\n", TIG_VERSION);
7766                         quit(0);
7768                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7769                         printf("%s\n", usage);
7770                         quit(0);
7771                 }
7773                 custom_argv[j++] = opt;
7774                 if (j >= ARRAY_SIZE(custom_argv))
7775                         die("command too long");
7776         }
7778         if (!prepare_update(VIEW(request), custom_argv, NULL))
7779                 die("Failed to format arguments");
7781         return request;
7784 int
7785 main(int argc, const char *argv[])
7787         const char *codeset = "UTF-8";
7788         enum request request = parse_options(argc, argv);
7789         struct view *view;
7790         size_t i;
7792         signal(SIGINT, quit);
7793         signal(SIGPIPE, SIG_IGN);
7795         if (setlocale(LC_ALL, "")) {
7796                 codeset = nl_langinfo(CODESET);
7797         }
7799         if (load_repo_info() == ERR)
7800                 die("Failed to load repo info.");
7802         if (load_options() == ERR)
7803                 die("Failed to load user config.");
7805         if (load_git_config() == ERR)
7806                 die("Failed to load repo config.");
7808         /* Require a git repository unless when running in pager mode. */
7809         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7810                 die("Not a git repository");
7812         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7813                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7814                 if (opt_iconv_in == ICONV_NONE)
7815                         die("Failed to initialize character set conversion");
7816         }
7818         if (codeset && strcmp(codeset, "UTF-8")) {
7819                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7820                 if (opt_iconv_out == ICONV_NONE)
7821                         die("Failed to initialize character set conversion");
7822         }
7824         if (load_refs() == ERR)
7825                 die("Failed to load refs.");
7827         foreach_view (view, i)
7828                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7829                         die("Too many arguments in the `%s` environment variable",
7830                             view->cmd_env);
7832         init_display();
7834         if (request != REQ_NONE)
7835                 open_view(NULL, request, OPEN_PREPARED);
7836         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7838         while (view_driver(display[current_view], request)) {
7839                 int key = get_input(0);
7841                 view = display[current_view];
7842                 request = get_keybinding(view->keymap, key);
7844                 /* Some low-level request handling. This keeps access to
7845                  * status_win restricted. */
7846                 switch (request) {
7847                 case REQ_NONE:
7848                         report("Unknown key, press %s for help",
7849                                get_key(view->keymap, REQ_VIEW_HELP));
7850                         break;
7851                 case REQ_PROMPT:
7852                 {
7853                         char *cmd = read_prompt(":");
7855                         if (cmd && isdigit(*cmd)) {
7856                                 int lineno = view->lineno + 1;
7858                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7859                                         select_view_line(view, lineno - 1);
7860                                         report("");
7861                                 } else {
7862                                         report("Unable to parse '%s' as a line number", cmd);
7863                                 }
7865                         } else if (cmd) {
7866                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7867                                 const char *argv[SIZEOF_ARG] = { "git" };
7868                                 int argc = 1;
7870                                 /* When running random commands, initially show the
7871                                  * command in the title. However, it maybe later be
7872                                  * overwritten if a commit line is selected. */
7873                                 string_ncopy(next->ref, cmd, strlen(cmd));
7875                                 if (!argv_from_string(argv, &argc, cmd)) {
7876                                         report("Too many arguments");
7877                                 } else if (!prepare_update(next, argv, NULL)) {
7878                                         report("Failed to format command");
7879                                 } else {
7880                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7881                                 }
7882                         }
7884                         request = REQ_NONE;
7885                         break;
7886                 }
7887                 case REQ_SEARCH:
7888                 case REQ_SEARCH_BACK:
7889                 {
7890                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7891                         char *search = read_prompt(prompt);
7893                         if (search)
7894                                 string_ncopy(opt_search, search, strlen(search));
7895                         else if (*opt_search)
7896                                 request = request == REQ_SEARCH ?
7897                                         REQ_FIND_NEXT :
7898                                         REQ_FIND_PREV;
7899                         else
7900                                 request = REQ_NONE;
7901                         break;
7902                 }
7903                 default:
7904                         break;
7905                 }
7906         }
7908         quit(0);
7910         return 0;