Code

Only update status view at EOF for displayed views
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int head:1;    /* Is it the current HEAD? */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int ltag:1;    /* If so, is the tag local? */
127         unsigned int remote:1;  /* Is it a remote ref? */
128         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129         char name[1];           /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         size_t size;            /* Number of refs. */
135         struct ref **refs;      /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151         INPUT_OK,
152         INPUT_SKIP,
153         INPUT_STOP,
154         INPUT_CANCEL
155 };
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163         int hotkey;
164         const char *text;
165         void *data;
166 };
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
170 /*
171  * Allocation helpers ... Entering macro hell to never be seen again.
172  */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
175 static type *                                                                   \
176 name(type **mem, size_t size, size_t increase)                                  \
177 {                                                                               \
178         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
179         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180         type *tmp = *mem;                                                       \
181                                                                                 \
182         if (mem == NULL || num_chunks != num_chunks_new) {                      \
183                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184                 if (tmp)                                                        \
185                         *mem = tmp;                                             \
186         }                                                                       \
187                                                                                 \
188         return tmp;                                                             \
191 /*
192  * String helpers
193  */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198         if (srclen > dstlen - 1)
199                 srclen = dstlen - 1;
201         strncpy(dst, src, srclen);
202         dst[srclen] = 0;
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211         string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
222         size_t size, pos;
224         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225                 if (src[pos] == '\t') {
226                         size_t expanded = tabsize - (size % tabsize);
228                         if (expanded + size >= dstlen - 1)
229                                 expanded = dstlen - size - 1;
230                         memcpy(dst + size, "        ", expanded);
231                         size += expanded;
232                 } else {
233                         dst[size++] = src[pos];
234                 }
235         }
237         dst[size] = 0;
240 static char *
241 chomp_string(char *name)
243         int namelen;
245         while (isspace(*name))
246                 name++;
248         namelen = strlen(name) - 1;
249         while (namelen > 0 && isspace(name[namelen]))
250                 name[namelen--] = 0;
252         return name;
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
258         va_list args;
259         size_t pos = bufpos ? *bufpos : 0;
261         va_start(args, fmt);
262         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263         va_end(args);
265         if (bufpos)
266                 *bufpos = pos;
268         return pos >= bufsize ? FALSE : TRUE;
271 #define string_format(buf, fmt, args...) \
272         string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275         string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
280         size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284         /* Diff-Header == DIFF_HEADER */
285         for (i = 0; i < len; i++) {
286                 if (toupper(str1[i]) == toupper(str2[i]))
287                         continue;
289                 if (string_enum_sep(str1[i]) &&
290                     string_enum_sep(str2[i]))
291                         continue;
293                 return str1[i] - str2[i];
294         }
296         return 0;
299 #define enum_equals(entry, str, len) \
300         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303         const char *name;
304         int namelen;
305         int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
313         static char buf[SIZEOF_STR];
314         int bufpos;
316         for (bufpos = 0; bufpos <= namelen; bufpos++) {
317                 buf[bufpos] = tolower(name[bufpos]);
318                 if (buf[bufpos] == '_')
319                         buf[bufpos] = '-';
320         }
322         buf[bufpos] = 0;
323         return buf;
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
331         size_t namelen = strlen(name);
332         int i;
334         for (i = 0; i < map_size; i++)
335                 if (enum_equals(map[i], name, namelen)) {
336                         *value = map[i].value;
337                         return TRUE;
338                 }
340         return FALSE;
343 #define map_enum(attr, map, name) \
344         map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347         strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
352         size_t len = slen >= 0 ? slen : strlen(str);
353         size_t suffixlen = strlen(suffix);
355         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
359 /*
360  * Unicode / UTF-8 handling
361  *
362  * NOTE: Much of the following code for dealing with Unicode is derived from
363  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
365  */
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
370         if (c >= 0x1100 &&
371            (c <= 0x115f                         /* Hangul Jamo */
372             || c == 0x2329
373             || c == 0x232a
374             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
375                                                 /* CJK ... Yi */
376             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
377             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
378             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
379             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
380             || (c >= 0xffe0  && c <= 0xffe6)
381             || (c >= 0x20000 && c <= 0x2fffd)
382             || (c >= 0x30000 && c <= 0x3fffd)))
383                 return 2;
385         if (c == '\t')
386                 return tab_size;
388         return 1;
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392  * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
396         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
397         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
398         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
399         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
400         2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
401         3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
402 };
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
407         int c = *(unsigned char *) string;
409         return utf8_bytes[c];
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
416         unsigned long unicode;
418         switch (length) {
419         case 1:
420                 unicode  =   string[0];
421                 break;
422         case 2:
423                 unicode  =  (string[0] & 0x1f) << 6;
424                 unicode +=  (string[1] & 0x3f);
425                 break;
426         case 3:
427                 unicode  =  (string[0] & 0x0f) << 12;
428                 unicode += ((string[1] & 0x3f) << 6);
429                 unicode +=  (string[2] & 0x3f);
430                 break;
431         case 4:
432                 unicode  =  (string[0] & 0x0f) << 18;
433                 unicode += ((string[1] & 0x3f) << 12);
434                 unicode += ((string[2] & 0x3f) << 6);
435                 unicode +=  (string[3] & 0x3f);
436                 break;
437         case 5:
438                 unicode  =  (string[0] & 0x0f) << 24;
439                 unicode += ((string[1] & 0x3f) << 18);
440                 unicode += ((string[2] & 0x3f) << 12);
441                 unicode += ((string[3] & 0x3f) << 6);
442                 unicode +=  (string[4] & 0x3f);
443                 break;
444         case 6:
445                 unicode  =  (string[0] & 0x01) << 30;
446                 unicode += ((string[1] & 0x3f) << 24);
447                 unicode += ((string[2] & 0x3f) << 18);
448                 unicode += ((string[3] & 0x3f) << 12);
449                 unicode += ((string[4] & 0x3f) << 6);
450                 unicode +=  (string[5] & 0x3f);
451                 break;
452         default:
453                 return 0;
454         }
456         /* Invalid characters could return the special 0xfffd value but NUL
457          * should be just as good. */
458         return unicode > 0xffff ? 0 : unicode;
461 /* Calculates how much of string can be shown within the given maximum width
462  * and sets trimmed parameter to non-zero value if all of string could not be
463  * shown. If the reserve flag is TRUE, it will reserve at least one
464  * trailing character, which can be useful when drawing a delimiter.
465  *
466  * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
470         const char *string = *start;
471         const char *end = strchr(string, '\0');
472         unsigned char last_bytes = 0;
473         size_t last_ucwidth = 0;
475         *width = 0;
476         *trimmed = 0;
478         while (string < end) {
479                 unsigned char bytes = utf8_char_length(string, end);
480                 size_t ucwidth;
481                 unsigned long unicode;
483                 if (string + bytes > end)
484                         break;
486                 /* Change representation to figure out whether
487                  * it is a single- or double-width character. */
489                 unicode = utf8_to_unicode(string, bytes);
490                 /* FIXME: Graceful handling of invalid Unicode character. */
491                 if (!unicode)
492                         break;
494                 ucwidth = unicode_width(unicode, tab_size);
495                 if (skip > 0) {
496                         skip -= ucwidth <= skip ? ucwidth : skip;
497                         *start += bytes;
498                 }
499                 *width  += ucwidth;
500                 if (*width > max_width) {
501                         *trimmed = 1;
502                         *width -= ucwidth;
503                         if (reserve && *width == max_width) {
504                                 string -= last_bytes;
505                                 *width -= last_ucwidth;
506                         }
507                         break;
508                 }
510                 string  += bytes;
511                 last_bytes = ucwidth ? bytes : 0;
512                 last_ucwidth = ucwidth;
513         }
515         return string - *start;
519 #define DATE_INFO \
520         DATE_(NO), \
521         DATE_(DEFAULT), \
522         DATE_(LOCAL), \
523         DATE_(RELATIVE), \
524         DATE_(SHORT)
526 enum date {
527 #define DATE_(name) DATE_##name
528         DATE_INFO
529 #undef  DATE_
530 };
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534         DATE_INFO
535 #undef  DATE_
536 };
538 struct time {
539         time_t sec;
540         int tz;
541 };
543 static inline int timecmp(const struct time *t1, const struct time *t2)
545         return t1->sec - t2->sec;
548 static const char *
549 mkdate(const struct time *time, enum date date)
551         static char buf[DATE_COLS + 1];
552         static const struct enum_map reldate[] = {
553                 { "second", 1,                  60 * 2 },
554                 { "minute", 60,                 60 * 60 * 2 },
555                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
556                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
557                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
558                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
559         };
560         struct tm tm;
562         if (!date || !time || !time->sec)
563                 return "";
565         if (date == DATE_RELATIVE) {
566                 struct timeval now;
567                 time_t date = time->sec + time->tz;
568                 time_t seconds;
569                 int i;
571                 gettimeofday(&now, NULL);
572                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
573                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
574                         if (seconds >= reldate[i].value)
575                                 continue;
577                         seconds /= reldate[i].namelen;
578                         if (!string_format(buf, "%ld %s%s %s",
579                                            seconds, reldate[i].name,
580                                            seconds > 1 ? "s" : "",
581                                            now.tv_sec >= date ? "ago" : "ahead"))
582                                 break;
583                         return buf;
584                 }
585         }
587         if (date == DATE_LOCAL) {
588                 time_t date = time->sec + time->tz;
589                 localtime_r(&date, &tm);
590         }
591         else {
592                 gmtime_r(&time->sec, &tm);
593         }
594         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
598 #define AUTHOR_VALUES \
599         AUTHOR_(NO), \
600         AUTHOR_(FULL), \
601         AUTHOR_(ABBREVIATED)
603 enum author {
604 #define AUTHOR_(name) AUTHOR_##name
605         AUTHOR_VALUES,
606 #undef  AUTHOR_
607         AUTHOR_DEFAULT = AUTHOR_FULL
608 };
610 static const struct enum_map author_map[] = {
611 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
612         AUTHOR_VALUES
613 #undef  AUTHOR_
614 };
616 static const char *
617 get_author_initials(const char *author)
619         static char initials[AUTHOR_COLS * 6 + 1];
620         size_t pos = 0;
621         const char *end = strchr(author, '\0');
623 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
625         memset(initials, 0, sizeof(initials));
626         while (author < end) {
627                 unsigned char bytes;
628                 size_t i;
630                 while (is_initial_sep(*author))
631                         author++;
633                 bytes = utf8_char_length(author, end);
634                 if (bytes < sizeof(initials) - 1 - pos) {
635                         while (bytes--) {
636                                 initials[pos++] = *author++;
637                         }
638                 }
640                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
641                         if (i < sizeof(initials) - 1)
642                                 initials[i++] = *author;
643                 }
645                 initials[i++] = 0;
646         }
648         return initials;
652 static bool
653 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
655         int valuelen;
657         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
658                 bool advance = cmd[valuelen] != 0;
660                 cmd[valuelen] = 0;
661                 argv[(*argc)++] = chomp_string(cmd);
662                 cmd = chomp_string(cmd + valuelen + advance);
663         }
665         if (*argc < SIZEOF_ARG)
666                 argv[*argc] = NULL;
667         return *argc < SIZEOF_ARG;
670 static bool
671 argv_from_env(const char **argv, const char *name)
673         char *env = argv ? getenv(name) : NULL;
674         int argc = 0;
676         if (env && *env)
677                 env = strdup(env);
678         return !env || argv_from_string(argv, &argc, env);
682 /*
683  * Executing external commands.
684  */
686 enum io_type {
687         IO_FD,                  /* File descriptor based IO. */
688         IO_BG,                  /* Execute command in the background. */
689         IO_FG,                  /* Execute command with same std{in,out,err}. */
690         IO_RD,                  /* Read only fork+exec IO. */
691         IO_WR,                  /* Write only fork+exec IO. */
692         IO_AP,                  /* Append fork+exec output to file. */
693 };
695 struct io {
696         enum io_type type;      /* The requested type of pipe. */
697         const char *dir;        /* Directory from which to execute. */
698         pid_t pid;              /* PID of spawned process. */
699         int pipe;               /* Pipe end for reading or writing. */
700         int error;              /* Error status. */
701         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
702         char *buf;              /* Read buffer. */
703         size_t bufalloc;        /* Allocated buffer size. */
704         size_t bufsize;         /* Buffer content size. */
705         char *bufpos;           /* Current buffer position. */
706         unsigned int eof:1;     /* Has end of file been reached. */
707 };
709 static void
710 io_reset(struct io *io)
712         io->pipe = -1;
713         io->pid = 0;
714         io->buf = io->bufpos = NULL;
715         io->bufalloc = io->bufsize = 0;
716         io->error = 0;
717         io->eof = 0;
720 static void
721 io_init(struct io *io, const char *dir, enum io_type type)
723         io_reset(io);
724         io->type = type;
725         io->dir = dir;
728 static bool
729 io_format(struct io *io, const char *dir, enum io_type type,
730           const char *argv[], enum format_flags flags)
732         io_init(io, dir, type);
733         return format_argv(io->argv, argv, flags);
736 static bool
737 io_open(struct io *io, const char *fmt, ...)
739         char name[SIZEOF_STR] = "";
740         bool fits;
741         va_list args;
743         io_init(io, NULL, IO_FD);
745         va_start(args, fmt);
746         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
747         va_end(args);
749         if (!fits) {
750                 io->error = ENAMETOOLONG;
751                 return FALSE;
752         }
753         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
754         if (io->pipe == -1)
755                 io->error = errno;
756         return io->pipe != -1;
759 static bool
760 io_kill(struct io *io)
762         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
765 static bool
766 io_done(struct io *io)
768         pid_t pid = io->pid;
770         if (io->pipe != -1)
771                 close(io->pipe);
772         free(io->buf);
773         io_reset(io);
775         while (pid > 0) {
776                 int status;
777                 pid_t waiting = waitpid(pid, &status, 0);
779                 if (waiting < 0) {
780                         if (errno == EINTR)
781                                 continue;
782                         io->error = errno;
783                         return FALSE;
784                 }
786                 return waiting == pid &&
787                        !WIFSIGNALED(status) &&
788                        WIFEXITED(status) &&
789                        !WEXITSTATUS(status);
790         }
792         return TRUE;
795 static bool
796 io_start(struct io *io)
798         int pipefds[2] = { -1, -1 };
800         if (io->type == IO_FD)
801                 return TRUE;
803         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
804                 io->error = errno;
805                 return FALSE;
806         } else if (io->type == IO_AP) {
807                 pipefds[1] = io->pipe;
808         }
810         if ((io->pid = fork())) {
811                 if (io->pid == -1)
812                         io->error = errno;
813                 if (pipefds[!(io->type == IO_WR)] != -1)
814                         close(pipefds[!(io->type == IO_WR)]);
815                 if (io->pid != -1) {
816                         io->pipe = pipefds[!!(io->type == IO_WR)];
817                         return TRUE;
818                 }
820         } else {
821                 if (io->type != IO_FG) {
822                         int devnull = open("/dev/null", O_RDWR);
823                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
824                         int writefd = (io->type == IO_RD || io->type == IO_AP)
825                                                         ? pipefds[1] : devnull;
827                         dup2(readfd,  STDIN_FILENO);
828                         dup2(writefd, STDOUT_FILENO);
829                         dup2(devnull, STDERR_FILENO);
831                         close(devnull);
832                         if (pipefds[0] != -1)
833                                 close(pipefds[0]);
834                         if (pipefds[1] != -1)
835                                 close(pipefds[1]);
836                 }
838                 if (io->dir && *io->dir && chdir(io->dir) == -1)
839                         exit(errno);
841                 execvp(io->argv[0], (char *const*) io->argv);
842                 exit(errno);
843         }
845         if (pipefds[!!(io->type == IO_WR)] != -1)
846                 close(pipefds[!!(io->type == IO_WR)]);
847         return FALSE;
850 static bool
851 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
853         io_init(io, dir, type);
854         if (!format_argv(io->argv, argv, FORMAT_NONE))
855                 return FALSE;
856         return io_start(io);
859 static int
860 io_complete(struct io *io)
862         return io_start(io) && io_done(io);
865 static int
866 io_run_bg(const char **argv)
868         struct io io = {};
870         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
871                 return FALSE;
872         return io_complete(&io);
875 static bool
876 io_run_fg(const char **argv, const char *dir)
878         struct io io = {};
880         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
881                 return FALSE;
882         return io_complete(&io);
885 static bool
886 io_run_append(const char **argv, enum format_flags flags, int fd)
888         struct io io = {};
890         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
891                 close(fd);
892                 return FALSE;
893         }
895         io.pipe = fd;
896         return io_complete(&io);
899 static bool
900 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
902         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
905 static bool
906 io_eof(struct io *io)
908         return io->eof;
911 static int
912 io_error(struct io *io)
914         return io->error;
917 static char *
918 io_strerror(struct io *io)
920         return strerror(io->error);
923 static bool
924 io_can_read(struct io *io)
926         struct timeval tv = { 0, 500 };
927         fd_set fds;
929         FD_ZERO(&fds);
930         FD_SET(io->pipe, &fds);
932         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
935 static ssize_t
936 io_read(struct io *io, void *buf, size_t bufsize)
938         do {
939                 ssize_t readsize = read(io->pipe, buf, bufsize);
941                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
942                         continue;
943                 else if (readsize == -1)
944                         io->error = errno;
945                 else if (readsize == 0)
946                         io->eof = 1;
947                 return readsize;
948         } while (1);
951 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
953 static char *
954 io_get(struct io *io, int c, bool can_read)
956         char *eol;
957         ssize_t readsize;
959         while (TRUE) {
960                 if (io->bufsize > 0) {
961                         eol = memchr(io->bufpos, c, io->bufsize);
962                         if (eol) {
963                                 char *line = io->bufpos;
965                                 *eol = 0;
966                                 io->bufpos = eol + 1;
967                                 io->bufsize -= io->bufpos - line;
968                                 return line;
969                         }
970                 }
972                 if (io_eof(io)) {
973                         if (io->bufsize) {
974                                 io->bufpos[io->bufsize] = 0;
975                                 io->bufsize = 0;
976                                 return io->bufpos;
977                         }
978                         return NULL;
979                 }
981                 if (!can_read)
982                         return NULL;
984                 if (io->bufsize > 0 && io->bufpos > io->buf)
985                         memmove(io->buf, io->bufpos, io->bufsize);
987                 if (io->bufalloc == io->bufsize) {
988                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
989                                 return NULL;
990                         io->bufalloc += BUFSIZ;
991                 }
993                 io->bufpos = io->buf;
994                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
995                 if (io_error(io))
996                         return NULL;
997                 io->bufsize += readsize;
998         }
1001 static bool
1002 io_write(struct io *io, const void *buf, size_t bufsize)
1004         size_t written = 0;
1006         while (!io_error(io) && written < bufsize) {
1007                 ssize_t size;
1009                 size = write(io->pipe, buf + written, bufsize - written);
1010                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1011                         continue;
1012                 else if (size == -1)
1013                         io->error = errno;
1014                 else
1015                         written += size;
1016         }
1018         return written == bufsize;
1021 static bool
1022 io_read_buf(struct io *io, char buf[], size_t bufsize)
1024         char *result = io_get(io, '\n', TRUE);
1026         if (result) {
1027                 result = chomp_string(result);
1028                 string_ncopy_do(buf, bufsize, result, strlen(result));
1029         }
1031         return io_done(io) && result;
1034 static bool
1035 io_run_buf(const char **argv, char buf[], size_t bufsize)
1037         struct io io = {};
1039         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1040             && io_read_buf(&io, buf, bufsize);
1043 static int
1044 io_load(struct io *io, const char *separators,
1045         int (*read_property)(char *, size_t, char *, size_t))
1047         char *name;
1048         int state = OK;
1050         if (!io_start(io))
1051                 return ERR;
1053         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054                 char *value;
1055                 size_t namelen;
1056                 size_t valuelen;
1058                 name = chomp_string(name);
1059                 namelen = strcspn(name, separators);
1061                 if (name[namelen]) {
1062                         name[namelen] = 0;
1063                         value = chomp_string(name + namelen + 1);
1064                         valuelen = strlen(value);
1066                 } else {
1067                         value = "";
1068                         valuelen = 0;
1069                 }
1071                 state = read_property(name, namelen, value, valuelen);
1072         }
1074         if (state != ERR && io_error(io))
1075                 state = ERR;
1076         io_done(io);
1078         return state;
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083             int (*read_property)(char *, size_t, char *, size_t))
1085         struct io io = {};
1087         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1088                 ? io_load(&io, separators, read_property) : ERR;
1092 /*
1093  * User requests
1094  */
1096 #define REQ_INFO \
1097         /* XXX: Keep the view request first and in sync with views[]. */ \
1098         REQ_GROUP("View switching") \
1099         REQ_(VIEW_MAIN,         "Show main view"), \
1100         REQ_(VIEW_DIFF,         "Show diff view"), \
1101         REQ_(VIEW_LOG,          "Show log view"), \
1102         REQ_(VIEW_TREE,         "Show tree view"), \
1103         REQ_(VIEW_BLOB,         "Show blob view"), \
1104         REQ_(VIEW_BLAME,        "Show blame view"), \
1105         REQ_(VIEW_BRANCH,       "Show branch view"), \
1106         REQ_(VIEW_HELP,         "Show help page"), \
1107         REQ_(VIEW_PAGER,        "Show pager view"), \
1108         REQ_(VIEW_STATUS,       "Show status view"), \
1109         REQ_(VIEW_STAGE,        "Show stage view"), \
1110         \
1111         REQ_GROUP("View manipulation") \
1112         REQ_(ENTER,             "Enter current line and scroll"), \
1113         REQ_(NEXT,              "Move to next"), \
1114         REQ_(PREVIOUS,          "Move to previous"), \
1115         REQ_(PARENT,            "Move to parent"), \
1116         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1117         REQ_(REFRESH,           "Reload and refresh"), \
1118         REQ_(MAXIMIZE,          "Maximize the current view"), \
1119         REQ_(VIEW_CLOSE,        "Close the current view"), \
1120         REQ_(QUIT,              "Close all views and quit"), \
1121         \
1122         REQ_GROUP("View specific requests") \
1123         REQ_(STATUS_UPDATE,     "Update file status"), \
1124         REQ_(STATUS_REVERT,     "Revert file changes"), \
1125         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1126         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1127         \
1128         REQ_GROUP("Cursor navigation") \
1129         REQ_(MOVE_UP,           "Move cursor one line up"), \
1130         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1131         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1132         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1133         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1134         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1135         \
1136         REQ_GROUP("Scrolling") \
1137         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1138         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1139         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1140         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1141         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1142         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1143         \
1144         REQ_GROUP("Searching") \
1145         REQ_(SEARCH,            "Search the view"), \
1146         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1147         REQ_(FIND_NEXT,         "Find next search match"), \
1148         REQ_(FIND_PREV,         "Find previous search match"), \
1149         \
1150         REQ_GROUP("Option manipulation") \
1151         REQ_(OPTIONS,           "Open option menu"), \
1152         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1153         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1154         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1155         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1156         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1157         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1158         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1159         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1160         \
1161         REQ_GROUP("Misc") \
1162         REQ_(PROMPT,            "Bring up the prompt"), \
1163         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1164         REQ_(SHOW_VERSION,      "Show version information"), \
1165         REQ_(STOP_LOADING,      "Stop all loading views"), \
1166         REQ_(EDIT,              "Open in editor"), \
1167         REQ_(NONE,              "Do nothing")
1170 /* User action requests. */
1171 enum request {
1172 #define REQ_GROUP(help)
1173 #define REQ_(req, help) REQ_##req
1175         /* Offset all requests to avoid conflicts with ncurses getch values. */
1176         REQ_UNKNOWN = KEY_MAX + 1,
1177         REQ_OFFSET,
1178         REQ_INFO
1180 #undef  REQ_GROUP
1181 #undef  REQ_
1182 };
1184 struct request_info {
1185         enum request request;
1186         const char *name;
1187         int namelen;
1188         const char *help;
1189 };
1191 static const struct request_info req_info[] = {
1192 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1193 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1194         REQ_INFO
1195 #undef  REQ_GROUP
1196 #undef  REQ_
1197 };
1199 static enum request
1200 get_request(const char *name)
1202         int namelen = strlen(name);
1203         int i;
1205         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1206                 if (enum_equals(req_info[i], name, namelen))
1207                         return req_info[i].request;
1209         return REQ_UNKNOWN;
1213 /*
1214  * Options
1215  */
1217 /* Option and state variables. */
1218 static enum date opt_date               = DATE_DEFAULT;
1219 static enum author opt_author           = AUTHOR_DEFAULT;
1220 static bool opt_line_number             = FALSE;
1221 static bool opt_line_graphics           = TRUE;
1222 static bool opt_rev_graph               = FALSE;
1223 static bool opt_show_refs               = TRUE;
1224 static int opt_num_interval             = 5;
1225 static double opt_hscroll               = 0.50;
1226 static double opt_scale_split_view      = 2.0 / 3.0;
1227 static int opt_tab_size                 = 8;
1228 static int opt_author_cols              = AUTHOR_COLS;
1229 static char opt_path[SIZEOF_STR]        = "";
1230 static char opt_file[SIZEOF_STR]        = "";
1231 static char opt_ref[SIZEOF_REF]         = "";
1232 static char opt_head[SIZEOF_REF]        = "";
1233 static char opt_remote[SIZEOF_REF]      = "";
1234 static char opt_encoding[20]            = "UTF-8";
1235 static iconv_t opt_iconv_in             = ICONV_NONE;
1236 static iconv_t opt_iconv_out            = ICONV_NONE;
1237 static char opt_search[SIZEOF_STR]      = "";
1238 static char opt_cdup[SIZEOF_STR]        = "";
1239 static char opt_prefix[SIZEOF_STR]      = "";
1240 static char opt_git_dir[SIZEOF_STR]     = "";
1241 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1242 static char opt_editor[SIZEOF_STR]      = "";
1243 static FILE *opt_tty                    = NULL;
1245 #define is_initial_commit()     (!get_ref_head())
1246 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1249 /*
1250  * Line-oriented content detection.
1251  */
1253 #define LINE_INFO \
1254 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1255 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1256 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1257 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1258 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1259 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1261 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1262 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1263 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1264 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1265 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1266 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1267 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1268 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1269 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1270 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1271 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1272 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1273 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1275 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1276 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1277 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1278 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1279 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1280 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1281 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1282 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1283 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1284 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1285 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1286 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1287 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1288 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1289 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1290 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1291 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1292 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1293 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1294 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1295 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1296 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1297 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1298 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1299 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1300 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1301 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1302 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1303 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1304 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1305 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1306 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1307 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1308 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1309 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1310 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1311 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1312 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1314 enum line_type {
1315 #define LINE(type, line, fg, bg, attr) \
1316         LINE_##type
1317         LINE_INFO,
1318         LINE_NONE
1319 #undef  LINE
1320 };
1322 struct line_info {
1323         const char *name;       /* Option name. */
1324         int namelen;            /* Size of option name. */
1325         const char *line;       /* The start of line to match. */
1326         int linelen;            /* Size of string to match. */
1327         int fg, bg, attr;       /* Color and text attributes for the lines. */
1328 };
1330 static struct line_info line_info[] = {
1331 #define LINE(type, line, fg, bg, attr) \
1332         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1333         LINE_INFO
1334 #undef  LINE
1335 };
1337 static enum line_type
1338 get_line_type(const char *line)
1340         int linelen = strlen(line);
1341         enum line_type type;
1343         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1344                 /* Case insensitive search matches Signed-off-by lines better. */
1345                 if (linelen >= line_info[type].linelen &&
1346                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1347                         return type;
1349         return LINE_DEFAULT;
1352 static inline int
1353 get_line_attr(enum line_type type)
1355         assert(type < ARRAY_SIZE(line_info));
1356         return COLOR_PAIR(type) | line_info[type].attr;
1359 static struct line_info *
1360 get_line_info(const char *name)
1362         size_t namelen = strlen(name);
1363         enum line_type type;
1365         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1366                 if (enum_equals(line_info[type], name, namelen))
1367                         return &line_info[type];
1369         return NULL;
1372 static void
1373 init_colors(void)
1375         int default_bg = line_info[LINE_DEFAULT].bg;
1376         int default_fg = line_info[LINE_DEFAULT].fg;
1377         enum line_type type;
1379         start_color();
1381         if (assume_default_colors(default_fg, default_bg) == ERR) {
1382                 default_bg = COLOR_BLACK;
1383                 default_fg = COLOR_WHITE;
1384         }
1386         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1387                 struct line_info *info = &line_info[type];
1388                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1389                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1391                 init_pair(type, fg, bg);
1392         }
1395 struct line {
1396         enum line_type type;
1398         /* State flags */
1399         unsigned int selected:1;
1400         unsigned int dirty:1;
1401         unsigned int cleareol:1;
1402         unsigned int other:16;
1404         void *data;             /* User data */
1405 };
1408 /*
1409  * Keys
1410  */
1412 struct keybinding {
1413         int alias;
1414         enum request request;
1415 };
1417 static struct keybinding default_keybindings[] = {
1418         /* View switching */
1419         { 'm',          REQ_VIEW_MAIN },
1420         { 'd',          REQ_VIEW_DIFF },
1421         { 'l',          REQ_VIEW_LOG },
1422         { 't',          REQ_VIEW_TREE },
1423         { 'f',          REQ_VIEW_BLOB },
1424         { 'B',          REQ_VIEW_BLAME },
1425         { 'H',          REQ_VIEW_BRANCH },
1426         { 'p',          REQ_VIEW_PAGER },
1427         { 'h',          REQ_VIEW_HELP },
1428         { 'S',          REQ_VIEW_STATUS },
1429         { 'c',          REQ_VIEW_STAGE },
1431         /* View manipulation */
1432         { 'q',          REQ_VIEW_CLOSE },
1433         { KEY_TAB,      REQ_VIEW_NEXT },
1434         { KEY_RETURN,   REQ_ENTER },
1435         { KEY_UP,       REQ_PREVIOUS },
1436         { KEY_DOWN,     REQ_NEXT },
1437         { 'R',          REQ_REFRESH },
1438         { KEY_F(5),     REQ_REFRESH },
1439         { 'O',          REQ_MAXIMIZE },
1441         /* Cursor navigation */
1442         { 'k',          REQ_MOVE_UP },
1443         { 'j',          REQ_MOVE_DOWN },
1444         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1445         { KEY_END,      REQ_MOVE_LAST_LINE },
1446         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1447         { ' ',          REQ_MOVE_PAGE_DOWN },
1448         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1449         { 'b',          REQ_MOVE_PAGE_UP },
1450         { '-',          REQ_MOVE_PAGE_UP },
1452         /* Scrolling */
1453         { KEY_LEFT,     REQ_SCROLL_LEFT },
1454         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1455         { KEY_IC,       REQ_SCROLL_LINE_UP },
1456         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1457         { 'w',          REQ_SCROLL_PAGE_UP },
1458         { 's',          REQ_SCROLL_PAGE_DOWN },
1460         /* Searching */
1461         { '/',          REQ_SEARCH },
1462         { '?',          REQ_SEARCH_BACK },
1463         { 'n',          REQ_FIND_NEXT },
1464         { 'N',          REQ_FIND_PREV },
1466         /* Misc */
1467         { 'Q',          REQ_QUIT },
1468         { 'z',          REQ_STOP_LOADING },
1469         { 'v',          REQ_SHOW_VERSION },
1470         { 'r',          REQ_SCREEN_REDRAW },
1471         { 'o',          REQ_OPTIONS },
1472         { '.',          REQ_TOGGLE_LINENO },
1473         { 'D',          REQ_TOGGLE_DATE },
1474         { 'A',          REQ_TOGGLE_AUTHOR },
1475         { 'g',          REQ_TOGGLE_REV_GRAPH },
1476         { 'F',          REQ_TOGGLE_REFS },
1477         { 'I',          REQ_TOGGLE_SORT_ORDER },
1478         { 'i',          REQ_TOGGLE_SORT_FIELD },
1479         { ':',          REQ_PROMPT },
1480         { 'u',          REQ_STATUS_UPDATE },
1481         { '!',          REQ_STATUS_REVERT },
1482         { 'M',          REQ_STATUS_MERGE },
1483         { '@',          REQ_STAGE_NEXT },
1484         { ',',          REQ_PARENT },
1485         { 'e',          REQ_EDIT },
1486 };
1488 #define KEYMAP_INFO \
1489         KEYMAP_(GENERIC), \
1490         KEYMAP_(MAIN), \
1491         KEYMAP_(DIFF), \
1492         KEYMAP_(LOG), \
1493         KEYMAP_(TREE), \
1494         KEYMAP_(BLOB), \
1495         KEYMAP_(BLAME), \
1496         KEYMAP_(BRANCH), \
1497         KEYMAP_(PAGER), \
1498         KEYMAP_(HELP), \
1499         KEYMAP_(STATUS), \
1500         KEYMAP_(STAGE)
1502 enum keymap {
1503 #define KEYMAP_(name) KEYMAP_##name
1504         KEYMAP_INFO
1505 #undef  KEYMAP_
1506 };
1508 static const struct enum_map keymap_table[] = {
1509 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1510         KEYMAP_INFO
1511 #undef  KEYMAP_
1512 };
1514 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1516 struct keybinding_table {
1517         struct keybinding *data;
1518         size_t size;
1519 };
1521 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1523 static void
1524 add_keybinding(enum keymap keymap, enum request request, int key)
1526         struct keybinding_table *table = &keybindings[keymap];
1527         size_t i;
1529         for (i = 0; i < keybindings[keymap].size; i++) {
1530                 if (keybindings[keymap].data[i].alias == key) {
1531                         keybindings[keymap].data[i].request = request;
1532                         return;
1533                 }
1534         }
1536         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1537         if (!table->data)
1538                 die("Failed to allocate keybinding");
1539         table->data[table->size].alias = key;
1540         table->data[table->size++].request = request;
1542         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1543                 int i;
1545                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1546                         if (default_keybindings[i].alias == key)
1547                                 default_keybindings[i].request = REQ_NONE;
1548         }
1551 /* Looks for a key binding first in the given map, then in the generic map, and
1552  * lastly in the default keybindings. */
1553 static enum request
1554 get_keybinding(enum keymap keymap, int key)
1556         size_t i;
1558         for (i = 0; i < keybindings[keymap].size; i++)
1559                 if (keybindings[keymap].data[i].alias == key)
1560                         return keybindings[keymap].data[i].request;
1562         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1563                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1564                         return keybindings[KEYMAP_GENERIC].data[i].request;
1566         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1567                 if (default_keybindings[i].alias == key)
1568                         return default_keybindings[i].request;
1570         return (enum request) key;
1574 struct key {
1575         const char *name;
1576         int value;
1577 };
1579 static const struct key key_table[] = {
1580         { "Enter",      KEY_RETURN },
1581         { "Space",      ' ' },
1582         { "Backspace",  KEY_BACKSPACE },
1583         { "Tab",        KEY_TAB },
1584         { "Escape",     KEY_ESC },
1585         { "Left",       KEY_LEFT },
1586         { "Right",      KEY_RIGHT },
1587         { "Up",         KEY_UP },
1588         { "Down",       KEY_DOWN },
1589         { "Insert",     KEY_IC },
1590         { "Delete",     KEY_DC },
1591         { "Hash",       '#' },
1592         { "Home",       KEY_HOME },
1593         { "End",        KEY_END },
1594         { "PageUp",     KEY_PPAGE },
1595         { "PageDown",   KEY_NPAGE },
1596         { "F1",         KEY_F(1) },
1597         { "F2",         KEY_F(2) },
1598         { "F3",         KEY_F(3) },
1599         { "F4",         KEY_F(4) },
1600         { "F5",         KEY_F(5) },
1601         { "F6",         KEY_F(6) },
1602         { "F7",         KEY_F(7) },
1603         { "F8",         KEY_F(8) },
1604         { "F9",         KEY_F(9) },
1605         { "F10",        KEY_F(10) },
1606         { "F11",        KEY_F(11) },
1607         { "F12",        KEY_F(12) },
1608 };
1610 static int
1611 get_key_value(const char *name)
1613         int i;
1615         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1616                 if (!strcasecmp(key_table[i].name, name))
1617                         return key_table[i].value;
1619         if (strlen(name) == 1 && isprint(*name))
1620                 return (int) *name;
1622         return ERR;
1625 static const char *
1626 get_key_name(int key_value)
1628         static char key_char[] = "'X'";
1629         const char *seq = NULL;
1630         int key;
1632         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1633                 if (key_table[key].value == key_value)
1634                         seq = key_table[key].name;
1636         if (seq == NULL &&
1637             key_value < 127 &&
1638             isprint(key_value)) {
1639                 key_char[1] = (char) key_value;
1640                 seq = key_char;
1641         }
1643         return seq ? seq : "(no key)";
1646 static bool
1647 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1649         const char *sep = *pos > 0 ? ", " : "";
1650         const char *keyname = get_key_name(keybinding->alias);
1652         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1655 static bool
1656 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1657                            enum keymap keymap, bool all)
1659         int i;
1661         for (i = 0; i < keybindings[keymap].size; i++) {
1662                 if (keybindings[keymap].data[i].request == request) {
1663                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1664                                 return FALSE;
1665                         if (!all)
1666                                 break;
1667                 }
1668         }
1670         return TRUE;
1673 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1675 static const char *
1676 get_keys(enum keymap keymap, enum request request, bool all)
1678         static char buf[BUFSIZ];
1679         size_t pos = 0;
1680         int i;
1682         buf[pos] = 0;
1684         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1685                 return "Too many keybindings!";
1686         if (pos > 0 && !all)
1687                 return buf;
1689         if (keymap != KEYMAP_GENERIC) {
1690                 /* Only the generic keymap includes the default keybindings when
1691                  * listing all keys. */
1692                 if (all)
1693                         return buf;
1695                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1696                         return "Too many keybindings!";
1697                 if (pos)
1698                         return buf;
1699         }
1701         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1702                 if (default_keybindings[i].request == request) {
1703                         if (!append_key(buf, &pos, &default_keybindings[i]))
1704                                 return "Too many keybindings!";
1705                         if (!all)
1706                                 return buf;
1707                 }
1708         }
1710         return buf;
1713 struct run_request {
1714         enum keymap keymap;
1715         int key;
1716         const char *argv[SIZEOF_ARG];
1717 };
1719 static struct run_request *run_request;
1720 static size_t run_requests;
1722 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1724 static enum request
1725 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1727         struct run_request *req;
1729         if (argc >= ARRAY_SIZE(req->argv) - 1)
1730                 return REQ_NONE;
1732         if (!realloc_run_requests(&run_request, run_requests, 1))
1733                 return REQ_NONE;
1735         req = &run_request[run_requests];
1736         req->keymap = keymap;
1737         req->key = key;
1738         req->argv[0] = NULL;
1740         if (!format_argv(req->argv, argv, FORMAT_NONE))
1741                 return REQ_NONE;
1743         return REQ_NONE + ++run_requests;
1746 static struct run_request *
1747 get_run_request(enum request request)
1749         if (request <= REQ_NONE)
1750                 return NULL;
1751         return &run_request[request - REQ_NONE - 1];
1754 static void
1755 add_builtin_run_requests(void)
1757         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1758         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1759         const char *commit[] = { "git", "commit", NULL };
1760         const char *gc[] = { "git", "gc", NULL };
1761         struct {
1762                 enum keymap keymap;
1763                 int key;
1764                 int argc;
1765                 const char **argv;
1766         } reqs[] = {
1767                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1768                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1769                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1770                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1771         };
1772         int i;
1774         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1775                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1777                 if (req != reqs[i].key)
1778                         continue;
1779                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1780                 if (req != REQ_NONE)
1781                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1782         }
1785 /*
1786  * User config file handling.
1787  */
1789 static int   config_lineno;
1790 static bool  config_errors;
1791 static const char *config_msg;
1793 static const struct enum_map color_map[] = {
1794 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1795         COLOR_MAP(DEFAULT),
1796         COLOR_MAP(BLACK),
1797         COLOR_MAP(BLUE),
1798         COLOR_MAP(CYAN),
1799         COLOR_MAP(GREEN),
1800         COLOR_MAP(MAGENTA),
1801         COLOR_MAP(RED),
1802         COLOR_MAP(WHITE),
1803         COLOR_MAP(YELLOW),
1804 };
1806 static const struct enum_map attr_map[] = {
1807 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1808         ATTR_MAP(NORMAL),
1809         ATTR_MAP(BLINK),
1810         ATTR_MAP(BOLD),
1811         ATTR_MAP(DIM),
1812         ATTR_MAP(REVERSE),
1813         ATTR_MAP(STANDOUT),
1814         ATTR_MAP(UNDERLINE),
1815 };
1817 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1819 static int parse_step(double *opt, const char *arg)
1821         *opt = atoi(arg);
1822         if (!strchr(arg, '%'))
1823                 return OK;
1825         /* "Shift down" so 100% and 1 does not conflict. */
1826         *opt = (*opt - 1) / 100;
1827         if (*opt >= 1.0) {
1828                 *opt = 0.99;
1829                 config_msg = "Step value larger than 100%";
1830                 return ERR;
1831         }
1832         if (*opt < 0.0) {
1833                 *opt = 1;
1834                 config_msg = "Invalid step value";
1835                 return ERR;
1836         }
1837         return OK;
1840 static int
1841 parse_int(int *opt, const char *arg, int min, int max)
1843         int value = atoi(arg);
1845         if (min <= value && value <= max) {
1846                 *opt = value;
1847                 return OK;
1848         }
1850         config_msg = "Integer value out of bound";
1851         return ERR;
1854 static bool
1855 set_color(int *color, const char *name)
1857         if (map_enum(color, color_map, name))
1858                 return TRUE;
1859         if (!prefixcmp(name, "color"))
1860                 return parse_int(color, name + 5, 0, 255) == OK;
1861         return FALSE;
1864 /* Wants: object fgcolor bgcolor [attribute] */
1865 static int
1866 option_color_command(int argc, const char *argv[])
1868         struct line_info *info;
1870         if (argc < 3) {
1871                 config_msg = "Wrong number of arguments given to color command";
1872                 return ERR;
1873         }
1875         info = get_line_info(argv[0]);
1876         if (!info) {
1877                 static const struct enum_map obsolete[] = {
1878                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1879                         ENUM_MAP("main-date",   LINE_DATE),
1880                         ENUM_MAP("main-author", LINE_AUTHOR),
1881                 };
1882                 int index;
1884                 if (!map_enum(&index, obsolete, argv[0])) {
1885                         config_msg = "Unknown color name";
1886                         return ERR;
1887                 }
1888                 info = &line_info[index];
1889         }
1891         if (!set_color(&info->fg, argv[1]) ||
1892             !set_color(&info->bg, argv[2])) {
1893                 config_msg = "Unknown color";
1894                 return ERR;
1895         }
1897         info->attr = 0;
1898         while (argc-- > 3) {
1899                 int attr;
1901                 if (!set_attribute(&attr, argv[argc])) {
1902                         config_msg = "Unknown attribute";
1903                         return ERR;
1904                 }
1905                 info->attr |= attr;
1906         }
1908         return OK;
1911 static int parse_bool(bool *opt, const char *arg)
1913         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1914                 ? TRUE : FALSE;
1915         return OK;
1918 static int parse_enum_do(unsigned int *opt, const char *arg,
1919                          const struct enum_map *map, size_t map_size)
1921         bool is_true;
1923         assert(map_size > 1);
1925         if (map_enum_do(map, map_size, (int *) opt, arg))
1926                 return OK;
1928         if (parse_bool(&is_true, arg) != OK)
1929                 return ERR;
1931         *opt = is_true ? map[1].value : map[0].value;
1932         return OK;
1935 #define parse_enum(opt, arg, map) \
1936         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1938 static int
1939 parse_string(char *opt, const char *arg, size_t optsize)
1941         int arglen = strlen(arg);
1943         switch (arg[0]) {
1944         case '\"':
1945         case '\'':
1946                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1947                         config_msg = "Unmatched quotation";
1948                         return ERR;
1949                 }
1950                 arg += 1; arglen -= 2;
1951         default:
1952                 string_ncopy_do(opt, optsize, arg, arglen);
1953                 return OK;
1954         }
1957 /* Wants: name = value */
1958 static int
1959 option_set_command(int argc, const char *argv[])
1961         if (argc != 3) {
1962                 config_msg = "Wrong number of arguments given to set command";
1963                 return ERR;
1964         }
1966         if (strcmp(argv[1], "=")) {
1967                 config_msg = "No value assigned";
1968                 return ERR;
1969         }
1971         if (!strcmp(argv[0], "show-author"))
1972                 return parse_enum(&opt_author, argv[2], author_map);
1974         if (!strcmp(argv[0], "show-date"))
1975                 return parse_enum(&opt_date, argv[2], date_map);
1977         if (!strcmp(argv[0], "show-rev-graph"))
1978                 return parse_bool(&opt_rev_graph, argv[2]);
1980         if (!strcmp(argv[0], "show-refs"))
1981                 return parse_bool(&opt_show_refs, argv[2]);
1983         if (!strcmp(argv[0], "show-line-numbers"))
1984                 return parse_bool(&opt_line_number, argv[2]);
1986         if (!strcmp(argv[0], "line-graphics"))
1987                 return parse_bool(&opt_line_graphics, argv[2]);
1989         if (!strcmp(argv[0], "line-number-interval"))
1990                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1992         if (!strcmp(argv[0], "author-width"))
1993                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1995         if (!strcmp(argv[0], "horizontal-scroll"))
1996                 return parse_step(&opt_hscroll, argv[2]);
1998         if (!strcmp(argv[0], "split-view-height"))
1999                 return parse_step(&opt_scale_split_view, argv[2]);
2001         if (!strcmp(argv[0], "tab-size"))
2002                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2004         if (!strcmp(argv[0], "commit-encoding"))
2005                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2007         config_msg = "Unknown variable name";
2008         return ERR;
2011 /* Wants: mode request key */
2012 static int
2013 option_bind_command(int argc, const char *argv[])
2015         enum request request;
2016         int keymap = -1;
2017         int key;
2019         if (argc < 3) {
2020                 config_msg = "Wrong number of arguments given to bind command";
2021                 return ERR;
2022         }
2024         if (!set_keymap(&keymap, argv[0])) {
2025                 config_msg = "Unknown key map";
2026                 return ERR;
2027         }
2029         key = get_key_value(argv[1]);
2030         if (key == ERR) {
2031                 config_msg = "Unknown key";
2032                 return ERR;
2033         }
2035         request = get_request(argv[2]);
2036         if (request == REQ_UNKNOWN) {
2037                 static const struct enum_map obsolete[] = {
2038                         ENUM_MAP("cherry-pick",         REQ_NONE),
2039                         ENUM_MAP("screen-resize",       REQ_NONE),
2040                         ENUM_MAP("tree-parent",         REQ_PARENT),
2041                 };
2042                 int alias;
2044                 if (map_enum(&alias, obsolete, argv[2])) {
2045                         if (alias != REQ_NONE)
2046                                 add_keybinding(keymap, alias, key);
2047                         config_msg = "Obsolete request name";
2048                         return ERR;
2049                 }
2050         }
2051         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2052                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2053         if (request == REQ_UNKNOWN) {
2054                 config_msg = "Unknown request name";
2055                 return ERR;
2056         }
2058         add_keybinding(keymap, request, key);
2060         return OK;
2063 static int
2064 set_option(const char *opt, char *value)
2066         const char *argv[SIZEOF_ARG];
2067         int argc = 0;
2069         if (!argv_from_string(argv, &argc, value)) {
2070                 config_msg = "Too many option arguments";
2071                 return ERR;
2072         }
2074         if (!strcmp(opt, "color"))
2075                 return option_color_command(argc, argv);
2077         if (!strcmp(opt, "set"))
2078                 return option_set_command(argc, argv);
2080         if (!strcmp(opt, "bind"))
2081                 return option_bind_command(argc, argv);
2083         config_msg = "Unknown option command";
2084         return ERR;
2087 static int
2088 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2090         int status = OK;
2092         config_lineno++;
2093         config_msg = "Internal error";
2095         /* Check for comment markers, since read_properties() will
2096          * only ensure opt and value are split at first " \t". */
2097         optlen = strcspn(opt, "#");
2098         if (optlen == 0)
2099                 return OK;
2101         if (opt[optlen] != 0) {
2102                 config_msg = "No option value";
2103                 status = ERR;
2105         }  else {
2106                 /* Look for comment endings in the value. */
2107                 size_t len = strcspn(value, "#");
2109                 if (len < valuelen) {
2110                         valuelen = len;
2111                         value[valuelen] = 0;
2112                 }
2114                 status = set_option(opt, value);
2115         }
2117         if (status == ERR) {
2118                 warn("Error on line %d, near '%.*s': %s",
2119                      config_lineno, (int) optlen, opt, config_msg);
2120                 config_errors = TRUE;
2121         }
2123         /* Always keep going if errors are encountered. */
2124         return OK;
2127 static void
2128 load_option_file(const char *path)
2130         struct io io = {};
2132         /* It's OK that the file doesn't exist. */
2133         if (!io_open(&io, "%s", path))
2134                 return;
2136         config_lineno = 0;
2137         config_errors = FALSE;
2139         if (io_load(&io, " \t", read_option) == ERR ||
2140             config_errors == TRUE)
2141                 warn("Errors while loading %s.", path);
2144 static int
2145 load_options(void)
2147         const char *home = getenv("HOME");
2148         const char *tigrc_user = getenv("TIGRC_USER");
2149         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2150         char buf[SIZEOF_STR];
2152         if (!tigrc_system)
2153                 tigrc_system = SYSCONFDIR "/tigrc";
2154         load_option_file(tigrc_system);
2156         if (!tigrc_user) {
2157                 if (!home || !string_format(buf, "%s/.tigrc", home))
2158                         return ERR;
2159                 tigrc_user = buf;
2160         }
2161         load_option_file(tigrc_user);
2163         /* Add _after_ loading config files to avoid adding run requests
2164          * that conflict with keybindings. */
2165         add_builtin_run_requests();
2167         return OK;
2171 /*
2172  * The viewer
2173  */
2175 struct view;
2176 struct view_ops;
2178 /* The display array of active views and the index of the current view. */
2179 static struct view *display[2];
2180 static unsigned int current_view;
2182 #define foreach_displayed_view(view, i) \
2183         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2185 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2187 /* Current head and commit ID */
2188 static char ref_blob[SIZEOF_REF]        = "";
2189 static char ref_commit[SIZEOF_REF]      = "HEAD";
2190 static char ref_head[SIZEOF_REF]        = "HEAD";
2191 static char ref_branch[SIZEOF_REF]      = "";
2193 enum view_type {
2194         VIEW_MAIN,
2195         VIEW_DIFF,
2196         VIEW_LOG,
2197         VIEW_TREE,
2198         VIEW_BLOB,
2199         VIEW_BLAME,
2200         VIEW_BRANCH,
2201         VIEW_HELP,
2202         VIEW_PAGER,
2203         VIEW_STATUS,
2204         VIEW_STAGE,
2205 };
2207 struct view {
2208         enum view_type type;    /* View type */
2209         const char *name;       /* View name */
2210         const char *cmd_env;    /* Command line set via environment */
2211         const char *id;         /* Points to either of ref_{head,commit,blob} */
2213         struct view_ops *ops;   /* View operations */
2215         enum keymap keymap;     /* What keymap does this view have */
2216         bool git_dir;           /* Whether the view requires a git directory. */
2217         bool refresh;           /* Whether the view supports refreshing. */
2219         char ref[SIZEOF_REF];   /* Hovered commit reference */
2220         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2222         int height, width;      /* The width and height of the main window */
2223         WINDOW *win;            /* The main window */
2224         WINDOW *title;          /* The title window living below the main window */
2226         /* Navigation */
2227         unsigned long offset;   /* Offset of the window top */
2228         unsigned long yoffset;  /* Offset from the window side. */
2229         unsigned long lineno;   /* Current line number */
2230         unsigned long p_offset; /* Previous offset of the window top */
2231         unsigned long p_yoffset;/* Previous offset from the window side */
2232         unsigned long p_lineno; /* Previous current line number */
2233         bool p_restore;         /* Should the previous position be restored. */
2235         /* Searching */
2236         char grep[SIZEOF_STR];  /* Search string */
2237         regex_t *regex;         /* Pre-compiled regexp */
2239         /* If non-NULL, points to the view that opened this view. If this view
2240          * is closed tig will switch back to the parent view. */
2241         struct view *parent;
2242         struct view *prev;
2244         /* Buffering */
2245         size_t lines;           /* Total number of lines */
2246         struct line *line;      /* Line index */
2247         unsigned int digits;    /* Number of digits in the lines member. */
2249         /* Drawing */
2250         struct line *curline;   /* Line currently being drawn. */
2251         enum line_type curtype; /* Attribute currently used for drawing. */
2252         unsigned long col;      /* Column when drawing. */
2253         bool has_scrolled;      /* View was scrolled. */
2255         /* Loading */
2256         struct io io;
2257         struct io *pipe;
2258         time_t start_time;
2259         time_t update_secs;
2260 };
2262 struct view_ops {
2263         /* What type of content being displayed. Used in the title bar. */
2264         const char *type;
2265         /* Default command arguments. */
2266         const char **argv;
2267         /* Open and reads in all view content. */
2268         bool (*open)(struct view *view);
2269         /* Read one line; updates view->line. */
2270         bool (*read)(struct view *view, char *data);
2271         /* Draw one line; @lineno must be < view->height. */
2272         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2273         /* Depending on view handle a special requests. */
2274         enum request (*request)(struct view *view, enum request request, struct line *line);
2275         /* Search for regexp in a line. */
2276         bool (*grep)(struct view *view, struct line *line);
2277         /* Select line */
2278         void (*select)(struct view *view, struct line *line);
2279         /* Prepare view for loading */
2280         bool (*prepare)(struct view *view);
2281 };
2283 static struct view_ops blame_ops;
2284 static struct view_ops blob_ops;
2285 static struct view_ops diff_ops;
2286 static struct view_ops help_ops;
2287 static struct view_ops log_ops;
2288 static struct view_ops main_ops;
2289 static struct view_ops pager_ops;
2290 static struct view_ops stage_ops;
2291 static struct view_ops status_ops;
2292 static struct view_ops tree_ops;
2293 static struct view_ops branch_ops;
2295 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2296         { type, name, #env, ref, ops, map, git, refresh }
2298 #define VIEW_(id, name, ops, git, refresh, ref) \
2299         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2301 static struct view views[] = {
2302         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  TRUE,  ref_head),
2303         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  FALSE, ref_commit),
2304         VIEW_(LOG,    "log",    &log_ops,    TRUE,  TRUE,  ref_head),
2305         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  FALSE, ref_commit),
2306         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  FALSE, ref_blob),
2307         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  FALSE, ref_commit),
2308         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  TRUE,  ref_head),
2309         VIEW_(HELP,   "help",   &help_ops,   FALSE, FALSE, ""),
2310         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, FALSE, "stdin"),
2311         VIEW_(STATUS, "status", &status_ops, TRUE,  TRUE,  ""),
2312         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  TRUE,  ""),
2313 };
2315 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2317 #define foreach_view(view, i) \
2318         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2320 #define view_is_displayed(view) \
2321         (view == display[0] || view == display[1])
2323 static inline void
2324 set_view_attr(struct view *view, enum line_type type)
2326         if (!view->curline->selected && view->curtype != type) {
2327                 (void) wattrset(view->win, get_line_attr(type));
2328                 wchgat(view->win, -1, 0, type, NULL);
2329                 view->curtype = type;
2330         }
2333 static int
2334 draw_chars(struct view *view, enum line_type type, const char *string,
2335            int max_len, bool use_tilde)
2337         static char out_buffer[BUFSIZ * 2];
2338         int len = 0;
2339         int col = 0;
2340         int trimmed = FALSE;
2341         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2343         if (max_len <= 0)
2344                 return 0;
2346         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2348         set_view_attr(view, type);
2349         if (len > 0) {
2350                 if (opt_iconv_out != ICONV_NONE) {
2351                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2352                         size_t inlen = len + 1;
2354                         char *outbuf = out_buffer;
2355                         size_t outlen = sizeof(out_buffer);
2357                         size_t ret;
2359                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2360                         if (ret != (size_t) -1) {
2361                                 string = out_buffer;
2362                                 len = sizeof(out_buffer) - outlen;
2363                         }
2364                 }
2366                 waddnstr(view->win, string, len);
2367         }
2368         if (trimmed && use_tilde) {
2369                 set_view_attr(view, LINE_DELIMITER);
2370                 waddch(view->win, '~');
2371                 col++;
2372         }
2374         return col;
2377 static int
2378 draw_space(struct view *view, enum line_type type, int max, int spaces)
2380         static char space[] = "                    ";
2381         int col = 0;
2383         spaces = MIN(max, spaces);
2385         while (spaces > 0) {
2386                 int len = MIN(spaces, sizeof(space) - 1);
2388                 col += draw_chars(view, type, space, len, FALSE);
2389                 spaces -= len;
2390         }
2392         return col;
2395 static bool
2396 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2398         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2399         return view->width + view->yoffset <= view->col;
2402 static bool
2403 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2405         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2406         int max = view->width + view->yoffset - view->col;
2407         int i;
2409         if (max < size)
2410                 size = max;
2412         set_view_attr(view, type);
2413         /* Using waddch() instead of waddnstr() ensures that
2414          * they'll be rendered correctly for the cursor line. */
2415         for (i = skip; i < size; i++)
2416                 waddch(view->win, graphic[i]);
2418         view->col += size;
2419         if (size < max && skip <= size)
2420                 waddch(view->win, ' ');
2421         view->col++;
2423         return view->width + view->yoffset <= view->col;
2426 static bool
2427 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2429         int max = MIN(view->width + view->yoffset - view->col, len);
2430         int col;
2432         if (text)
2433                 col = draw_chars(view, type, text, max - 1, trim);
2434         else
2435                 col = draw_space(view, type, max - 1, max - 1);
2437         view->col += col;
2438         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2439         return view->width + view->yoffset <= view->col;
2442 static bool
2443 draw_date(struct view *view, struct time *time)
2445         const char *date = mkdate(time, opt_date);
2446         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2448         return draw_field(view, LINE_DATE, date, cols, FALSE);
2451 static bool
2452 draw_author(struct view *view, const char *author)
2454         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2455         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2457         if (abbreviate && author)
2458                 author = get_author_initials(author);
2460         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2463 static bool
2464 draw_mode(struct view *view, mode_t mode)
2466         const char *str;
2468         if (S_ISDIR(mode))
2469                 str = "drwxr-xr-x";
2470         else if (S_ISLNK(mode))
2471                 str = "lrwxrwxrwx";
2472         else if (S_ISGITLINK(mode))
2473                 str = "m---------";
2474         else if (S_ISREG(mode) && mode & S_IXUSR)
2475                 str = "-rwxr-xr-x";
2476         else if (S_ISREG(mode))
2477                 str = "-rw-r--r--";
2478         else
2479                 str = "----------";
2481         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2484 static bool
2485 draw_lineno(struct view *view, unsigned int lineno)
2487         char number[10];
2488         int digits3 = view->digits < 3 ? 3 : view->digits;
2489         int max = MIN(view->width + view->yoffset - view->col, digits3);
2490         char *text = NULL;
2491         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2493         lineno += view->offset + 1;
2494         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2495                 static char fmt[] = "%1ld";
2497                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2498                 if (string_format(number, fmt, lineno))
2499                         text = number;
2500         }
2501         if (text)
2502                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2503         else
2504                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2505         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2508 static bool
2509 draw_view_line(struct view *view, unsigned int lineno)
2511         struct line *line;
2512         bool selected = (view->offset + lineno == view->lineno);
2514         assert(view_is_displayed(view));
2516         if (view->offset + lineno >= view->lines)
2517                 return FALSE;
2519         line = &view->line[view->offset + lineno];
2521         wmove(view->win, lineno, 0);
2522         if (line->cleareol)
2523                 wclrtoeol(view->win);
2524         view->col = 0;
2525         view->curline = line;
2526         view->curtype = LINE_NONE;
2527         line->selected = FALSE;
2528         line->dirty = line->cleareol = 0;
2530         if (selected) {
2531                 set_view_attr(view, LINE_CURSOR);
2532                 line->selected = TRUE;
2533                 view->ops->select(view, line);
2534         }
2536         return view->ops->draw(view, line, lineno);
2539 static void
2540 redraw_view_dirty(struct view *view)
2542         bool dirty = FALSE;
2543         int lineno;
2545         for (lineno = 0; lineno < view->height; lineno++) {
2546                 if (view->offset + lineno >= view->lines)
2547                         break;
2548                 if (!view->line[view->offset + lineno].dirty)
2549                         continue;
2550                 dirty = TRUE;
2551                 if (!draw_view_line(view, lineno))
2552                         break;
2553         }
2555         if (!dirty)
2556                 return;
2557         wnoutrefresh(view->win);
2560 static void
2561 redraw_view_from(struct view *view, int lineno)
2563         assert(0 <= lineno && lineno < view->height);
2565         for (; lineno < view->height; lineno++) {
2566                 if (!draw_view_line(view, lineno))
2567                         break;
2568         }
2570         wnoutrefresh(view->win);
2573 static void
2574 redraw_view(struct view *view)
2576         werase(view->win);
2577         redraw_view_from(view, 0);
2581 static void
2582 update_view_title(struct view *view)
2584         char buf[SIZEOF_STR];
2585         char state[SIZEOF_STR];
2586         size_t bufpos = 0, statelen = 0;
2588         assert(view_is_displayed(view));
2590         if (view->type != VIEW_STATUS && view->lines) {
2591                 unsigned int view_lines = view->offset + view->height;
2592                 unsigned int lines = view->lines
2593                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2594                                    : 0;
2596                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2597                                    view->ops->type,
2598                                    view->lineno + 1,
2599                                    view->lines,
2600                                    lines);
2602         }
2604         if (view->pipe) {
2605                 time_t secs = time(NULL) - view->start_time;
2607                 /* Three git seconds are a long time ... */
2608                 if (secs > 2)
2609                         string_format_from(state, &statelen, " loading %lds", secs);
2610         }
2612         string_format_from(buf, &bufpos, "[%s]", view->name);
2613         if (*view->ref && bufpos < view->width) {
2614                 size_t refsize = strlen(view->ref);
2615                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2617                 if (minsize < view->width)
2618                         refsize = view->width - minsize + 7;
2619                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2620         }
2622         if (statelen && bufpos < view->width) {
2623                 string_format_from(buf, &bufpos, "%s", state);
2624         }
2626         if (view == display[current_view])
2627                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2628         else
2629                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2631         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2632         wclrtoeol(view->title);
2633         wnoutrefresh(view->title);
2636 static int
2637 apply_step(double step, int value)
2639         if (step >= 1)
2640                 return (int) step;
2641         value *= step + 0.01;
2642         return value ? value : 1;
2645 static void
2646 resize_display(void)
2648         int offset, i;
2649         struct view *base = display[0];
2650         struct view *view = display[1] ? display[1] : display[0];
2652         /* Setup window dimensions */
2654         getmaxyx(stdscr, base->height, base->width);
2656         /* Make room for the status window. */
2657         base->height -= 1;
2659         if (view != base) {
2660                 /* Horizontal split. */
2661                 view->width   = base->width;
2662                 view->height  = apply_step(opt_scale_split_view, base->height);
2663                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2664                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2665                 base->height -= view->height;
2667                 /* Make room for the title bar. */
2668                 view->height -= 1;
2669         }
2671         /* Make room for the title bar. */
2672         base->height -= 1;
2674         offset = 0;
2676         foreach_displayed_view (view, i) {
2677                 if (!view->win) {
2678                         view->win = newwin(view->height, 0, offset, 0);
2679                         if (!view->win)
2680                                 die("Failed to create %s view", view->name);
2682                         scrollok(view->win, FALSE);
2684                         view->title = newwin(1, 0, offset + view->height, 0);
2685                         if (!view->title)
2686                                 die("Failed to create title window");
2688                 } else {
2689                         wresize(view->win, view->height, view->width);
2690                         mvwin(view->win,   offset, 0);
2691                         mvwin(view->title, offset + view->height, 0);
2692                 }
2694                 offset += view->height + 1;
2695         }
2698 static void
2699 redraw_display(bool clear)
2701         struct view *view;
2702         int i;
2704         foreach_displayed_view (view, i) {
2705                 if (clear)
2706                         wclear(view->win);
2707                 redraw_view(view);
2708                 update_view_title(view);
2709         }
2712 static void
2713 toggle_enum_option_do(unsigned int *opt, const char *help,
2714                       const struct enum_map *map, size_t size)
2716         *opt = (*opt + 1) % size;
2717         redraw_display(FALSE);
2718         report("Displaying %s %s", enum_name(map[*opt]), help);
2721 #define toggle_enum_option(opt, help, map) \
2722         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2724 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2725 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2727 static void
2728 toggle_view_option(bool *option, const char *help)
2730         *option = !*option;
2731         redraw_display(FALSE);
2732         report("%sabling %s", *option ? "En" : "Dis", help);
2735 static void
2736 open_option_menu(void)
2738         const struct menu_item menu[] = {
2739                 { '.', "line numbers", &opt_line_number },
2740                 { 'D', "date display", &opt_date },
2741                 { 'A', "author display", &opt_author },
2742                 { 'g', "revision graph display", &opt_rev_graph },
2743                 { 'F', "reference display", &opt_show_refs },
2744                 { 0 }
2745         };
2746         int selected = 0;
2748         if (prompt_menu("Toggle option", menu, &selected)) {
2749                 if (menu[selected].data == &opt_date)
2750                         toggle_date();
2751                 else if (menu[selected].data == &opt_author)
2752                         toggle_author();
2753                 else
2754                         toggle_view_option(menu[selected].data, menu[selected].text);
2755         }
2758 static void
2759 maximize_view(struct view *view)
2761         memset(display, 0, sizeof(display));
2762         current_view = 0;
2763         display[current_view] = view;
2764         resize_display();
2765         redraw_display(FALSE);
2766         report("");
2770 /*
2771  * Navigation
2772  */
2774 static bool
2775 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2777         if (lineno >= view->lines)
2778                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2780         if (offset > lineno || offset + view->height <= lineno) {
2781                 unsigned long half = view->height / 2;
2783                 if (lineno > half)
2784                         offset = lineno - half;
2785                 else
2786                         offset = 0;
2787         }
2789         if (offset != view->offset || lineno != view->lineno) {
2790                 view->offset = offset;
2791                 view->lineno = lineno;
2792                 return TRUE;
2793         }
2795         return FALSE;
2798 /* Scrolling backend */
2799 static void
2800 do_scroll_view(struct view *view, int lines)
2802         bool redraw_current_line = FALSE;
2804         /* The rendering expects the new offset. */
2805         view->offset += lines;
2807         assert(0 <= view->offset && view->offset < view->lines);
2808         assert(lines);
2810         /* Move current line into the view. */
2811         if (view->lineno < view->offset) {
2812                 view->lineno = view->offset;
2813                 redraw_current_line = TRUE;
2814         } else if (view->lineno >= view->offset + view->height) {
2815                 view->lineno = view->offset + view->height - 1;
2816                 redraw_current_line = TRUE;
2817         }
2819         assert(view->offset <= view->lineno && view->lineno < view->lines);
2821         /* Redraw the whole screen if scrolling is pointless. */
2822         if (view->height < ABS(lines)) {
2823                 redraw_view(view);
2825         } else {
2826                 int line = lines > 0 ? view->height - lines : 0;
2827                 int end = line + ABS(lines);
2829                 scrollok(view->win, TRUE);
2830                 wscrl(view->win, lines);
2831                 scrollok(view->win, FALSE);
2833                 while (line < end && draw_view_line(view, line))
2834                         line++;
2836                 if (redraw_current_line)
2837                         draw_view_line(view, view->lineno - view->offset);
2838                 wnoutrefresh(view->win);
2839         }
2841         view->has_scrolled = TRUE;
2842         report("");
2845 /* Scroll frontend */
2846 static void
2847 scroll_view(struct view *view, enum request request)
2849         int lines = 1;
2851         assert(view_is_displayed(view));
2853         switch (request) {
2854         case REQ_SCROLL_LEFT:
2855                 if (view->yoffset == 0) {
2856                         report("Cannot scroll beyond the first column");
2857                         return;
2858                 }
2859                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2860                         view->yoffset = 0;
2861                 else
2862                         view->yoffset -= apply_step(opt_hscroll, view->width);
2863                 redraw_view_from(view, 0);
2864                 report("");
2865                 return;
2866         case REQ_SCROLL_RIGHT:
2867                 view->yoffset += apply_step(opt_hscroll, view->width);
2868                 redraw_view(view);
2869                 report("");
2870                 return;
2871         case REQ_SCROLL_PAGE_DOWN:
2872                 lines = view->height;
2873         case REQ_SCROLL_LINE_DOWN:
2874                 if (view->offset + lines > view->lines)
2875                         lines = view->lines - view->offset;
2877                 if (lines == 0 || view->offset + view->height >= view->lines) {
2878                         report("Cannot scroll beyond the last line");
2879                         return;
2880                 }
2881                 break;
2883         case REQ_SCROLL_PAGE_UP:
2884                 lines = view->height;
2885         case REQ_SCROLL_LINE_UP:
2886                 if (lines > view->offset)
2887                         lines = view->offset;
2889                 if (lines == 0) {
2890                         report("Cannot scroll beyond the first line");
2891                         return;
2892                 }
2894                 lines = -lines;
2895                 break;
2897         default:
2898                 die("request %d not handled in switch", request);
2899         }
2901         do_scroll_view(view, lines);
2904 /* Cursor moving */
2905 static void
2906 move_view(struct view *view, enum request request)
2908         int scroll_steps = 0;
2909         int steps;
2911         switch (request) {
2912         case REQ_MOVE_FIRST_LINE:
2913                 steps = -view->lineno;
2914                 break;
2916         case REQ_MOVE_LAST_LINE:
2917                 steps = view->lines - view->lineno - 1;
2918                 break;
2920         case REQ_MOVE_PAGE_UP:
2921                 steps = view->height > view->lineno
2922                       ? -view->lineno : -view->height;
2923                 break;
2925         case REQ_MOVE_PAGE_DOWN:
2926                 steps = view->lineno + view->height >= view->lines
2927                       ? view->lines - view->lineno - 1 : view->height;
2928                 break;
2930         case REQ_MOVE_UP:
2931                 steps = -1;
2932                 break;
2934         case REQ_MOVE_DOWN:
2935                 steps = 1;
2936                 break;
2938         default:
2939                 die("request %d not handled in switch", request);
2940         }
2942         if (steps <= 0 && view->lineno == 0) {
2943                 report("Cannot move beyond the first line");
2944                 return;
2946         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2947                 report("Cannot move beyond the last line");
2948                 return;
2949         }
2951         /* Move the current line */
2952         view->lineno += steps;
2953         assert(0 <= view->lineno && view->lineno < view->lines);
2955         /* Check whether the view needs to be scrolled */
2956         if (view->lineno < view->offset ||
2957             view->lineno >= view->offset + view->height) {
2958                 scroll_steps = steps;
2959                 if (steps < 0 && -steps > view->offset) {
2960                         scroll_steps = -view->offset;
2962                 } else if (steps > 0) {
2963                         if (view->lineno == view->lines - 1 &&
2964                             view->lines > view->height) {
2965                                 scroll_steps = view->lines - view->offset - 1;
2966                                 if (scroll_steps >= view->height)
2967                                         scroll_steps -= view->height - 1;
2968                         }
2969                 }
2970         }
2972         if (!view_is_displayed(view)) {
2973                 view->offset += scroll_steps;
2974                 assert(0 <= view->offset && view->offset < view->lines);
2975                 view->ops->select(view, &view->line[view->lineno]);
2976                 return;
2977         }
2979         /* Repaint the old "current" line if we be scrolling */
2980         if (ABS(steps) < view->height)
2981                 draw_view_line(view, view->lineno - steps - view->offset);
2983         if (scroll_steps) {
2984                 do_scroll_view(view, scroll_steps);
2985                 return;
2986         }
2988         /* Draw the current line */
2989         draw_view_line(view, view->lineno - view->offset);
2991         wnoutrefresh(view->win);
2992         report("");
2996 /*
2997  * Searching
2998  */
3000 static void search_view(struct view *view, enum request request);
3002 static bool
3003 grep_text(struct view *view, const char *text[])
3005         regmatch_t pmatch;
3006         size_t i;
3008         for (i = 0; text[i]; i++)
3009                 if (*text[i] &&
3010                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3011                         return TRUE;
3012         return FALSE;
3015 static void
3016 select_view_line(struct view *view, unsigned long lineno)
3018         unsigned long old_lineno = view->lineno;
3019         unsigned long old_offset = view->offset;
3021         if (goto_view_line(view, view->offset, lineno)) {
3022                 if (view_is_displayed(view)) {
3023                         if (old_offset != view->offset) {
3024                                 redraw_view(view);
3025                         } else {
3026                                 draw_view_line(view, old_lineno - view->offset);
3027                                 draw_view_line(view, view->lineno - view->offset);
3028                                 wnoutrefresh(view->win);
3029                         }
3030                 } else {
3031                         view->ops->select(view, &view->line[view->lineno]);
3032                 }
3033         }
3036 static void
3037 find_next(struct view *view, enum request request)
3039         unsigned long lineno = view->lineno;
3040         int direction;
3042         if (!*view->grep) {
3043                 if (!*opt_search)
3044                         report("No previous search");
3045                 else
3046                         search_view(view, request);
3047                 return;
3048         }
3050         switch (request) {
3051         case REQ_SEARCH:
3052         case REQ_FIND_NEXT:
3053                 direction = 1;
3054                 break;
3056         case REQ_SEARCH_BACK:
3057         case REQ_FIND_PREV:
3058                 direction = -1;
3059                 break;
3061         default:
3062                 return;
3063         }
3065         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3066                 lineno += direction;
3068         /* Note, lineno is unsigned long so will wrap around in which case it
3069          * will become bigger than view->lines. */
3070         for (; lineno < view->lines; lineno += direction) {
3071                 if (view->ops->grep(view, &view->line[lineno])) {
3072                         select_view_line(view, lineno);
3073                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3074                         return;
3075                 }
3076         }
3078         report("No match found for '%s'", view->grep);
3081 static void
3082 search_view(struct view *view, enum request request)
3084         int regex_err;
3086         if (view->regex) {
3087                 regfree(view->regex);
3088                 *view->grep = 0;
3089         } else {
3090                 view->regex = calloc(1, sizeof(*view->regex));
3091                 if (!view->regex)
3092                         return;
3093         }
3095         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3096         if (regex_err != 0) {
3097                 char buf[SIZEOF_STR] = "unknown error";
3099                 regerror(regex_err, view->regex, buf, sizeof(buf));
3100                 report("Search failed: %s", buf);
3101                 return;
3102         }
3104         string_copy(view->grep, opt_search);
3106         find_next(view, request);
3109 /*
3110  * Incremental updating
3111  */
3113 static void
3114 reset_view(struct view *view)
3116         int i;
3118         for (i = 0; i < view->lines; i++)
3119                 free(view->line[i].data);
3120         free(view->line);
3122         view->p_offset = view->offset;
3123         view->p_yoffset = view->yoffset;
3124         view->p_lineno = view->lineno;
3126         view->line = NULL;
3127         view->offset = 0;
3128         view->yoffset = 0;
3129         view->lines  = 0;
3130         view->lineno = 0;
3131         view->vid[0] = 0;
3132         view->update_secs = 0;
3135 static void
3136 free_argv(const char *argv[])
3138         int argc;
3140         for (argc = 0; argv[argc]; argc++)
3141                 free((void *) argv[argc]);
3144 static const char *
3145 format_arg(const char *name)
3147         static struct {
3148                 const char *name;
3149                 size_t namelen;
3150                 const char *value;
3151                 const char *value_if_empty;
3152         } vars[] = {
3153 #define FORMAT_VAR(name, value, value_if_empty) \
3154         { name, STRING_SIZE(name), value, value_if_empty }
3155                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3156                 FORMAT_VAR("%(file)",           opt_file,       ""),
3157                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3158                 FORMAT_VAR("%(head)",           ref_head,       ""),
3159                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3160                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3161                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3162         };
3163         int i;
3165         for (i = 0; i < ARRAY_SIZE(vars); i++)
3166                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3167                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3169         report("Unknown replacement: `%s`", name);
3170         return NULL;
3173 static bool
3174 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3176         char buf[SIZEOF_STR];
3177         int argc;
3178         bool noreplace = flags == FORMAT_NONE;
3180         free_argv(dst_argv);
3182         for (argc = 0; src_argv[argc]; argc++) {
3183                 const char *arg = src_argv[argc];
3184                 size_t bufpos = 0;
3186                 while (arg) {
3187                         char *next = strstr(arg, "%(");
3188                         int len = next - arg;
3189                         const char *value;
3191                         if (!next || noreplace) {
3192                                 len = strlen(arg);
3193                                 value = "";
3195                         } else {
3196                                 value = format_arg(next);
3198                                 if (!value) {
3199                                         return FALSE;
3200                                 }
3201                         }
3203                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3204                                 return FALSE;
3206                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3207                 }
3209                 dst_argv[argc] = strdup(buf);
3210                 if (!dst_argv[argc])
3211                         break;
3212         }
3214         dst_argv[argc] = NULL;
3216         return src_argv[argc] == NULL;
3219 static bool
3220 restore_view_position(struct view *view)
3222         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3223                 return FALSE;
3225         /* Changing the view position cancels the restoring. */
3226         /* FIXME: Changing back to the first line is not detected. */
3227         if (view->offset != 0 || view->lineno != 0) {
3228                 view->p_restore = FALSE;
3229                 return FALSE;
3230         }
3232         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3233             view_is_displayed(view))
3234                 werase(view->win);
3236         view->yoffset = view->p_yoffset;
3237         view->p_restore = FALSE;
3239         return TRUE;
3242 static void
3243 end_update(struct view *view, bool force)
3245         if (!view->pipe)
3246                 return;
3247         while (!view->ops->read(view, NULL))
3248                 if (!force)
3249                         return;
3250         if (force)
3251                 io_kill(view->pipe);
3252         io_done(view->pipe);
3253         view->pipe = NULL;
3256 static void
3257 setup_update(struct view *view, const char *vid)
3259         reset_view(view);
3260         string_copy_rev(view->vid, vid);
3261         view->pipe = &view->io;
3262         view->start_time = time(NULL);
3265 static bool
3266 prepare_update(struct view *view, const char *argv[], const char *dir)
3268         if (view->pipe)
3269                 end_update(view, TRUE);
3270         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3273 static bool
3274 prepare_update_file(struct view *view, const char *name)
3276         if (view->pipe)
3277                 end_update(view, TRUE);
3278         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3281 static bool
3282 begin_update(struct view *view, bool refresh)
3284         if (view->pipe)
3285                 end_update(view, TRUE);
3287         if (!refresh) {
3288                 if (view->ops->prepare) {
3289                         if (!view->ops->prepare(view))
3290                                 return FALSE;
3291                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3292                         return FALSE;
3293                 }
3295                 /* Put the current ref_* value to the view title ref
3296                  * member. This is needed by the blob view. Most other
3297                  * views sets it automatically after loading because the
3298                  * first line is a commit line. */
3299                 string_copy_rev(view->ref, view->id);
3300         }
3302         if (!io_start(&view->io))
3303                 return FALSE;
3305         setup_update(view, view->id);
3307         return TRUE;
3310 static bool
3311 update_view(struct view *view)
3313         char out_buffer[BUFSIZ * 2];
3314         char *line;
3315         /* Clear the view and redraw everything since the tree sorting
3316          * might have rearranged things. */
3317         bool redraw = view->lines == 0;
3318         bool can_read = TRUE;
3320         if (!view->pipe)
3321                 return TRUE;
3323         if (!io_can_read(view->pipe)) {
3324                 if (view->lines == 0 && view_is_displayed(view)) {
3325                         time_t secs = time(NULL) - view->start_time;
3327                         if (secs > 1 && secs > view->update_secs) {
3328                                 if (view->update_secs == 0)
3329                                         redraw_view(view);
3330                                 update_view_title(view);
3331                                 view->update_secs = secs;
3332                         }
3333                 }
3334                 return TRUE;
3335         }
3337         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3338                 if (opt_iconv_in != ICONV_NONE) {
3339                         ICONV_CONST char *inbuf = line;
3340                         size_t inlen = strlen(line) + 1;
3342                         char *outbuf = out_buffer;
3343                         size_t outlen = sizeof(out_buffer);
3345                         size_t ret;
3347                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3348                         if (ret != (size_t) -1)
3349                                 line = out_buffer;
3350                 }
3352                 if (!view->ops->read(view, line)) {
3353                         report("Allocation failure");
3354                         end_update(view, TRUE);
3355                         return FALSE;
3356                 }
3357         }
3359         {
3360                 unsigned long lines = view->lines;
3361                 int digits;
3363                 for (digits = 0; lines; digits++)
3364                         lines /= 10;
3366                 /* Keep the displayed view in sync with line number scaling. */
3367                 if (digits != view->digits) {
3368                         view->digits = digits;
3369                         if (opt_line_number || view->type == VIEW_BLAME)
3370                                 redraw = TRUE;
3371                 }
3372         }
3374         if (io_error(view->pipe)) {
3375                 report("Failed to read: %s", io_strerror(view->pipe));
3376                 end_update(view, TRUE);
3378         } else if (io_eof(view->pipe)) {
3379                 if (view_is_displayed(view))
3380                         report("");
3381                 end_update(view, FALSE);
3382         }
3384         if (restore_view_position(view))
3385                 redraw = TRUE;
3387         if (!view_is_displayed(view))
3388                 return TRUE;
3390         if (redraw)
3391                 redraw_view_from(view, 0);
3392         else
3393                 redraw_view_dirty(view);
3395         /* Update the title _after_ the redraw so that if the redraw picks up a
3396          * commit reference in view->ref it'll be available here. */
3397         update_view_title(view);
3398         return TRUE;
3401 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3403 static struct line *
3404 add_line_data(struct view *view, void *data, enum line_type type)
3406         struct line *line;
3408         if (!realloc_lines(&view->line, view->lines, 1))
3409                 return NULL;
3411         line = &view->line[view->lines++];
3412         memset(line, 0, sizeof(*line));
3413         line->type = type;
3414         line->data = data;
3415         line->dirty = 1;
3417         return line;
3420 static struct line *
3421 add_line_text(struct view *view, const char *text, enum line_type type)
3423         char *data = text ? strdup(text) : NULL;
3425         return data ? add_line_data(view, data, type) : NULL;
3428 static struct line *
3429 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3431         char buf[SIZEOF_STR];
3432         va_list args;
3434         va_start(args, fmt);
3435         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3436                 buf[0] = 0;
3437         va_end(args);
3439         return buf[0] ? add_line_text(view, buf, type) : NULL;
3442 /*
3443  * View opening
3444  */
3446 enum open_flags {
3447         OPEN_DEFAULT = 0,       /* Use default view switching. */
3448         OPEN_SPLIT = 1,         /* Split current view. */
3449         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3450         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3451         OPEN_PREPARED = 32,     /* Open already prepared command. */
3452 };
3454 static void
3455 open_view(struct view *prev, enum request request, enum open_flags flags)
3457         bool split = !!(flags & OPEN_SPLIT);
3458         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3459         bool nomaximize = !!(flags & OPEN_REFRESH);
3460         struct view *view = VIEW(request);
3461         int nviews = displayed_views();
3462         struct view *base_view = display[0];
3464         if (view == prev && nviews == 1 && !reload) {
3465                 report("Already in %s view", view->name);
3466                 return;
3467         }
3469         if (view->git_dir && !opt_git_dir[0]) {
3470                 report("The %s view is disabled in pager view", view->name);
3471                 return;
3472         }
3474         if (split) {
3475                 display[1] = view;
3476                 current_view = 1;
3477                 view->parent = prev;
3478         } else if (!nomaximize) {
3479                 /* Maximize the current view. */
3480                 memset(display, 0, sizeof(display));
3481                 current_view = 0;
3482                 display[current_view] = view;
3483         }
3485         /* No prev signals that this is the first loaded view. */
3486         if (prev && view != prev) {
3487                 view->prev = prev;
3488         }
3490         /* Resize the view when switching between split- and full-screen,
3491          * or when switching between two different full-screen views. */
3492         if (nviews != displayed_views() ||
3493             (nviews == 1 && base_view != display[0]))
3494                 resize_display();
3496         if (view->ops->open) {
3497                 if (view->pipe)
3498                         end_update(view, TRUE);
3499                 if (!view->ops->open(view)) {
3500                         report("Failed to load %s view", view->name);
3501                         return;
3502                 }
3503                 restore_view_position(view);
3505         } else if ((reload || strcmp(view->vid, view->id)) &&
3506                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3507                 report("Failed to load %s view", view->name);
3508                 return;
3509         }
3511         if (split && prev->lineno - prev->offset >= prev->height) {
3512                 /* Take the title line into account. */
3513                 int lines = prev->lineno - prev->offset - prev->height + 1;
3515                 /* Scroll the view that was split if the current line is
3516                  * outside the new limited view. */
3517                 do_scroll_view(prev, lines);
3518         }
3520         if (prev && view != prev && split && view_is_displayed(prev)) {
3521                 /* "Blur" the previous view. */
3522                 update_view_title(prev);
3523         }
3525         if (view->pipe && view->lines == 0) {
3526                 /* Clear the old view and let the incremental updating refill
3527                  * the screen. */
3528                 werase(view->win);
3529                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3530                 report("");
3531         } else if (view_is_displayed(view)) {
3532                 redraw_view(view);
3533                 report("");
3534         }
3537 static void
3538 open_external_viewer(const char *argv[], const char *dir)
3540         def_prog_mode();           /* save current tty modes */
3541         endwin();                  /* restore original tty modes */
3542         io_run_fg(argv, dir);
3543         fprintf(stderr, "Press Enter to continue");
3544         getc(opt_tty);
3545         reset_prog_mode();
3546         redraw_display(TRUE);
3549 static void
3550 open_mergetool(const char *file)
3552         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3554         open_external_viewer(mergetool_argv, opt_cdup);
3557 static void
3558 open_editor(const char *file)
3560         const char *editor_argv[] = { "vi", file, NULL };
3561         const char *editor;
3563         editor = getenv("GIT_EDITOR");
3564         if (!editor && *opt_editor)
3565                 editor = opt_editor;
3566         if (!editor)
3567                 editor = getenv("VISUAL");
3568         if (!editor)
3569                 editor = getenv("EDITOR");
3570         if (!editor)
3571                 editor = "vi";
3573         editor_argv[0] = editor;
3574         open_external_viewer(editor_argv, opt_cdup);
3577 static void
3578 open_run_request(enum request request)
3580         struct run_request *req = get_run_request(request);
3581         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3583         if (!req) {
3584                 report("Unknown run request");
3585                 return;
3586         }
3588         if (format_argv(argv, req->argv, FORMAT_ALL))
3589                 open_external_viewer(argv, NULL);
3590         free_argv(argv);
3593 /*
3594  * User request switch noodle
3595  */
3597 static int
3598 view_driver(struct view *view, enum request request)
3600         int i;
3602         if (request == REQ_NONE)
3603                 return TRUE;
3605         if (request > REQ_NONE) {
3606                 open_run_request(request);
3607                 /* FIXME: When all views can refresh always do this. */
3608                 if (view->refresh)
3609                         request = REQ_REFRESH;
3610                 else
3611                         return TRUE;
3612         }
3614         if (view && view->lines) {
3615                 request = view->ops->request(view, request, &view->line[view->lineno]);
3616                 if (request == REQ_NONE)
3617                         return TRUE;
3618         }
3620         switch (request) {
3621         case REQ_MOVE_UP:
3622         case REQ_MOVE_DOWN:
3623         case REQ_MOVE_PAGE_UP:
3624         case REQ_MOVE_PAGE_DOWN:
3625         case REQ_MOVE_FIRST_LINE:
3626         case REQ_MOVE_LAST_LINE:
3627                 move_view(view, request);
3628                 break;
3630         case REQ_SCROLL_LEFT:
3631         case REQ_SCROLL_RIGHT:
3632         case REQ_SCROLL_LINE_DOWN:
3633         case REQ_SCROLL_LINE_UP:
3634         case REQ_SCROLL_PAGE_DOWN:
3635         case REQ_SCROLL_PAGE_UP:
3636                 scroll_view(view, request);
3637                 break;
3639         case REQ_VIEW_BLAME:
3640                 if (!opt_file[0]) {
3641                         report("No file chosen, press %s to open tree view",
3642                                get_key(view->keymap, REQ_VIEW_TREE));
3643                         break;
3644                 }
3645                 open_view(view, request, OPEN_DEFAULT);
3646                 break;
3648         case REQ_VIEW_BLOB:
3649                 if (!ref_blob[0]) {
3650                         report("No file chosen, press %s to open tree view",
3651                                get_key(view->keymap, REQ_VIEW_TREE));
3652                         break;
3653                 }
3654                 open_view(view, request, OPEN_DEFAULT);
3655                 break;
3657         case REQ_VIEW_PAGER:
3658                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3659                         report("No pager content, press %s to run command from prompt",
3660                                get_key(view->keymap, REQ_PROMPT));
3661                         break;
3662                 }
3663                 open_view(view, request, OPEN_DEFAULT);
3664                 break;
3666         case REQ_VIEW_STAGE:
3667                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3668                         report("No stage content, press %s to open the status view and choose file",
3669                                get_key(view->keymap, REQ_VIEW_STATUS));
3670                         break;
3671                 }
3672                 open_view(view, request, OPEN_DEFAULT);
3673                 break;
3675         case REQ_VIEW_STATUS:
3676                 if (opt_is_inside_work_tree == FALSE) {
3677                         report("The status view requires a working tree");
3678                         break;
3679                 }
3680                 open_view(view, request, OPEN_DEFAULT);
3681                 break;
3683         case REQ_VIEW_MAIN:
3684         case REQ_VIEW_DIFF:
3685         case REQ_VIEW_LOG:
3686         case REQ_VIEW_TREE:
3687         case REQ_VIEW_HELP:
3688         case REQ_VIEW_BRANCH:
3689                 open_view(view, request, OPEN_DEFAULT);
3690                 break;
3692         case REQ_NEXT:
3693         case REQ_PREVIOUS:
3694                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3696                 if (view->parent) {
3697                         int line;
3699                         view = view->parent;
3700                         line = view->lineno;
3701                         move_view(view, request);
3702                         if (view_is_displayed(view))
3703                                 update_view_title(view);
3704                         if (line != view->lineno)
3705                                 view->ops->request(view, REQ_ENTER,
3706                                                    &view->line[view->lineno]);
3708                 } else {
3709                         move_view(view, request);
3710                 }
3711                 break;
3713         case REQ_VIEW_NEXT:
3714         {
3715                 int nviews = displayed_views();
3716                 int next_view = (current_view + 1) % nviews;
3718                 if (next_view == current_view) {
3719                         report("Only one view is displayed");
3720                         break;
3721                 }
3723                 current_view = next_view;
3724                 /* Blur out the title of the previous view. */
3725                 update_view_title(view);
3726                 report("");
3727                 break;
3728         }
3729         case REQ_REFRESH:
3730                 report("Refreshing is not yet supported for the %s view", view->name);
3731                 break;
3733         case REQ_MAXIMIZE:
3734                 if (displayed_views() == 2)
3735                         maximize_view(view);
3736                 break;
3738         case REQ_OPTIONS:
3739                 open_option_menu();
3740                 break;
3742         case REQ_TOGGLE_LINENO:
3743                 toggle_view_option(&opt_line_number, "line numbers");
3744                 break;
3746         case REQ_TOGGLE_DATE:
3747                 toggle_date();
3748                 break;
3750         case REQ_TOGGLE_AUTHOR:
3751                 toggle_author();
3752                 break;
3754         case REQ_TOGGLE_REV_GRAPH:
3755                 toggle_view_option(&opt_rev_graph, "revision graph display");
3756                 break;
3758         case REQ_TOGGLE_REFS:
3759                 toggle_view_option(&opt_show_refs, "reference display");
3760                 break;
3762         case REQ_TOGGLE_SORT_FIELD:
3763         case REQ_TOGGLE_SORT_ORDER:
3764                 report("Sorting is not yet supported for the %s view", view->name);
3765                 break;
3767         case REQ_SEARCH:
3768         case REQ_SEARCH_BACK:
3769                 search_view(view, request);
3770                 break;
3772         case REQ_FIND_NEXT:
3773         case REQ_FIND_PREV:
3774                 find_next(view, request);
3775                 break;
3777         case REQ_STOP_LOADING:
3778                 foreach_view(view, i) {
3779                         if (view->pipe)
3780                                 report("Stopped loading the %s view", view->name),
3781                         end_update(view, TRUE);
3782                 }
3783                 break;
3785         case REQ_SHOW_VERSION:
3786                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3787                 return TRUE;
3789         case REQ_SCREEN_REDRAW:
3790                 redraw_display(TRUE);
3791                 break;
3793         case REQ_EDIT:
3794                 report("Nothing to edit");
3795                 break;
3797         case REQ_ENTER:
3798                 report("Nothing to enter");
3799                 break;
3801         case REQ_VIEW_CLOSE:
3802                 /* XXX: Mark closed views by letting view->prev point to the
3803                  * view itself. Parents to closed view should never be
3804                  * followed. */
3805                 if (view->prev && view->prev != view) {
3806                         maximize_view(view->prev);
3807                         view->prev = view;
3808                         break;
3809                 }
3810                 /* Fall-through */
3811         case REQ_QUIT:
3812                 return FALSE;
3814         default:
3815                 report("Unknown key, press %s for help",
3816                        get_key(view->keymap, REQ_VIEW_HELP));
3817                 return TRUE;
3818         }
3820         return TRUE;
3824 /*
3825  * View backend utilities
3826  */
3828 enum sort_field {
3829         ORDERBY_NAME,
3830         ORDERBY_DATE,
3831         ORDERBY_AUTHOR,
3832 };
3834 struct sort_state {
3835         const enum sort_field *fields;
3836         size_t size, current;
3837         bool reverse;
3838 };
3840 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3841 #define get_sort_field(state) ((state).fields[(state).current])
3842 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3844 static void
3845 sort_view(struct view *view, enum request request, struct sort_state *state,
3846           int (*compare)(const void *, const void *))
3848         switch (request) {
3849         case REQ_TOGGLE_SORT_FIELD:
3850                 state->current = (state->current + 1) % state->size;
3851                 break;
3853         case REQ_TOGGLE_SORT_ORDER:
3854                 state->reverse = !state->reverse;
3855                 break;
3856         default:
3857                 die("Not a sort request");
3858         }
3860         qsort(view->line, view->lines, sizeof(*view->line), compare);
3861         redraw_view(view);
3864 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3866 /* Small author cache to reduce memory consumption. It uses binary
3867  * search to lookup or find place to position new entries. No entries
3868  * are ever freed. */
3869 static const char *
3870 get_author(const char *name)
3872         static const char **authors;
3873         static size_t authors_size;
3874         int from = 0, to = authors_size - 1;
3876         while (from <= to) {
3877                 size_t pos = (to + from) / 2;
3878                 int cmp = strcmp(name, authors[pos]);
3880                 if (!cmp)
3881                         return authors[pos];
3883                 if (cmp < 0)
3884                         to = pos - 1;
3885                 else
3886                         from = pos + 1;
3887         }
3889         if (!realloc_authors(&authors, authors_size, 1))
3890                 return NULL;
3891         name = strdup(name);
3892         if (!name)
3893                 return NULL;
3895         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3896         authors[from] = name;
3897         authors_size++;
3899         return name;
3902 static void
3903 parse_timesec(struct time *time, const char *sec)
3905         time->sec = (time_t) atol(sec);
3908 static void
3909 parse_timezone(struct time *time, const char *zone)
3911         long tz;
3913         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3914         tz += ('0' - zone[2]) * 60 * 60;
3915         tz += ('0' - zone[3]) * 60 * 10;
3916         tz += ('0' - zone[4]) * 60;
3918         if (zone[0] == '-')
3919                 tz = -tz;
3921         time->tz = tz;
3922         time->sec -= tz;
3925 /* Parse author lines where the name may be empty:
3926  *      author  <email@address.tld> 1138474660 +0100
3927  */
3928 static void
3929 parse_author_line(char *ident, const char **author, struct time *time)
3931         char *nameend = strchr(ident, '<');
3932         char *emailend = strchr(ident, '>');
3934         if (nameend && emailend)
3935                 *nameend = *emailend = 0;
3936         ident = chomp_string(ident);
3937         if (!*ident) {
3938                 if (nameend)
3939                         ident = chomp_string(nameend + 1);
3940                 if (!*ident)
3941                         ident = "Unknown";
3942         }
3944         *author = get_author(ident);
3946         /* Parse epoch and timezone */
3947         if (emailend && emailend[1] == ' ') {
3948                 char *secs = emailend + 2;
3949                 char *zone = strchr(secs, ' ');
3951                 parse_timesec(time, secs);
3953                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3954                         parse_timezone(time, zone + 1);
3955         }
3958 static bool
3959 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3961         char rev[SIZEOF_REV];
3962         const char *revlist_argv[] = {
3963                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3964         };
3965         struct menu_item *items;
3966         char text[SIZEOF_STR];
3967         bool ok = TRUE;
3968         int i;
3970         items = calloc(*parents + 1, sizeof(*items));
3971         if (!items)
3972                 return FALSE;
3974         for (i = 0; i < *parents; i++) {
3975                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3976                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3977                     !(items[i].text = strdup(text))) {
3978                         ok = FALSE;
3979                         break;
3980                 }
3981         }
3983         if (ok) {
3984                 *parents = 0;
3985                 ok = prompt_menu("Select parent", items, parents);
3986         }
3987         for (i = 0; items[i].text; i++)
3988                 free((char *) items[i].text);
3989         free(items);
3990         return ok;
3993 static bool
3994 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3996         char buf[SIZEOF_STR * 4];
3997         const char *revlist_argv[] = {
3998                 "git", "log", "--no-color", "-1",
3999                         "--pretty=format:%P", id, "--", path, NULL
4000         };
4001         int parents;
4003         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4004             (parents = strlen(buf) / 40) < 0) {
4005                 report("Failed to get parent information");
4006                 return FALSE;
4008         } else if (parents == 0) {
4009                 if (path)
4010                         report("Path '%s' does not exist in the parent", path);
4011                 else
4012                         report("The selected commit has no parents");
4013                 return FALSE;
4014         }
4016         if (parents == 1)
4017                 parents = 0;
4018         else if (!open_commit_parent_menu(buf, &parents))
4019                 return FALSE;
4021         string_copy_rev(rev, &buf[41 * parents]);
4022         return TRUE;
4025 /*
4026  * Pager backend
4027  */
4029 static bool
4030 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4032         char text[SIZEOF_STR];
4034         if (opt_line_number && draw_lineno(view, lineno))
4035                 return TRUE;
4037         string_expand(text, sizeof(text), line->data, opt_tab_size);
4038         draw_text(view, line->type, text, TRUE);
4039         return TRUE;
4042 static bool
4043 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4045         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4046         char ref[SIZEOF_STR];
4048         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4049                 return TRUE;
4051         /* This is the only fatal call, since it can "corrupt" the buffer. */
4052         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4053                 return FALSE;
4055         return TRUE;
4058 static void
4059 add_pager_refs(struct view *view, struct line *line)
4061         char buf[SIZEOF_STR];
4062         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4063         struct ref_list *list;
4064         size_t bufpos = 0, i;
4065         const char *sep = "Refs: ";
4066         bool is_tag = FALSE;
4068         assert(line->type == LINE_COMMIT);
4070         list = get_ref_list(commit_id);
4071         if (!list) {
4072                 if (view->type == VIEW_DIFF)
4073                         goto try_add_describe_ref;
4074                 return;
4075         }
4077         for (i = 0; i < list->size; i++) {
4078                 struct ref *ref = list->refs[i];
4079                 const char *fmt = ref->tag    ? "%s[%s]" :
4080                                   ref->remote ? "%s<%s>" : "%s%s";
4082                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4083                         return;
4084                 sep = ", ";
4085                 if (ref->tag)
4086                         is_tag = TRUE;
4087         }
4089         if (!is_tag && view->type == VIEW_DIFF) {
4090 try_add_describe_ref:
4091                 /* Add <tag>-g<commit_id> "fake" reference. */
4092                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4093                         return;
4094         }
4096         if (bufpos == 0)
4097                 return;
4099         add_line_text(view, buf, LINE_PP_REFS);
4102 static bool
4103 pager_read(struct view *view, char *data)
4105         struct line *line;
4107         if (!data)
4108                 return TRUE;
4110         line = add_line_text(view, data, get_line_type(data));
4111         if (!line)
4112                 return FALSE;
4114         if (line->type == LINE_COMMIT &&
4115             (view->type == VIEW_DIFF ||
4116              view->type == VIEW_LOG))
4117                 add_pager_refs(view, line);
4119         return TRUE;
4122 static enum request
4123 pager_request(struct view *view, enum request request, struct line *line)
4125         int split = 0;
4127         if (request != REQ_ENTER)
4128                 return request;
4130         if (line->type == LINE_COMMIT &&
4131            (view->type == VIEW_LOG ||
4132             view->type == VIEW_PAGER)) {
4133                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4134                 split = 1;
4135         }
4137         /* Always scroll the view even if it was split. That way
4138          * you can use Enter to scroll through the log view and
4139          * split open each commit diff. */
4140         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4142         /* FIXME: A minor workaround. Scrolling the view will call report("")
4143          * but if we are scrolling a non-current view this won't properly
4144          * update the view title. */
4145         if (split)
4146                 update_view_title(view);
4148         return REQ_NONE;
4151 static bool
4152 pager_grep(struct view *view, struct line *line)
4154         const char *text[] = { line->data, NULL };
4156         return grep_text(view, text);
4159 static void
4160 pager_select(struct view *view, struct line *line)
4162         if (line->type == LINE_COMMIT) {
4163                 char *text = (char *)line->data + STRING_SIZE("commit ");
4165                 if (view->type != VIEW_PAGER)
4166                         string_copy_rev(view->ref, text);
4167                 string_copy_rev(ref_commit, text);
4168         }
4171 static struct view_ops pager_ops = {
4172         "line",
4173         NULL,
4174         NULL,
4175         pager_read,
4176         pager_draw,
4177         pager_request,
4178         pager_grep,
4179         pager_select,
4180 };
4182 static const char *log_argv[SIZEOF_ARG] = {
4183         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4184 };
4186 static enum request
4187 log_request(struct view *view, enum request request, struct line *line)
4189         switch (request) {
4190         case REQ_REFRESH:
4191                 load_refs();
4192                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4193                 return REQ_NONE;
4194         default:
4195                 return pager_request(view, request, line);
4196         }
4199 static struct view_ops log_ops = {
4200         "line",
4201         log_argv,
4202         NULL,
4203         pager_read,
4204         pager_draw,
4205         log_request,
4206         pager_grep,
4207         pager_select,
4208 };
4210 static const char *diff_argv[SIZEOF_ARG] = {
4211         "git", "show", "--pretty=fuller", "--no-color", "--root",
4212                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4213 };
4215 static struct view_ops diff_ops = {
4216         "line",
4217         diff_argv,
4218         NULL,
4219         pager_read,
4220         pager_draw,
4221         pager_request,
4222         pager_grep,
4223         pager_select,
4224 };
4226 /*
4227  * Help backend
4228  */
4230 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4232 static bool
4233 help_open_keymap_title(struct view *view, enum keymap keymap)
4235         struct line *line;
4237         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4238                                help_keymap_hidden[keymap] ? '+' : '-',
4239                                enum_name(keymap_table[keymap]));
4240         if (line)
4241                 line->other = keymap;
4243         return help_keymap_hidden[keymap];
4246 static void
4247 help_open_keymap(struct view *view, enum keymap keymap)
4249         const char *group = NULL;
4250         char buf[SIZEOF_STR];
4251         size_t bufpos;
4252         bool add_title = TRUE;
4253         int i;
4255         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4256                 const char *key = NULL;
4258                 if (req_info[i].request == REQ_NONE)
4259                         continue;
4261                 if (!req_info[i].request) {
4262                         group = req_info[i].help;
4263                         continue;
4264                 }
4266                 key = get_keys(keymap, req_info[i].request, TRUE);
4267                 if (!key || !*key)
4268                         continue;
4270                 if (add_title && help_open_keymap_title(view, keymap))
4271                         return;
4272                 add_title = FALSE;
4274                 if (group) {
4275                         add_line_text(view, group, LINE_HELP_GROUP);
4276                         group = NULL;
4277                 }
4279                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4280                                 enum_name(req_info[i]), req_info[i].help);
4281         }
4283         group = "External commands:";
4285         for (i = 0; i < run_requests; i++) {
4286                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4287                 const char *key;
4288                 int argc;
4290                 if (!req || req->keymap != keymap)
4291                         continue;
4293                 key = get_key_name(req->key);
4294                 if (!*key)
4295                         key = "(no key defined)";
4297                 if (add_title && help_open_keymap_title(view, keymap))
4298                         return;
4299                 if (group) {
4300                         add_line_text(view, group, LINE_HELP_GROUP);
4301                         group = NULL;
4302                 }
4304                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4305                         if (!string_format_from(buf, &bufpos, "%s%s",
4306                                                 argc ? " " : "", req->argv[argc]))
4307                                 return;
4309                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4310         }
4313 static bool
4314 help_open(struct view *view)
4316         enum keymap keymap;
4318         reset_view(view);
4319         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4320         add_line_text(view, "", LINE_DEFAULT);
4322         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4323                 help_open_keymap(view, keymap);
4325         return TRUE;
4328 static enum request
4329 help_request(struct view *view, enum request request, struct line *line)
4331         switch (request) {
4332         case REQ_ENTER:
4333                 if (line->type == LINE_HELP_KEYMAP) {
4334                         help_keymap_hidden[line->other] =
4335                                 !help_keymap_hidden[line->other];
4336                         view->p_restore = TRUE;
4337                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4338                 }
4340                 return REQ_NONE;
4341         default:
4342                 return pager_request(view, request, line);
4343         }
4346 static struct view_ops help_ops = {
4347         "line",
4348         NULL,
4349         help_open,
4350         NULL,
4351         pager_draw,
4352         help_request,
4353         pager_grep,
4354         pager_select,
4355 };
4358 /*
4359  * Tree backend
4360  */
4362 struct tree_stack_entry {
4363         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4364         unsigned long lineno;           /* Line number to restore */
4365         char *name;                     /* Position of name in opt_path */
4366 };
4368 /* The top of the path stack. */
4369 static struct tree_stack_entry *tree_stack = NULL;
4370 unsigned long tree_lineno = 0;
4372 static void
4373 pop_tree_stack_entry(void)
4375         struct tree_stack_entry *entry = tree_stack;
4377         tree_lineno = entry->lineno;
4378         entry->name[0] = 0;
4379         tree_stack = entry->prev;
4380         free(entry);
4383 static void
4384 push_tree_stack_entry(const char *name, unsigned long lineno)
4386         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4387         size_t pathlen = strlen(opt_path);
4389         if (!entry)
4390                 return;
4392         entry->prev = tree_stack;
4393         entry->name = opt_path + pathlen;
4394         tree_stack = entry;
4396         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4397                 pop_tree_stack_entry();
4398                 return;
4399         }
4401         /* Move the current line to the first tree entry. */
4402         tree_lineno = 1;
4403         entry->lineno = lineno;
4406 /* Parse output from git-ls-tree(1):
4407  *
4408  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4409  */
4411 #define SIZEOF_TREE_ATTR \
4412         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4414 #define SIZEOF_TREE_MODE \
4415         STRING_SIZE("100644 ")
4417 #define TREE_ID_OFFSET \
4418         STRING_SIZE("100644 blob ")
4420 struct tree_entry {
4421         char id[SIZEOF_REV];
4422         mode_t mode;
4423         struct time time;               /* Date from the author ident. */
4424         const char *author;             /* Author of the commit. */
4425         char name[1];
4426 };
4428 static const char *
4429 tree_path(const struct line *line)
4431         return ((struct tree_entry *) line->data)->name;
4434 static int
4435 tree_compare_entry(const struct line *line1, const struct line *line2)
4437         if (line1->type != line2->type)
4438                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4439         return strcmp(tree_path(line1), tree_path(line2));
4442 static const enum sort_field tree_sort_fields[] = {
4443         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4444 };
4445 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4447 static int
4448 tree_compare(const void *l1, const void *l2)
4450         const struct line *line1 = (const struct line *) l1;
4451         const struct line *line2 = (const struct line *) l2;
4452         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4453         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4455         if (line1->type == LINE_TREE_HEAD)
4456                 return -1;
4457         if (line2->type == LINE_TREE_HEAD)
4458                 return 1;
4460         switch (get_sort_field(tree_sort_state)) {
4461         case ORDERBY_DATE:
4462                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4464         case ORDERBY_AUTHOR:
4465                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4467         case ORDERBY_NAME:
4468         default:
4469                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4470         }
4474 static struct line *
4475 tree_entry(struct view *view, enum line_type type, const char *path,
4476            const char *mode, const char *id)
4478         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4479         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4481         if (!entry || !line) {
4482                 free(entry);
4483                 return NULL;
4484         }
4486         strncpy(entry->name, path, strlen(path));
4487         if (mode)
4488                 entry->mode = strtoul(mode, NULL, 8);
4489         if (id)
4490                 string_copy_rev(entry->id, id);
4492         return line;
4495 static bool
4496 tree_read_date(struct view *view, char *text, bool *read_date)
4498         static const char *author_name;
4499         static struct time author_time;
4501         if (!text && *read_date) {
4502                 *read_date = FALSE;
4503                 return TRUE;
4505         } else if (!text) {
4506                 char *path = *opt_path ? opt_path : ".";
4507                 /* Find next entry to process */
4508                 const char *log_file[] = {
4509                         "git", "log", "--no-color", "--pretty=raw",
4510                                 "--cc", "--raw", view->id, "--", path, NULL
4511                 };
4512                 struct io io = {};
4514                 if (!view->lines) {
4515                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4516                         report("Tree is empty");
4517                         return TRUE;
4518                 }
4520                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4521                         report("Failed to load tree data");
4522                         return TRUE;
4523                 }
4525                 io_done(view->pipe);
4526                 view->io = io;
4527                 *read_date = TRUE;
4528                 return FALSE;
4530         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4531                 parse_author_line(text + STRING_SIZE("author "),
4532                                   &author_name, &author_time);
4534         } else if (*text == ':') {
4535                 char *pos;
4536                 size_t annotated = 1;
4537                 size_t i;
4539                 pos = strchr(text, '\t');
4540                 if (!pos)
4541                         return TRUE;
4542                 text = pos + 1;
4543                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4544                         text += strlen(opt_path);
4545                 pos = strchr(text, '/');
4546                 if (pos)
4547                         *pos = 0;
4549                 for (i = 1; i < view->lines; i++) {
4550                         struct line *line = &view->line[i];
4551                         struct tree_entry *entry = line->data;
4553                         annotated += !!entry->author;
4554                         if (entry->author || strcmp(entry->name, text))
4555                                 continue;
4557                         entry->author = author_name;
4558                         entry->time = author_time;
4559                         line->dirty = 1;
4560                         break;
4561                 }
4563                 if (annotated == view->lines)
4564                         io_kill(view->pipe);
4565         }
4566         return TRUE;
4569 static bool
4570 tree_read(struct view *view, char *text)
4572         static bool read_date = FALSE;
4573         struct tree_entry *data;
4574         struct line *entry, *line;
4575         enum line_type type;
4576         size_t textlen = text ? strlen(text) : 0;
4577         char *path = text + SIZEOF_TREE_ATTR;
4579         if (read_date || !text)
4580                 return tree_read_date(view, text, &read_date);
4582         if (textlen <= SIZEOF_TREE_ATTR)
4583                 return FALSE;
4584         if (view->lines == 0 &&
4585             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4586                 return FALSE;
4588         /* Strip the path part ... */
4589         if (*opt_path) {
4590                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4591                 size_t striplen = strlen(opt_path);
4593                 if (pathlen > striplen)
4594                         memmove(path, path + striplen,
4595                                 pathlen - striplen + 1);
4597                 /* Insert "link" to parent directory. */
4598                 if (view->lines == 1 &&
4599                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4600                         return FALSE;
4601         }
4603         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4604         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4605         if (!entry)
4606                 return FALSE;
4607         data = entry->data;
4609         /* Skip "Directory ..." and ".." line. */
4610         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4611                 if (tree_compare_entry(line, entry) <= 0)
4612                         continue;
4614                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4616                 line->data = data;
4617                 line->type = type;
4618                 for (; line <= entry; line++)
4619                         line->dirty = line->cleareol = 1;
4620                 return TRUE;
4621         }
4623         if (tree_lineno > view->lineno) {
4624                 view->lineno = tree_lineno;
4625                 tree_lineno = 0;
4626         }
4628         return TRUE;
4631 static bool
4632 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4634         struct tree_entry *entry = line->data;
4636         if (line->type == LINE_TREE_HEAD) {
4637                 if (draw_text(view, line->type, "Directory path /", TRUE))
4638                         return TRUE;
4639         } else {
4640                 if (draw_mode(view, entry->mode))
4641                         return TRUE;
4643                 if (opt_author && draw_author(view, entry->author))
4644                         return TRUE;
4646                 if (opt_date && draw_date(view, &entry->time))
4647                         return TRUE;
4648         }
4649         if (draw_text(view, line->type, entry->name, TRUE))
4650                 return TRUE;
4651         return TRUE;
4654 static void
4655 open_blob_editor()
4657         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4658         int fd = mkstemp(file);
4660         if (fd == -1)
4661                 report("Failed to create temporary file");
4662         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4663                 report("Failed to save blob data to file");
4664         else
4665                 open_editor(file);
4666         if (fd != -1)
4667                 unlink(file);
4670 static enum request
4671 tree_request(struct view *view, enum request request, struct line *line)
4673         enum open_flags flags;
4675         switch (request) {
4676         case REQ_VIEW_BLAME:
4677                 if (line->type != LINE_TREE_FILE) {
4678                         report("Blame only supported for files");
4679                         return REQ_NONE;
4680                 }
4682                 string_copy(opt_ref, view->vid);
4683                 return request;
4685         case REQ_EDIT:
4686                 if (line->type != LINE_TREE_FILE) {
4687                         report("Edit only supported for files");
4688                 } else if (!is_head_commit(view->vid)) {
4689                         open_blob_editor();
4690                 } else {
4691                         open_editor(opt_file);
4692                 }
4693                 return REQ_NONE;
4695         case REQ_TOGGLE_SORT_FIELD:
4696         case REQ_TOGGLE_SORT_ORDER:
4697                 sort_view(view, request, &tree_sort_state, tree_compare);
4698                 return REQ_NONE;
4700         case REQ_PARENT:
4701                 if (!*opt_path) {
4702                         /* quit view if at top of tree */
4703                         return REQ_VIEW_CLOSE;
4704                 }
4705                 /* fake 'cd  ..' */
4706                 line = &view->line[1];
4707                 break;
4709         case REQ_ENTER:
4710                 break;
4712         default:
4713                 return request;
4714         }
4716         /* Cleanup the stack if the tree view is at a different tree. */
4717         while (!*opt_path && tree_stack)
4718                 pop_tree_stack_entry();
4720         switch (line->type) {
4721         case LINE_TREE_DIR:
4722                 /* Depending on whether it is a subdirectory or parent link
4723                  * mangle the path buffer. */
4724                 if (line == &view->line[1] && *opt_path) {
4725                         pop_tree_stack_entry();
4727                 } else {
4728                         const char *basename = tree_path(line);
4730                         push_tree_stack_entry(basename, view->lineno);
4731                 }
4733                 /* Trees and subtrees share the same ID, so they are not not
4734                  * unique like blobs. */
4735                 flags = OPEN_RELOAD;
4736                 request = REQ_VIEW_TREE;
4737                 break;
4739         case LINE_TREE_FILE:
4740                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4741                 request = REQ_VIEW_BLOB;
4742                 break;
4744         default:
4745                 return REQ_NONE;
4746         }
4748         open_view(view, request, flags);
4749         if (request == REQ_VIEW_TREE)
4750                 view->lineno = tree_lineno;
4752         return REQ_NONE;
4755 static bool
4756 tree_grep(struct view *view, struct line *line)
4758         struct tree_entry *entry = line->data;
4759         const char *text[] = {
4760                 entry->name,
4761                 opt_author ? entry->author : "",
4762                 mkdate(&entry->time, opt_date),
4763                 NULL
4764         };
4766         return grep_text(view, text);
4769 static void
4770 tree_select(struct view *view, struct line *line)
4772         struct tree_entry *entry = line->data;
4774         if (line->type == LINE_TREE_FILE) {
4775                 string_copy_rev(ref_blob, entry->id);
4776                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4778         } else if (line->type != LINE_TREE_DIR) {
4779                 return;
4780         }
4782         string_copy_rev(view->ref, entry->id);
4785 static bool
4786 tree_prepare(struct view *view)
4788         if (view->lines == 0 && opt_prefix[0]) {
4789                 char *pos = opt_prefix;
4791                 while (pos && *pos) {
4792                         char *end = strchr(pos, '/');
4794                         if (end)
4795                                 *end = 0;
4796                         push_tree_stack_entry(pos, 0);
4797                         pos = end;
4798                         if (end) {
4799                                 *end = '/';
4800                                 pos++;
4801                         }
4802                 }
4804         } else if (strcmp(view->vid, view->id)) {
4805                 opt_path[0] = 0;
4806         }
4808         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4811 static const char *tree_argv[SIZEOF_ARG] = {
4812         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4813 };
4815 static struct view_ops tree_ops = {
4816         "file",
4817         tree_argv,
4818         NULL,
4819         tree_read,
4820         tree_draw,
4821         tree_request,
4822         tree_grep,
4823         tree_select,
4824         tree_prepare,
4825 };
4827 static bool
4828 blob_read(struct view *view, char *line)
4830         if (!line)
4831                 return TRUE;
4832         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4835 static enum request
4836 blob_request(struct view *view, enum request request, struct line *line)
4838         switch (request) {
4839         case REQ_EDIT:
4840                 open_blob_editor();
4841                 return REQ_NONE;
4842         default:
4843                 return pager_request(view, request, line);
4844         }
4847 static const char *blob_argv[SIZEOF_ARG] = {
4848         "git", "cat-file", "blob", "%(blob)", NULL
4849 };
4851 static struct view_ops blob_ops = {
4852         "line",
4853         blob_argv,
4854         NULL,
4855         blob_read,
4856         pager_draw,
4857         blob_request,
4858         pager_grep,
4859         pager_select,
4860 };
4862 /*
4863  * Blame backend
4864  *
4865  * Loading the blame view is a two phase job:
4866  *
4867  *  1. File content is read either using opt_file from the
4868  *     filesystem or using git-cat-file.
4869  *  2. Then blame information is incrementally added by
4870  *     reading output from git-blame.
4871  */
4873 static const char *blame_head_argv[] = {
4874         "git", "blame", "--incremental", "--", "%(file)", NULL
4875 };
4877 static const char *blame_ref_argv[] = {
4878         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4879 };
4881 static const char *blame_cat_file_argv[] = {
4882         "git", "cat-file", "blob", "%(ref):%(file)", NULL
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                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4913                         return FALSE;
4914         }
4916         setup_update(view, opt_file);
4917         string_format(view->ref, "%s ...", opt_file);
4919         return TRUE;
4922 static struct blame_commit *
4923 get_blame_commit(struct view *view, const char *id)
4925         size_t i;
4927         for (i = 0; i < view->lines; i++) {
4928                 struct blame *blame = view->line[i].data;
4930                 if (!blame->commit)
4931                         continue;
4933                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4934                         return blame->commit;
4935         }
4937         {
4938                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4940                 if (commit)
4941                         string_ncopy(commit->id, id, SIZEOF_REV);
4942                 return commit;
4943         }
4946 static bool
4947 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4949         const char *pos = *posref;
4951         *posref = NULL;
4952         pos = strchr(pos + 1, ' ');
4953         if (!pos || !isdigit(pos[1]))
4954                 return FALSE;
4955         *number = atoi(pos + 1);
4956         if (*number < min || *number > max)
4957                 return FALSE;
4959         *posref = pos;
4960         return TRUE;
4963 static struct blame_commit *
4964 parse_blame_commit(struct view *view, const char *text, int *blamed)
4966         struct blame_commit *commit;
4967         struct blame *blame;
4968         const char *pos = text + SIZEOF_REV - 2;
4969         size_t orig_lineno = 0;
4970         size_t lineno;
4971         size_t group;
4973         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4974                 return NULL;
4976         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4977             !parse_number(&pos, &lineno, 1, view->lines) ||
4978             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4979                 return NULL;
4981         commit = get_blame_commit(view, text);
4982         if (!commit)
4983                 return NULL;
4985         *blamed += group;
4986         while (group--) {
4987                 struct line *line = &view->line[lineno + group - 1];
4989                 blame = line->data;
4990                 blame->commit = commit;
4991                 blame->lineno = orig_lineno + group - 1;
4992                 line->dirty = 1;
4993         }
4995         return commit;
4998 static bool
4999 blame_read_file(struct view *view, const char *line, bool *read_file)
5001         if (!line) {
5002                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
5003                 struct io io = {};
5005                 if (view->lines == 0 && !view->prev)
5006                         die("No blame exist for %s", view->vid);
5008                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
5009                         report("Failed to load blame data");
5010                         return TRUE;
5011                 }
5013                 io_done(view->pipe);
5014                 view->io = io;
5015                 *read_file = FALSE;
5016                 return FALSE;
5018         } else {
5019                 size_t linelen = strlen(line);
5020                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5022                 if (!blame)
5023                         return FALSE;
5025                 blame->commit = NULL;
5026                 strncpy(blame->text, line, linelen);
5027                 blame->text[linelen] = 0;
5028                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5029         }
5032 static bool
5033 match_blame_header(const char *name, char **line)
5035         size_t namelen = strlen(name);
5036         bool matched = !strncmp(name, *line, namelen);
5038         if (matched)
5039                 *line += namelen;
5041         return matched;
5044 static bool
5045 blame_read(struct view *view, char *line)
5047         static struct blame_commit *commit = NULL;
5048         static int blamed = 0;
5049         static bool read_file = TRUE;
5051         if (read_file)
5052                 return blame_read_file(view, line, &read_file);
5054         if (!line) {
5055                 /* Reset all! */
5056                 commit = NULL;
5057                 blamed = 0;
5058                 read_file = TRUE;
5059                 string_format(view->ref, "%s", view->vid);
5060                 if (view_is_displayed(view)) {
5061                         update_view_title(view);
5062                         redraw_view_from(view, 0);
5063                 }
5064                 return TRUE;
5065         }
5067         if (!commit) {
5068                 commit = parse_blame_commit(view, line, &blamed);
5069                 string_format(view->ref, "%s %2d%%", view->vid,
5070                               view->lines ? blamed * 100 / view->lines : 0);
5072         } else if (match_blame_header("author ", &line)) {
5073                 commit->author = get_author(line);
5075         } else if (match_blame_header("author-time ", &line)) {
5076                 parse_timesec(&commit->time, line);
5078         } else if (match_blame_header("author-tz ", &line)) {
5079                 parse_timezone(&commit->time, line);
5081         } else if (match_blame_header("summary ", &line)) {
5082                 string_ncopy(commit->title, line, strlen(line));
5084         } else if (match_blame_header("previous ", &line)) {
5085                 commit->has_previous = TRUE;
5087         } else if (match_blame_header("filename ", &line)) {
5088                 string_ncopy(commit->filename, line, strlen(line));
5089                 commit = NULL;
5090         }
5092         return TRUE;
5095 static bool
5096 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5098         struct blame *blame = line->data;
5099         struct time *time = NULL;
5100         const char *id = NULL, *author = NULL;
5101         char text[SIZEOF_STR];
5103         if (blame->commit && *blame->commit->filename) {
5104                 id = blame->commit->id;
5105                 author = blame->commit->author;
5106                 time = &blame->commit->time;
5107         }
5109         if (opt_date && draw_date(view, time))
5110                 return TRUE;
5112         if (opt_author && draw_author(view, author))
5113                 return TRUE;
5115         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5116                 return TRUE;
5118         if (draw_lineno(view, lineno))
5119                 return TRUE;
5121         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5122         draw_text(view, LINE_DEFAULT, text, TRUE);
5123         return TRUE;
5126 static bool
5127 check_blame_commit(struct blame *blame, bool check_null_id)
5129         if (!blame->commit)
5130                 report("Commit data not loaded yet");
5131         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5132                 report("No commit exist for the selected line");
5133         else
5134                 return TRUE;
5135         return FALSE;
5138 static void
5139 setup_blame_parent_line(struct view *view, struct blame *blame)
5141         const char *diff_tree_argv[] = {
5142                 "git", "diff-tree", "-U0", blame->commit->id,
5143                         "--", blame->commit->filename, NULL
5144         };
5145         struct io io = {};
5146         int parent_lineno = -1;
5147         int blamed_lineno = -1;
5148         char *line;
5150         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5151                 return;
5153         while ((line = io_get(&io, '\n', TRUE))) {
5154                 if (*line == '@') {
5155                         char *pos = strchr(line, '+');
5157                         parent_lineno = atoi(line + 4);
5158                         if (pos)
5159                                 blamed_lineno = atoi(pos + 1);
5161                 } else if (*line == '+' && parent_lineno != -1) {
5162                         if (blame->lineno == blamed_lineno - 1 &&
5163                             !strcmp(blame->text, line + 1)) {
5164                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5165                                 break;
5166                         }
5167                         blamed_lineno++;
5168                 }
5169         }
5171         io_done(&io);
5174 static enum request
5175 blame_request(struct view *view, enum request request, struct line *line)
5177         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5178         struct blame *blame = line->data;
5180         switch (request) {
5181         case REQ_VIEW_BLAME:
5182                 if (check_blame_commit(blame, TRUE)) {
5183                         string_copy(opt_ref, blame->commit->id);
5184                         string_copy(opt_file, blame->commit->filename);
5185                         if (blame->lineno)
5186                                 view->lineno = blame->lineno;
5187                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5188                 }
5189                 break;
5191         case REQ_PARENT:
5192                 if (check_blame_commit(blame, TRUE) &&
5193                     select_commit_parent(blame->commit->id, opt_ref,
5194                                          blame->commit->filename)) {
5195                         string_copy(opt_file, blame->commit->filename);
5196                         setup_blame_parent_line(view, blame);
5197                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5198                 }
5199                 break;
5201         case REQ_ENTER:
5202                 if (!check_blame_commit(blame, FALSE))
5203                         break;
5205                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5206                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5207                         break;
5209                 if (!strcmp(blame->commit->id, NULL_ID)) {
5210                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5211                         const char *diff_index_argv[] = {
5212                                 "git", "diff-index", "--root", "--patch-with-stat",
5213                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5214                         };
5216                         if (!blame->commit->has_previous) {
5217                                 diff_index_argv[1] = "diff";
5218                                 diff_index_argv[2] = "--no-color";
5219                                 diff_index_argv[6] = "--";
5220                                 diff_index_argv[7] = "/dev/null";
5221                         }
5223                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5224                                 report("Failed to allocate diff command");
5225                                 break;
5226                         }
5227                         flags |= OPEN_PREPARED;
5228                 }
5230                 open_view(view, REQ_VIEW_DIFF, flags);
5231                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5232                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5233                 break;
5235         default:
5236                 return request;
5237         }
5239         return REQ_NONE;
5242 static bool
5243 blame_grep(struct view *view, struct line *line)
5245         struct blame *blame = line->data;
5246         struct blame_commit *commit = blame->commit;
5247         const char *text[] = {
5248                 blame->text,
5249                 commit ? commit->title : "",
5250                 commit ? commit->id : "",
5251                 commit && opt_author ? commit->author : "",
5252                 commit ? mkdate(&commit->time, opt_date) : "",
5253                 NULL
5254         };
5256         return grep_text(view, text);
5259 static void
5260 blame_select(struct view *view, struct line *line)
5262         struct blame *blame = line->data;
5263         struct blame_commit *commit = blame->commit;
5265         if (!commit)
5266                 return;
5268         if (!strcmp(commit->id, NULL_ID))
5269                 string_ncopy(ref_commit, "HEAD", 4);
5270         else
5271                 string_copy_rev(ref_commit, commit->id);
5274 static struct view_ops blame_ops = {
5275         "line",
5276         NULL,
5277         blame_open,
5278         blame_read,
5279         blame_draw,
5280         blame_request,
5281         blame_grep,
5282         blame_select,
5283 };
5285 /*
5286  * Branch backend
5287  */
5289 struct branch {
5290         const char *author;             /* Author of the last commit. */
5291         struct time time;               /* Date of the last activity. */
5292         const struct ref *ref;          /* Name and commit ID information. */
5293 };
5295 static const struct ref branch_all;
5297 static const enum sort_field branch_sort_fields[] = {
5298         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5299 };
5300 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5302 static int
5303 branch_compare(const void *l1, const void *l2)
5305         const struct branch *branch1 = ((const struct line *) l1)->data;
5306         const struct branch *branch2 = ((const struct line *) l2)->data;
5308         switch (get_sort_field(branch_sort_state)) {
5309         case ORDERBY_DATE:
5310                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5312         case ORDERBY_AUTHOR:
5313                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5315         case ORDERBY_NAME:
5316         default:
5317                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5318         }
5321 static bool
5322 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5324         struct branch *branch = line->data;
5325         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5327         if (opt_date && draw_date(view, &branch->time))
5328                 return TRUE;
5330         if (opt_author && draw_author(view, branch->author))
5331                 return TRUE;
5333         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5334         return TRUE;
5337 static enum request
5338 branch_request(struct view *view, enum request request, struct line *line)
5340         struct branch *branch = line->data;
5342         switch (request) {
5343         case REQ_REFRESH:
5344                 load_refs();
5345                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5346                 return REQ_NONE;
5348         case REQ_TOGGLE_SORT_FIELD:
5349         case REQ_TOGGLE_SORT_ORDER:
5350                 sort_view(view, request, &branch_sort_state, branch_compare);
5351                 return REQ_NONE;
5353         case REQ_ENTER:
5354                 if (branch->ref == &branch_all) {
5355                         const char *all_branches_argv[] = {
5356                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5357                                       "--topo-order", "--all", NULL
5358                         };
5359                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5361                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5362                                 report("Failed to load view of all branches");
5363                                 return REQ_NONE;
5364                         }
5365                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5366                 } else {
5367                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5368                 }
5369                 return REQ_NONE;
5371         default:
5372                 return request;
5373         }
5376 static bool
5377 branch_read(struct view *view, char *line)
5379         static char id[SIZEOF_REV];
5380         struct branch *reference;
5381         size_t i;
5383         if (!line)
5384                 return TRUE;
5386         switch (get_line_type(line)) {
5387         case LINE_COMMIT:
5388                 string_copy_rev(id, line + STRING_SIZE("commit "));
5389                 return TRUE;
5391         case LINE_AUTHOR:
5392                 for (i = 0, reference = NULL; i < view->lines; i++) {
5393                         struct branch *branch = view->line[i].data;
5395                         if (strcmp(branch->ref->id, id))
5396                                 continue;
5398                         view->line[i].dirty = TRUE;
5399                         if (reference) {
5400                                 branch->author = reference->author;
5401                                 branch->time = reference->time;
5402                                 continue;
5403                         }
5405                         parse_author_line(line + STRING_SIZE("author "),
5406                                           &branch->author, &branch->time);
5407                         reference = branch;
5408                 }
5409                 return TRUE;
5411         default:
5412                 return TRUE;
5413         }
5417 static bool
5418 branch_open_visitor(void *data, const struct ref *ref)
5420         struct view *view = data;
5421         struct branch *branch;
5423         if (ref->tag || ref->ltag || ref->remote)
5424                 return TRUE;
5426         branch = calloc(1, sizeof(*branch));
5427         if (!branch)
5428                 return FALSE;
5430         branch->ref = ref;
5431         return !!add_line_data(view, branch, LINE_DEFAULT);
5434 static bool
5435 branch_open(struct view *view)
5437         const char *branch_log[] = {
5438                 "git", "log", "--no-color", "--pretty=raw",
5439                         "--simplify-by-decoration", "--all", NULL
5440         };
5442         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5443                 report("Failed to load branch data");
5444                 return TRUE;
5445         }
5447         setup_update(view, view->id);
5448         branch_open_visitor(view, &branch_all);
5449         foreach_ref(branch_open_visitor, view);
5450         view->p_restore = TRUE;
5452         return TRUE;
5455 static bool
5456 branch_grep(struct view *view, struct line *line)
5458         struct branch *branch = line->data;
5459         const char *text[] = {
5460                 branch->ref->name,
5461                 branch->author,
5462                 NULL
5463         };
5465         return grep_text(view, text);
5468 static void
5469 branch_select(struct view *view, struct line *line)
5471         struct branch *branch = line->data;
5473         string_copy_rev(view->ref, branch->ref->id);
5474         string_copy_rev(ref_commit, branch->ref->id);
5475         string_copy_rev(ref_head, branch->ref->id);
5476         string_copy_rev(ref_branch, branch->ref->name);
5479 static struct view_ops branch_ops = {
5480         "branch",
5481         NULL,
5482         branch_open,
5483         branch_read,
5484         branch_draw,
5485         branch_request,
5486         branch_grep,
5487         branch_select,
5488 };
5490 /*
5491  * Status backend
5492  */
5494 struct status {
5495         char status;
5496         struct {
5497                 mode_t mode;
5498                 char rev[SIZEOF_REV];
5499                 char name[SIZEOF_STR];
5500         } old;
5501         struct {
5502                 mode_t mode;
5503                 char rev[SIZEOF_REV];
5504                 char name[SIZEOF_STR];
5505         } new;
5506 };
5508 static char status_onbranch[SIZEOF_STR];
5509 static struct status stage_status;
5510 static enum line_type stage_line_type;
5511 static size_t stage_chunks;
5512 static int *stage_chunk;
5514 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5516 /* This should work even for the "On branch" line. */
5517 static inline bool
5518 status_has_none(struct view *view, struct line *line)
5520         return line < view->line + view->lines && !line[1].data;
5523 /* Get fields from the diff line:
5524  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5525  */
5526 static inline bool
5527 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5529         const char *old_mode = buf +  1;
5530         const char *new_mode = buf +  8;
5531         const char *old_rev  = buf + 15;
5532         const char *new_rev  = buf + 56;
5533         const char *status   = buf + 97;
5535         if (bufsize < 98 ||
5536             old_mode[-1] != ':' ||
5537             new_mode[-1] != ' ' ||
5538             old_rev[-1]  != ' ' ||
5539             new_rev[-1]  != ' ' ||
5540             status[-1]   != ' ')
5541                 return FALSE;
5543         file->status = *status;
5545         string_copy_rev(file->old.rev, old_rev);
5546         string_copy_rev(file->new.rev, new_rev);
5548         file->old.mode = strtoul(old_mode, NULL, 8);
5549         file->new.mode = strtoul(new_mode, NULL, 8);
5551         file->old.name[0] = file->new.name[0] = 0;
5553         return TRUE;
5556 static bool
5557 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5559         struct status *unmerged = NULL;
5560         char *buf;
5561         struct io io = {};
5563         if (!io_run(&io, argv, opt_cdup, IO_RD))
5564                 return FALSE;
5566         add_line_data(view, NULL, type);
5568         while ((buf = io_get(&io, 0, TRUE))) {
5569                 struct status *file = unmerged;
5571                 if (!file) {
5572                         file = calloc(1, sizeof(*file));
5573                         if (!file || !add_line_data(view, file, type))
5574                                 goto error_out;
5575                 }
5577                 /* Parse diff info part. */
5578                 if (status) {
5579                         file->status = status;
5580                         if (status == 'A')
5581                                 string_copy(file->old.rev, NULL_ID);
5583                 } else if (!file->status || file == unmerged) {
5584                         if (!status_get_diff(file, buf, strlen(buf)))
5585                                 goto error_out;
5587                         buf = io_get(&io, 0, TRUE);
5588                         if (!buf)
5589                                 break;
5591                         /* Collapse all modified entries that follow an
5592                          * associated unmerged entry. */
5593                         if (unmerged == file) {
5594                                 unmerged->status = 'U';
5595                                 unmerged = NULL;
5596                         } else if (file->status == 'U') {
5597                                 unmerged = file;
5598                         }
5599                 }
5601                 /* Grab the old name for rename/copy. */
5602                 if (!*file->old.name &&
5603                     (file->status == 'R' || file->status == 'C')) {
5604                         string_ncopy(file->old.name, buf, strlen(buf));
5606                         buf = io_get(&io, 0, TRUE);
5607                         if (!buf)
5608                                 break;
5609                 }
5611                 /* git-ls-files just delivers a NUL separated list of
5612                  * file names similar to the second half of the
5613                  * git-diff-* output. */
5614                 string_ncopy(file->new.name, buf, strlen(buf));
5615                 if (!*file->old.name)
5616                         string_copy(file->old.name, file->new.name);
5617                 file = NULL;
5618         }
5620         if (io_error(&io)) {
5621 error_out:
5622                 io_done(&io);
5623                 return FALSE;
5624         }
5626         if (!view->line[view->lines - 1].data)
5627                 add_line_data(view, NULL, LINE_STAT_NONE);
5629         io_done(&io);
5630         return TRUE;
5633 /* Don't show unmerged entries in the staged section. */
5634 static const char *status_diff_index_argv[] = {
5635         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5636                              "--cached", "-M", "HEAD", NULL
5637 };
5639 static const char *status_diff_files_argv[] = {
5640         "git", "diff-files", "-z", NULL
5641 };
5643 static const char *status_list_other_argv[] = {
5644         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5645 };
5647 static const char *status_list_no_head_argv[] = {
5648         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5649 };
5651 static const char *update_index_argv[] = {
5652         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5653 };
5655 /* Restore the previous line number to stay in the context or select a
5656  * line with something that can be updated. */
5657 static void
5658 status_restore(struct view *view)
5660         if (view->p_lineno >= view->lines)
5661                 view->p_lineno = view->lines - 1;
5662         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5663                 view->p_lineno++;
5664         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5665                 view->p_lineno--;
5667         /* If the above fails, always skip the "On branch" line. */
5668         if (view->p_lineno < view->lines)
5669                 view->lineno = view->p_lineno;
5670         else
5671                 view->lineno = 1;
5673         if (view->lineno < view->offset)
5674                 view->offset = view->lineno;
5675         else if (view->offset + view->height <= view->lineno)
5676                 view->offset = view->lineno - view->height + 1;
5678         view->p_restore = FALSE;
5681 static void
5682 status_update_onbranch(void)
5684         static const char *paths[][2] = {
5685                 { "rebase-apply/rebasing",      "Rebasing" },
5686                 { "rebase-apply/applying",      "Applying mailbox" },
5687                 { "rebase-apply/",              "Rebasing mailbox" },
5688                 { "rebase-merge/interactive",   "Interactive rebase" },
5689                 { "rebase-merge/",              "Rebase merge" },
5690                 { "MERGE_HEAD",                 "Merging" },
5691                 { "BISECT_LOG",                 "Bisecting" },
5692                 { "HEAD",                       "On branch" },
5693         };
5694         char buf[SIZEOF_STR];
5695         struct stat stat;
5696         int i;
5698         if (is_initial_commit()) {
5699                 string_copy(status_onbranch, "Initial commit");
5700                 return;
5701         }
5703         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5704                 char *head = opt_head;
5706                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5707                     lstat(buf, &stat) < 0)
5708                         continue;
5710                 if (!*opt_head) {
5711                         struct io io = {};
5713                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5714                             io_read_buf(&io, buf, sizeof(buf))) {
5715                                 head = buf;
5716                                 if (!prefixcmp(head, "refs/heads/"))
5717                                         head += STRING_SIZE("refs/heads/");
5718                         }
5719                 }
5721                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5722                         string_copy(status_onbranch, opt_head);
5723                 return;
5724         }
5726         string_copy(status_onbranch, "Not currently on any branch");
5729 /* First parse staged info using git-diff-index(1), then parse unstaged
5730  * info using git-diff-files(1), and finally untracked files using
5731  * git-ls-files(1). */
5732 static bool
5733 status_open(struct view *view)
5735         reset_view(view);
5737         add_line_data(view, NULL, LINE_STAT_HEAD);
5738         status_update_onbranch();
5740         io_run_bg(update_index_argv);
5742         if (is_initial_commit()) {
5743                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5744                         return FALSE;
5745         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5746                 return FALSE;
5747         }
5749         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5750             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5751                 return FALSE;
5753         /* Restore the exact position or use the specialized restore
5754          * mode? */
5755         if (!view->p_restore)
5756                 status_restore(view);
5757         return TRUE;
5760 static bool
5761 status_draw(struct view *view, struct line *line, unsigned int lineno)
5763         struct status *status = line->data;
5764         enum line_type type;
5765         const char *text;
5767         if (!status) {
5768                 switch (line->type) {
5769                 case LINE_STAT_STAGED:
5770                         type = LINE_STAT_SECTION;
5771                         text = "Changes to be committed:";
5772                         break;
5774                 case LINE_STAT_UNSTAGED:
5775                         type = LINE_STAT_SECTION;
5776                         text = "Changed but not updated:";
5777                         break;
5779                 case LINE_STAT_UNTRACKED:
5780                         type = LINE_STAT_SECTION;
5781                         text = "Untracked files:";
5782                         break;
5784                 case LINE_STAT_NONE:
5785                         type = LINE_DEFAULT;
5786                         text = "  (no files)";
5787                         break;
5789                 case LINE_STAT_HEAD:
5790                         type = LINE_STAT_HEAD;
5791                         text = status_onbranch;
5792                         break;
5794                 default:
5795                         return FALSE;
5796                 }
5797         } else {
5798                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5800                 buf[0] = status->status;
5801                 if (draw_text(view, line->type, buf, TRUE))
5802                         return TRUE;
5803                 type = LINE_DEFAULT;
5804                 text = status->new.name;
5805         }
5807         draw_text(view, type, text, TRUE);
5808         return TRUE;
5811 static enum request
5812 status_load_error(struct view *view, struct view *stage, const char *path)
5814         if (displayed_views() == 2 || display[current_view] != view)
5815                 maximize_view(view);
5816         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5817         return REQ_NONE;
5820 static enum request
5821 status_enter(struct view *view, struct line *line)
5823         struct status *status = line->data;
5824         const char *oldpath = status ? status->old.name : NULL;
5825         /* Diffs for unmerged entries are empty when passing the new
5826          * path, so leave it empty. */
5827         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5828         const char *info;
5829         enum open_flags split;
5830         struct view *stage = VIEW(REQ_VIEW_STAGE);
5832         if (line->type == LINE_STAT_NONE ||
5833             (!status && line[1].type == LINE_STAT_NONE)) {
5834                 report("No file to diff");
5835                 return REQ_NONE;
5836         }
5838         switch (line->type) {
5839         case LINE_STAT_STAGED:
5840                 if (is_initial_commit()) {
5841                         const char *no_head_diff_argv[] = {
5842                                 "git", "diff", "--no-color", "--patch-with-stat",
5843                                         "--", "/dev/null", newpath, NULL
5844                         };
5846                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5847                                 return status_load_error(view, stage, newpath);
5848                 } else {
5849                         const char *index_show_argv[] = {
5850                                 "git", "diff-index", "--root", "--patch-with-stat",
5851                                         "-C", "-M", "--cached", "HEAD", "--",
5852                                         oldpath, newpath, NULL
5853                         };
5855                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5856                                 return status_load_error(view, stage, newpath);
5857                 }
5859                 if (status)
5860                         info = "Staged changes to %s";
5861                 else
5862                         info = "Staged changes";
5863                 break;
5865         case LINE_STAT_UNSTAGED:
5866         {
5867                 const char *files_show_argv[] = {
5868                         "git", "diff-files", "--root", "--patch-with-stat",
5869                                 "-C", "-M", "--", oldpath, newpath, NULL
5870                 };
5872                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5873                         return status_load_error(view, stage, newpath);
5874                 if (status)
5875                         info = "Unstaged changes to %s";
5876                 else
5877                         info = "Unstaged changes";
5878                 break;
5879         }
5880         case LINE_STAT_UNTRACKED:
5881                 if (!newpath) {
5882                         report("No file to show");
5883                         return REQ_NONE;
5884                 }
5886                 if (!suffixcmp(status->new.name, -1, "/")) {
5887                         report("Cannot display a directory");
5888                         return REQ_NONE;
5889                 }
5891                 if (!prepare_update_file(stage, newpath))
5892                         return status_load_error(view, stage, newpath);
5893                 info = "Untracked file %s";
5894                 break;
5896         case LINE_STAT_HEAD:
5897                 return REQ_NONE;
5899         default:
5900                 die("line type %d not handled in switch", line->type);
5901         }
5903         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5904         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5905         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5906                 if (status) {
5907                         stage_status = *status;
5908                 } else {
5909                         memset(&stage_status, 0, sizeof(stage_status));
5910                 }
5912                 stage_line_type = line->type;
5913                 stage_chunks = 0;
5914                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5915         }
5917         return REQ_NONE;
5920 static bool
5921 status_exists(struct status *status, enum line_type type)
5923         struct view *view = VIEW(REQ_VIEW_STATUS);
5924         unsigned long lineno;
5926         for (lineno = 0; lineno < view->lines; lineno++) {
5927                 struct line *line = &view->line[lineno];
5928                 struct status *pos = line->data;
5930                 if (line->type != type)
5931                         continue;
5932                 if (!pos && (!status || !status->status) && line[1].data) {
5933                         select_view_line(view, lineno);
5934                         return TRUE;
5935                 }
5936                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5937                         select_view_line(view, lineno);
5938                         return TRUE;
5939                 }
5940         }
5942         return FALSE;
5946 static bool
5947 status_update_prepare(struct io *io, enum line_type type)
5949         const char *staged_argv[] = {
5950                 "git", "update-index", "-z", "--index-info", NULL
5951         };
5952         const char *others_argv[] = {
5953                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5954         };
5956         switch (type) {
5957         case LINE_STAT_STAGED:
5958                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5960         case LINE_STAT_UNSTAGED:
5961         case LINE_STAT_UNTRACKED:
5962                 return io_run(io, others_argv, opt_cdup, IO_WR);
5964         default:
5965                 die("line type %d not handled in switch", type);
5966                 return FALSE;
5967         }
5970 static bool
5971 status_update_write(struct io *io, struct status *status, enum line_type type)
5973         char buf[SIZEOF_STR];
5974         size_t bufsize = 0;
5976         switch (type) {
5977         case LINE_STAT_STAGED:
5978                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5979                                         status->old.mode,
5980                                         status->old.rev,
5981                                         status->old.name, 0))
5982                         return FALSE;
5983                 break;
5985         case LINE_STAT_UNSTAGED:
5986         case LINE_STAT_UNTRACKED:
5987                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5988                         return FALSE;
5989                 break;
5991         default:
5992                 die("line type %d not handled in switch", type);
5993         }
5995         return io_write(io, buf, bufsize);
5998 static bool
5999 status_update_file(struct status *status, enum line_type type)
6001         struct io io = {};
6002         bool result;
6004         if (!status_update_prepare(&io, type))
6005                 return FALSE;
6007         result = status_update_write(&io, status, type);
6008         return io_done(&io) && result;
6011 static bool
6012 status_update_files(struct view *view, struct line *line)
6014         char buf[sizeof(view->ref)];
6015         struct io io = {};
6016         bool result = TRUE;
6017         struct line *pos = view->line + view->lines;
6018         int files = 0;
6019         int file, done;
6020         int cursor_y = -1, cursor_x = -1;
6022         if (!status_update_prepare(&io, line->type))
6023                 return FALSE;
6025         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6026                 files++;
6028         string_copy(buf, view->ref);
6029         getsyx(cursor_y, cursor_x);
6030         for (file = 0, done = 5; result && file < files; line++, file++) {
6031                 int almost_done = file * 100 / files;
6033                 if (almost_done > done) {
6034                         done = almost_done;
6035                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6036                                       file, files, done);
6037                         update_view_title(view);
6038                         setsyx(cursor_y, cursor_x);
6039                         doupdate();
6040                 }
6041                 result = status_update_write(&io, line->data, line->type);
6042         }
6043         string_copy(view->ref, buf);
6045         return io_done(&io) && result;
6048 static bool
6049 status_update(struct view *view)
6051         struct line *line = &view->line[view->lineno];
6053         assert(view->lines);
6055         if (!line->data) {
6056                 /* This should work even for the "On branch" line. */
6057                 if (line < view->line + view->lines && !line[1].data) {
6058                         report("Nothing to update");
6059                         return FALSE;
6060                 }
6062                 if (!status_update_files(view, line + 1)) {
6063                         report("Failed to update file status");
6064                         return FALSE;
6065                 }
6067         } else if (!status_update_file(line->data, line->type)) {
6068                 report("Failed to update file status");
6069                 return FALSE;
6070         }
6072         return TRUE;
6075 static bool
6076 status_revert(struct status *status, enum line_type type, bool has_none)
6078         if (!status || type != LINE_STAT_UNSTAGED) {
6079                 if (type == LINE_STAT_STAGED) {
6080                         report("Cannot revert changes to staged files");
6081                 } else if (type == LINE_STAT_UNTRACKED) {
6082                         report("Cannot revert changes to untracked files");
6083                 } else if (has_none) {
6084                         report("Nothing to revert");
6085                 } else {
6086                         report("Cannot revert changes to multiple files");
6087                 }
6089         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6090                 char mode[10] = "100644";
6091                 const char *reset_argv[] = {
6092                         "git", "update-index", "--cacheinfo", mode,
6093                                 status->old.rev, status->old.name, NULL
6094                 };
6095                 const char *checkout_argv[] = {
6096                         "git", "checkout", "--", status->old.name, NULL
6097                 };
6099                 if (status->status == 'U') {
6100                         string_format(mode, "%5o", status->old.mode);
6102                         if (status->old.mode == 0 && status->new.mode == 0) {
6103                                 reset_argv[2] = "--force-remove";
6104                                 reset_argv[3] = status->old.name;
6105                                 reset_argv[4] = NULL;
6106                         }
6108                         if (!io_run_fg(reset_argv, opt_cdup))
6109                                 return FALSE;
6110                         if (status->old.mode == 0 && status->new.mode == 0)
6111                                 return TRUE;
6112                 }
6114                 return io_run_fg(checkout_argv, opt_cdup);
6115         }
6117         return FALSE;
6120 static enum request
6121 status_request(struct view *view, enum request request, struct line *line)
6123         struct status *status = line->data;
6125         switch (request) {
6126         case REQ_STATUS_UPDATE:
6127                 if (!status_update(view))
6128                         return REQ_NONE;
6129                 break;
6131         case REQ_STATUS_REVERT:
6132                 if (!status_revert(status, line->type, status_has_none(view, line)))
6133                         return REQ_NONE;
6134                 break;
6136         case REQ_STATUS_MERGE:
6137                 if (!status || status->status != 'U') {
6138                         report("Merging only possible for files with unmerged status ('U').");
6139                         return REQ_NONE;
6140                 }
6141                 open_mergetool(status->new.name);
6142                 break;
6144         case REQ_EDIT:
6145                 if (!status)
6146                         return request;
6147                 if (status->status == 'D') {
6148                         report("File has been deleted.");
6149                         return REQ_NONE;
6150                 }
6152                 open_editor(status->new.name);
6153                 break;
6155         case REQ_VIEW_BLAME:
6156                 if (status)
6157                         opt_ref[0] = 0;
6158                 return request;
6160         case REQ_ENTER:
6161                 /* After returning the status view has been split to
6162                  * show the stage view. No further reloading is
6163                  * necessary. */
6164                 return status_enter(view, line);
6166         case REQ_REFRESH:
6167                 /* Simply reload the view. */
6168                 break;
6170         default:
6171                 return request;
6172         }
6174         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6176         return REQ_NONE;
6179 static void
6180 status_select(struct view *view, struct line *line)
6182         struct status *status = line->data;
6183         char file[SIZEOF_STR] = "all files";
6184         const char *text;
6185         const char *key;
6187         if (status && !string_format(file, "'%s'", status->new.name))
6188                 return;
6190         if (!status && line[1].type == LINE_STAT_NONE)
6191                 line++;
6193         switch (line->type) {
6194         case LINE_STAT_STAGED:
6195                 text = "Press %s to unstage %s for commit";
6196                 break;
6198         case LINE_STAT_UNSTAGED:
6199                 text = "Press %s to stage %s for commit";
6200                 break;
6202         case LINE_STAT_UNTRACKED:
6203                 text = "Press %s to stage %s for addition";
6204                 break;
6206         case LINE_STAT_HEAD:
6207         case LINE_STAT_NONE:
6208                 text = "Nothing to update";
6209                 break;
6211         default:
6212                 die("line type %d not handled in switch", line->type);
6213         }
6215         if (status && status->status == 'U') {
6216                 text = "Press %s to resolve conflict in %s";
6217                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6219         } else {
6220                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6221         }
6223         string_format(view->ref, text, key, file);
6224         if (status)
6225                 string_copy(opt_file, status->new.name);
6228 static bool
6229 status_grep(struct view *view, struct line *line)
6231         struct status *status = line->data;
6233         if (status) {
6234                 const char buf[2] = { status->status, 0 };
6235                 const char *text[] = { status->new.name, buf, NULL };
6237                 return grep_text(view, text);
6238         }
6240         return FALSE;
6243 static struct view_ops status_ops = {
6244         "file",
6245         NULL,
6246         status_open,
6247         NULL,
6248         status_draw,
6249         status_request,
6250         status_grep,
6251         status_select,
6252 };
6255 static bool
6256 stage_diff_write(struct io *io, struct line *line, struct line *end)
6258         while (line < end) {
6259                 if (!io_write(io, line->data, strlen(line->data)) ||
6260                     !io_write(io, "\n", 1))
6261                         return FALSE;
6262                 line++;
6263                 if (line->type == LINE_DIFF_CHUNK ||
6264                     line->type == LINE_DIFF_HEADER)
6265                         break;
6266         }
6268         return TRUE;
6271 static struct line *
6272 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6274         for (; view->line < line; line--)
6275                 if (line->type == type)
6276                         return line;
6278         return NULL;
6281 static bool
6282 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6284         const char *apply_argv[SIZEOF_ARG] = {
6285                 "git", "apply", "--whitespace=nowarn", NULL
6286         };
6287         struct line *diff_hdr;
6288         struct io io = {};
6289         int argc = 3;
6291         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6292         if (!diff_hdr)
6293                 return FALSE;
6295         if (!revert)
6296                 apply_argv[argc++] = "--cached";
6297         if (revert || stage_line_type == LINE_STAT_STAGED)
6298                 apply_argv[argc++] = "-R";
6299         apply_argv[argc++] = "-";
6300         apply_argv[argc++] = NULL;
6301         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6302                 return FALSE;
6304         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6305             !stage_diff_write(&io, chunk, view->line + view->lines))
6306                 chunk = NULL;
6308         io_done(&io);
6309         io_run_bg(update_index_argv);
6311         return chunk ? TRUE : FALSE;
6314 static bool
6315 stage_update(struct view *view, struct line *line)
6317         struct line *chunk = NULL;
6319         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6320                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6322         if (chunk) {
6323                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6324                         report("Failed to apply chunk");
6325                         return FALSE;
6326                 }
6328         } else if (!stage_status.status) {
6329                 view = VIEW(REQ_VIEW_STATUS);
6331                 for (line = view->line; line < view->line + view->lines; line++)
6332                         if (line->type == stage_line_type)
6333                                 break;
6335                 if (!status_update_files(view, line + 1)) {
6336                         report("Failed to update files");
6337                         return FALSE;
6338                 }
6340         } else if (!status_update_file(&stage_status, stage_line_type)) {
6341                 report("Failed to update file");
6342                 return FALSE;
6343         }
6345         return TRUE;
6348 static bool
6349 stage_revert(struct view *view, struct line *line)
6351         struct line *chunk = NULL;
6353         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6354                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6356         if (chunk) {
6357                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6358                         return FALSE;
6360                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6361                         report("Failed to revert chunk");
6362                         return FALSE;
6363                 }
6364                 return TRUE;
6366         } else {
6367                 return status_revert(stage_status.status ? &stage_status : NULL,
6368                                      stage_line_type, FALSE);
6369         }
6373 static void
6374 stage_next(struct view *view, struct line *line)
6376         int i;
6378         if (!stage_chunks) {
6379                 for (line = view->line; line < view->line + view->lines; line++) {
6380                         if (line->type != LINE_DIFF_CHUNK)
6381                                 continue;
6383                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6384                                 report("Allocation failure");
6385                                 return;
6386                         }
6388                         stage_chunk[stage_chunks++] = line - view->line;
6389                 }
6390         }
6392         for (i = 0; i < stage_chunks; i++) {
6393                 if (stage_chunk[i] > view->lineno) {
6394                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6395                         report("Chunk %d of %d", i + 1, stage_chunks);
6396                         return;
6397                 }
6398         }
6400         report("No next chunk found");
6403 static enum request
6404 stage_request(struct view *view, enum request request, struct line *line)
6406         switch (request) {
6407         case REQ_STATUS_UPDATE:
6408                 if (!stage_update(view, line))
6409                         return REQ_NONE;
6410                 break;
6412         case REQ_STATUS_REVERT:
6413                 if (!stage_revert(view, line))
6414                         return REQ_NONE;
6415                 break;
6417         case REQ_STAGE_NEXT:
6418                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6419                         report("File is untracked; press %s to add",
6420                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6421                         return REQ_NONE;
6422                 }
6423                 stage_next(view, line);
6424                 return REQ_NONE;
6426         case REQ_EDIT:
6427                 if (!stage_status.new.name[0])
6428                         return request;
6429                 if (stage_status.status == 'D') {
6430                         report("File has been deleted.");
6431                         return REQ_NONE;
6432                 }
6434                 open_editor(stage_status.new.name);
6435                 break;
6437         case REQ_REFRESH:
6438                 /* Reload everything ... */
6439                 break;
6441         case REQ_VIEW_BLAME:
6442                 if (stage_status.new.name[0]) {
6443                         string_copy(opt_file, stage_status.new.name);
6444                         opt_ref[0] = 0;
6445                 }
6446                 return request;
6448         case REQ_ENTER:
6449                 return pager_request(view, request, line);
6451         default:
6452                 return request;
6453         }
6455         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6456         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6458         /* Check whether the staged entry still exists, and close the
6459          * stage view if it doesn't. */
6460         if (!status_exists(&stage_status, stage_line_type)) {
6461                 status_restore(VIEW(REQ_VIEW_STATUS));
6462                 return REQ_VIEW_CLOSE;
6463         }
6465         if (stage_line_type == LINE_STAT_UNTRACKED) {
6466                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6467                         report("Cannot display a directory");
6468                         return REQ_NONE;
6469                 }
6471                 if (!prepare_update_file(view, stage_status.new.name)) {
6472                         report("Failed to open file: %s", strerror(errno));
6473                         return REQ_NONE;
6474                 }
6475         }
6476         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6478         return REQ_NONE;
6481 static struct view_ops stage_ops = {
6482         "line",
6483         NULL,
6484         NULL,
6485         pager_read,
6486         pager_draw,
6487         stage_request,
6488         pager_grep,
6489         pager_select,
6490 };
6493 /*
6494  * Revision graph
6495  */
6497 struct commit {
6498         char id[SIZEOF_REV];            /* SHA1 ID. */
6499         char title[128];                /* First line of the commit message. */
6500         const char *author;             /* Author of the commit. */
6501         struct time time;               /* Date from the author ident. */
6502         struct ref_list *refs;          /* Repository references. */
6503         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6504         size_t graph_size;              /* The width of the graph array. */
6505         bool has_parents;               /* Rewritten --parents seen. */
6506 };
6508 /* Size of rev graph with no  "padding" columns */
6509 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6511 struct rev_graph {
6512         struct rev_graph *prev, *next, *parents;
6513         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6514         size_t size;
6515         struct commit *commit;
6516         size_t pos;
6517         unsigned int boundary:1;
6518 };
6520 /* Parents of the commit being visualized. */
6521 static struct rev_graph graph_parents[4];
6523 /* The current stack of revisions on the graph. */
6524 static struct rev_graph graph_stacks[4] = {
6525         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6526         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6527         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6528         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6529 };
6531 static inline bool
6532 graph_parent_is_merge(struct rev_graph *graph)
6534         return graph->parents->size > 1;
6537 static inline void
6538 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6540         struct commit *commit = graph->commit;
6542         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6543                 commit->graph[commit->graph_size++] = symbol;
6546 static void
6547 clear_rev_graph(struct rev_graph *graph)
6549         graph->boundary = 0;
6550         graph->size = graph->pos = 0;
6551         graph->commit = NULL;
6552         memset(graph->parents, 0, sizeof(*graph->parents));
6555 static void
6556 done_rev_graph(struct rev_graph *graph)
6558         if (graph_parent_is_merge(graph) &&
6559             graph->pos < graph->size - 1 &&
6560             graph->next->size == graph->size + graph->parents->size - 1) {
6561                 size_t i = graph->pos + graph->parents->size - 1;
6563                 graph->commit->graph_size = i * 2;
6564                 while (i < graph->next->size - 1) {
6565                         append_to_rev_graph(graph, ' ');
6566                         append_to_rev_graph(graph, '\\');
6567                         i++;
6568                 }
6569         }
6571         clear_rev_graph(graph);
6574 static void
6575 push_rev_graph(struct rev_graph *graph, const char *parent)
6577         int i;
6579         /* "Collapse" duplicate parents lines.
6580          *
6581          * FIXME: This needs to also update update the drawn graph but
6582          * for now it just serves as a method for pruning graph lines. */
6583         for (i = 0; i < graph->size; i++)
6584                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6585                         return;
6587         if (graph->size < SIZEOF_REVITEMS) {
6588                 string_copy_rev(graph->rev[graph->size++], parent);
6589         }
6592 static chtype
6593 get_rev_graph_symbol(struct rev_graph *graph)
6595         chtype symbol;
6597         if (graph->boundary)
6598                 symbol = REVGRAPH_BOUND;
6599         else if (graph->parents->size == 0)
6600                 symbol = REVGRAPH_INIT;
6601         else if (graph_parent_is_merge(graph))
6602                 symbol = REVGRAPH_MERGE;
6603         else if (graph->pos >= graph->size)
6604                 symbol = REVGRAPH_BRANCH;
6605         else
6606                 symbol = REVGRAPH_COMMIT;
6608         return symbol;
6611 static void
6612 draw_rev_graph(struct rev_graph *graph)
6614         struct rev_filler {
6615                 chtype separator, line;
6616         };
6617         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6618         static struct rev_filler fillers[] = {
6619                 { ' ',  '|' },
6620                 { '`',  '.' },
6621                 { '\'', ' ' },
6622                 { '/',  ' ' },
6623         };
6624         chtype symbol = get_rev_graph_symbol(graph);
6625         struct rev_filler *filler;
6626         size_t i;
6628         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6629         filler = &fillers[DEFAULT];
6631         for (i = 0; i < graph->pos; i++) {
6632                 append_to_rev_graph(graph, filler->line);
6633                 if (graph_parent_is_merge(graph->prev) &&
6634                     graph->prev->pos == i)
6635                         filler = &fillers[RSHARP];
6637                 append_to_rev_graph(graph, filler->separator);
6638         }
6640         /* Place the symbol for this revision. */
6641         append_to_rev_graph(graph, symbol);
6643         if (graph->prev->size > graph->size)
6644                 filler = &fillers[RDIAG];
6645         else
6646                 filler = &fillers[DEFAULT];
6648         i++;
6650         for (; i < graph->size; i++) {
6651                 append_to_rev_graph(graph, filler->separator);
6652                 append_to_rev_graph(graph, filler->line);
6653                 if (graph_parent_is_merge(graph->prev) &&
6654                     i < graph->prev->pos + graph->parents->size)
6655                         filler = &fillers[RSHARP];
6656                 if (graph->prev->size > graph->size)
6657                         filler = &fillers[LDIAG];
6658         }
6660         if (graph->prev->size > graph->size) {
6661                 append_to_rev_graph(graph, filler->separator);
6662                 if (filler->line != ' ')
6663                         append_to_rev_graph(graph, filler->line);
6664         }
6667 /* Prepare the next rev graph */
6668 static void
6669 prepare_rev_graph(struct rev_graph *graph)
6671         size_t i;
6673         /* First, traverse all lines of revisions up to the active one. */
6674         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6675                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6676                         break;
6678                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6679         }
6681         /* Interleave the new revision parent(s). */
6682         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6683                 push_rev_graph(graph->next, graph->parents->rev[i]);
6685         /* Lastly, put any remaining revisions. */
6686         for (i = graph->pos + 1; i < graph->size; i++)
6687                 push_rev_graph(graph->next, graph->rev[i]);
6690 static void
6691 update_rev_graph(struct view *view, struct rev_graph *graph)
6693         /* If this is the finalizing update ... */
6694         if (graph->commit)
6695                 prepare_rev_graph(graph);
6697         /* Graph visualization needs a one rev look-ahead,
6698          * so the first update doesn't visualize anything. */
6699         if (!graph->prev->commit)
6700                 return;
6702         if (view->lines > 2)
6703                 view->line[view->lines - 3].dirty = 1;
6704         if (view->lines > 1)
6705                 view->line[view->lines - 2].dirty = 1;
6706         draw_rev_graph(graph->prev);
6707         done_rev_graph(graph->prev->prev);
6711 /*
6712  * Main view backend
6713  */
6715 static const char *main_argv[SIZEOF_ARG] = {
6716         "git", "log", "--no-color", "--pretty=raw", "--parents",
6717                       "--topo-order", "%(head)", NULL
6718 };
6720 static bool
6721 main_draw(struct view *view, struct line *line, unsigned int lineno)
6723         struct commit *commit = line->data;
6725         if (!commit->author)
6726                 return FALSE;
6728         if (opt_date && draw_date(view, &commit->time))
6729                 return TRUE;
6731         if (opt_author && draw_author(view, commit->author))
6732                 return TRUE;
6734         if (opt_rev_graph && commit->graph_size &&
6735             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6736                 return TRUE;
6738         if (opt_show_refs && commit->refs) {
6739                 size_t i;
6741                 for (i = 0; i < commit->refs->size; i++) {
6742                         struct ref *ref = commit->refs->refs[i];
6743                         enum line_type type;
6745                         if (ref->head)
6746                                 type = LINE_MAIN_HEAD;
6747                         else if (ref->ltag)
6748                                 type = LINE_MAIN_LOCAL_TAG;
6749                         else if (ref->tag)
6750                                 type = LINE_MAIN_TAG;
6751                         else if (ref->tracked)
6752                                 type = LINE_MAIN_TRACKED;
6753                         else if (ref->remote)
6754                                 type = LINE_MAIN_REMOTE;
6755                         else
6756                                 type = LINE_MAIN_REF;
6758                         if (draw_text(view, type, "[", TRUE) ||
6759                             draw_text(view, type, ref->name, TRUE) ||
6760                             draw_text(view, type, "]", TRUE))
6761                                 return TRUE;
6763                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6764                                 return TRUE;
6765                 }
6766         }
6768         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6769         return TRUE;
6772 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6773 static bool
6774 main_read(struct view *view, char *line)
6776         static struct rev_graph *graph = graph_stacks;
6777         enum line_type type;
6778         struct commit *commit;
6780         if (!line) {
6781                 int i;
6783                 if (!view->lines && !view->prev)
6784                         die("No revisions match the given arguments.");
6785                 if (view->lines > 0) {
6786                         commit = view->line[view->lines - 1].data;
6787                         view->line[view->lines - 1].dirty = 1;
6788                         if (!commit->author) {
6789                                 view->lines--;
6790                                 free(commit);
6791                                 graph->commit = NULL;
6792                         }
6793                 }
6794                 update_rev_graph(view, graph);
6796                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6797                         clear_rev_graph(&graph_stacks[i]);
6798                 return TRUE;
6799         }
6801         type = get_line_type(line);
6802         if (type == LINE_COMMIT) {
6803                 commit = calloc(1, sizeof(struct commit));
6804                 if (!commit)
6805                         return FALSE;
6807                 line += STRING_SIZE("commit ");
6808                 if (*line == '-') {
6809                         graph->boundary = 1;
6810                         line++;
6811                 }
6813                 string_copy_rev(commit->id, line);
6814                 commit->refs = get_ref_list(commit->id);
6815                 graph->commit = commit;
6816                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6818                 while ((line = strchr(line, ' '))) {
6819                         line++;
6820                         push_rev_graph(graph->parents, line);
6821                         commit->has_parents = TRUE;
6822                 }
6823                 return TRUE;
6824         }
6826         if (!view->lines)
6827                 return TRUE;
6828         commit = view->line[view->lines - 1].data;
6830         switch (type) {
6831         case LINE_PARENT:
6832                 if (commit->has_parents)
6833                         break;
6834                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6835                 break;
6837         case LINE_AUTHOR:
6838                 parse_author_line(line + STRING_SIZE("author "),
6839                                   &commit->author, &commit->time);
6840                 update_rev_graph(view, graph);
6841                 graph = graph->next;
6842                 break;
6844         default:
6845                 /* Fill in the commit title if it has not already been set. */
6846                 if (commit->title[0])
6847                         break;
6849                 /* Require titles to start with a non-space character at the
6850                  * offset used by git log. */
6851                 if (strncmp(line, "    ", 4))
6852                         break;
6853                 line += 4;
6854                 /* Well, if the title starts with a whitespace character,
6855                  * try to be forgiving.  Otherwise we end up with no title. */
6856                 while (isspace(*line))
6857                         line++;
6858                 if (*line == '\0')
6859                         break;
6860                 /* FIXME: More graceful handling of titles; append "..." to
6861                  * shortened titles, etc. */
6863                 string_expand(commit->title, sizeof(commit->title), line, 1);
6864                 view->line[view->lines - 1].dirty = 1;
6865         }
6867         return TRUE;
6870 static enum request
6871 main_request(struct view *view, enum request request, struct line *line)
6873         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6875         switch (request) {
6876         case REQ_ENTER:
6877                 open_view(view, REQ_VIEW_DIFF, flags);
6878                 break;
6879         case REQ_REFRESH:
6880                 load_refs();
6881                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6882                 break;
6883         default:
6884                 return request;
6885         }
6887         return REQ_NONE;
6890 static bool
6891 grep_refs(struct ref_list *list, regex_t *regex)
6893         regmatch_t pmatch;
6894         size_t i;
6896         if (!opt_show_refs || !list)
6897                 return FALSE;
6899         for (i = 0; i < list->size; i++) {
6900                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6901                         return TRUE;
6902         }
6904         return FALSE;
6907 static bool
6908 main_grep(struct view *view, struct line *line)
6910         struct commit *commit = line->data;
6911         const char *text[] = {
6912                 commit->title,
6913                 opt_author ? commit->author : "",
6914                 mkdate(&commit->time, opt_date),
6915                 NULL
6916         };
6918         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6921 static void
6922 main_select(struct view *view, struct line *line)
6924         struct commit *commit = line->data;
6926         string_copy_rev(view->ref, commit->id);
6927         string_copy_rev(ref_commit, view->ref);
6930 static struct view_ops main_ops = {
6931         "commit",
6932         main_argv,
6933         NULL,
6934         main_read,
6935         main_draw,
6936         main_request,
6937         main_grep,
6938         main_select,
6939 };
6942 /*
6943  * Status management
6944  */
6946 /* Whether or not the curses interface has been initialized. */
6947 static bool cursed = FALSE;
6949 /* Terminal hacks and workarounds. */
6950 static bool use_scroll_redrawwin;
6951 static bool use_scroll_status_wclear;
6953 /* The status window is used for polling keystrokes. */
6954 static WINDOW *status_win;
6956 /* Reading from the prompt? */
6957 static bool input_mode = FALSE;
6959 static bool status_empty = FALSE;
6961 /* Update status and title window. */
6962 static void
6963 report(const char *msg, ...)
6965         struct view *view = display[current_view];
6967         if (input_mode)
6968                 return;
6970         if (!view) {
6971                 char buf[SIZEOF_STR];
6972                 va_list args;
6974                 va_start(args, msg);
6975                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6976                         buf[sizeof(buf) - 1] = 0;
6977                         buf[sizeof(buf) - 2] = '.';
6978                         buf[sizeof(buf) - 3] = '.';
6979                         buf[sizeof(buf) - 4] = '.';
6980                 }
6981                 va_end(args);
6982                 die("%s", buf);
6983         }
6985         if (!status_empty || *msg) {
6986                 va_list args;
6988                 va_start(args, msg);
6990                 wmove(status_win, 0, 0);
6991                 if (view->has_scrolled && use_scroll_status_wclear)
6992                         wclear(status_win);
6993                 if (*msg) {
6994                         vwprintw(status_win, msg, args);
6995                         status_empty = FALSE;
6996                 } else {
6997                         status_empty = TRUE;
6998                 }
6999                 wclrtoeol(status_win);
7000                 wnoutrefresh(status_win);
7002                 va_end(args);
7003         }
7005         update_view_title(view);
7008 static void
7009 init_display(void)
7011         const char *term;
7012         int x, y;
7014         /* Initialize the curses library */
7015         if (isatty(STDIN_FILENO)) {
7016                 cursed = !!initscr();
7017                 opt_tty = stdin;
7018         } else {
7019                 /* Leave stdin and stdout alone when acting as a pager. */
7020                 opt_tty = fopen("/dev/tty", "r+");
7021                 if (!opt_tty)
7022                         die("Failed to open /dev/tty");
7023                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7024         }
7026         if (!cursed)
7027                 die("Failed to initialize curses");
7029         nonl();         /* Disable conversion and detect newlines from input. */
7030         cbreak();       /* Take input chars one at a time, no wait for \n */
7031         noecho();       /* Don't echo input */
7032         leaveok(stdscr, FALSE);
7034         if (has_colors())
7035                 init_colors();
7037         getmaxyx(stdscr, y, x);
7038         status_win = newwin(1, 0, y - 1, 0);
7039         if (!status_win)
7040                 die("Failed to create status window");
7042         /* Enable keyboard mapping */
7043         keypad(status_win, TRUE);
7044         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7046         TABSIZE = opt_tab_size;
7048         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7049         if (term && !strcmp(term, "gnome-terminal")) {
7050                 /* In the gnome-terminal-emulator, the message from
7051                  * scrolling up one line when impossible followed by
7052                  * scrolling down one line causes corruption of the
7053                  * status line. This is fixed by calling wclear. */
7054                 use_scroll_status_wclear = TRUE;
7055                 use_scroll_redrawwin = FALSE;
7057         } else if (term && !strcmp(term, "xrvt-xpm")) {
7058                 /* No problems with full optimizations in xrvt-(unicode)
7059                  * and aterm. */
7060                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7062         } else {
7063                 /* When scrolling in (u)xterm the last line in the
7064                  * scrolling direction will update slowly. */
7065                 use_scroll_redrawwin = TRUE;
7066                 use_scroll_status_wclear = FALSE;
7067         }
7070 static int
7071 get_input(int prompt_position)
7073         struct view *view;
7074         int i, key, cursor_y, cursor_x;
7075         bool loading = FALSE;
7077         if (prompt_position)
7078                 input_mode = TRUE;
7080         while (TRUE) {
7081                 foreach_view (view, i) {
7082                         update_view(view);
7083                         if (view_is_displayed(view) && view->has_scrolled &&
7084                             use_scroll_redrawwin)
7085                                 redrawwin(view->win);
7086                         view->has_scrolled = FALSE;
7087                         if (view->pipe)
7088                                 loading = TRUE;
7089                 }
7091                 /* Update the cursor position. */
7092                 if (prompt_position) {
7093                         getbegyx(status_win, cursor_y, cursor_x);
7094                         cursor_x = prompt_position;
7095                 } else {
7096                         view = display[current_view];
7097                         getbegyx(view->win, cursor_y, cursor_x);
7098                         cursor_x = view->width - 1;
7099                         cursor_y += view->lineno - view->offset;
7100                 }
7101                 setsyx(cursor_y, cursor_x);
7103                 /* Refresh, accept single keystroke of input */
7104                 doupdate();
7105                 nodelay(status_win, loading);
7106                 key = wgetch(status_win);
7108                 /* wgetch() with nodelay() enabled returns ERR when
7109                  * there's no input. */
7110                 if (key == ERR) {
7112                 } else if (key == KEY_RESIZE) {
7113                         int height, width;
7115                         getmaxyx(stdscr, height, width);
7117                         wresize(status_win, 1, width);
7118                         mvwin(status_win, height - 1, 0);
7119                         wnoutrefresh(status_win);
7120                         resize_display();
7121                         redraw_display(TRUE);
7123                 } else {
7124                         input_mode = FALSE;
7125                         return key;
7126                 }
7127         }
7130 static char *
7131 prompt_input(const char *prompt, input_handler handler, void *data)
7133         enum input_status status = INPUT_OK;
7134         static char buf[SIZEOF_STR];
7135         size_t pos = 0;
7137         buf[pos] = 0;
7139         while (status == INPUT_OK || status == INPUT_SKIP) {
7140                 int key;
7142                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7143                 wclrtoeol(status_win);
7145                 key = get_input(pos + 1);
7146                 switch (key) {
7147                 case KEY_RETURN:
7148                 case KEY_ENTER:
7149                 case '\n':
7150                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7151                         break;
7153                 case KEY_BACKSPACE:
7154                         if (pos > 0)
7155                                 buf[--pos] = 0;
7156                         else
7157                                 status = INPUT_CANCEL;
7158                         break;
7160                 case KEY_ESC:
7161                         status = INPUT_CANCEL;
7162                         break;
7164                 default:
7165                         if (pos >= sizeof(buf)) {
7166                                 report("Input string too long");
7167                                 return NULL;
7168                         }
7170                         status = handler(data, buf, key);
7171                         if (status == INPUT_OK)
7172                                 buf[pos++] = (char) key;
7173                 }
7174         }
7176         /* Clear the status window */
7177         status_empty = FALSE;
7178         report("");
7180         if (status == INPUT_CANCEL)
7181                 return NULL;
7183         buf[pos++] = 0;
7185         return buf;
7188 static enum input_status
7189 prompt_yesno_handler(void *data, char *buf, int c)
7191         if (c == 'y' || c == 'Y')
7192                 return INPUT_STOP;
7193         if (c == 'n' || c == 'N')
7194                 return INPUT_CANCEL;
7195         return INPUT_SKIP;
7198 static bool
7199 prompt_yesno(const char *prompt)
7201         char prompt2[SIZEOF_STR];
7203         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7204                 return FALSE;
7206         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7209 static enum input_status
7210 read_prompt_handler(void *data, char *buf, int c)
7212         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7215 static char *
7216 read_prompt(const char *prompt)
7218         return prompt_input(prompt, read_prompt_handler, NULL);
7221 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7223         enum input_status status = INPUT_OK;
7224         int size = 0;
7226         while (items[size].text)
7227                 size++;
7229         while (status == INPUT_OK) {
7230                 const struct menu_item *item = &items[*selected];
7231                 int key;
7232                 int i;
7234                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7235                           prompt, *selected + 1, size);
7236                 if (item->hotkey)
7237                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7238                 wprintw(status_win, "%s", item->text);
7239                 wclrtoeol(status_win);
7241                 key = get_input(COLS - 1);
7242                 switch (key) {
7243                 case KEY_RETURN:
7244                 case KEY_ENTER:
7245                 case '\n':
7246                         status = INPUT_STOP;
7247                         break;
7249                 case KEY_LEFT:
7250                 case KEY_UP:
7251                         *selected = *selected - 1;
7252                         if (*selected < 0)
7253                                 *selected = size - 1;
7254                         break;
7256                 case KEY_RIGHT:
7257                 case KEY_DOWN:
7258                         *selected = (*selected + 1) % size;
7259                         break;
7261                 case KEY_ESC:
7262                         status = INPUT_CANCEL;
7263                         break;
7265                 default:
7266                         for (i = 0; items[i].text; i++)
7267                                 if (items[i].hotkey == key) {
7268                                         *selected = i;
7269                                         status = INPUT_STOP;
7270                                         break;
7271                                 }
7272                 }
7273         }
7275         /* Clear the status window */
7276         status_empty = FALSE;
7277         report("");
7279         return status != INPUT_CANCEL;
7282 /*
7283  * Repository properties
7284  */
7286 static struct ref **refs = NULL;
7287 static size_t refs_size = 0;
7288 static struct ref *refs_head = NULL;
7290 static struct ref_list **ref_lists = NULL;
7291 static size_t ref_lists_size = 0;
7293 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7294 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7295 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7297 static int
7298 compare_refs(const void *ref1_, const void *ref2_)
7300         const struct ref *ref1 = *(const struct ref **)ref1_;
7301         const struct ref *ref2 = *(const struct ref **)ref2_;
7303         if (ref1->tag != ref2->tag)
7304                 return ref2->tag - ref1->tag;
7305         if (ref1->ltag != ref2->ltag)
7306                 return ref2->ltag - ref2->ltag;
7307         if (ref1->head != ref2->head)
7308                 return ref2->head - ref1->head;
7309         if (ref1->tracked != ref2->tracked)
7310                 return ref2->tracked - ref1->tracked;
7311         if (ref1->remote != ref2->remote)
7312                 return ref2->remote - ref1->remote;
7313         return strcmp(ref1->name, ref2->name);
7316 static void
7317 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7319         size_t i;
7321         for (i = 0; i < refs_size; i++)
7322                 if (!visitor(data, refs[i]))
7323                         break;
7326 static struct ref *
7327 get_ref_head()
7329         return refs_head;
7332 static struct ref_list *
7333 get_ref_list(const char *id)
7335         struct ref_list *list;
7336         size_t i;
7338         for (i = 0; i < ref_lists_size; i++)
7339                 if (!strcmp(id, ref_lists[i]->id))
7340                         return ref_lists[i];
7342         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7343                 return NULL;
7344         list = calloc(1, sizeof(*list));
7345         if (!list)
7346                 return NULL;
7348         for (i = 0; i < refs_size; i++) {
7349                 if (!strcmp(id, refs[i]->id) &&
7350                     realloc_refs_list(&list->refs, list->size, 1))
7351                         list->refs[list->size++] = refs[i];
7352         }
7354         if (!list->refs) {
7355                 free(list);
7356                 return NULL;
7357         }
7359         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7360         ref_lists[ref_lists_size++] = list;
7361         return list;
7364 static int
7365 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7367         struct ref *ref = NULL;
7368         bool tag = FALSE;
7369         bool ltag = FALSE;
7370         bool remote = FALSE;
7371         bool tracked = FALSE;
7372         bool head = FALSE;
7373         int from = 0, to = refs_size - 1;
7375         if (!prefixcmp(name, "refs/tags/")) {
7376                 if (!suffixcmp(name, namelen, "^{}")) {
7377                         namelen -= 3;
7378                         name[namelen] = 0;
7379                 } else {
7380                         ltag = TRUE;
7381                 }
7383                 tag = TRUE;
7384                 namelen -= STRING_SIZE("refs/tags/");
7385                 name    += STRING_SIZE("refs/tags/");
7387         } else if (!prefixcmp(name, "refs/remotes/")) {
7388                 remote = TRUE;
7389                 namelen -= STRING_SIZE("refs/remotes/");
7390                 name    += STRING_SIZE("refs/remotes/");
7391                 tracked  = !strcmp(opt_remote, name);
7393         } else if (!prefixcmp(name, "refs/heads/")) {
7394                 namelen -= STRING_SIZE("refs/heads/");
7395                 name    += STRING_SIZE("refs/heads/");
7396                 if (!strncmp(opt_head, name, namelen))
7397                         return OK;
7399         } else if (!strcmp(name, "HEAD")) {
7400                 head     = TRUE;
7401                 if (*opt_head) {
7402                         namelen  = strlen(opt_head);
7403                         name     = opt_head;
7404                 }
7405         }
7407         /* If we are reloading or it's an annotated tag, replace the
7408          * previous SHA1 with the resolved commit id; relies on the fact
7409          * git-ls-remote lists the commit id of an annotated tag right
7410          * before the commit id it points to. */
7411         while (from <= to) {
7412                 size_t pos = (to + from) / 2;
7413                 int cmp = strcmp(name, refs[pos]->name);
7415                 if (!cmp) {
7416                         ref = refs[pos];
7417                         break;
7418                 }
7420                 if (cmp < 0)
7421                         to = pos - 1;
7422                 else
7423                         from = pos + 1;
7424         }
7426         if (!ref) {
7427                 if (!realloc_refs(&refs, refs_size, 1))
7428                         return ERR;
7429                 ref = calloc(1, sizeof(*ref) + namelen);
7430                 if (!ref)
7431                         return ERR;
7432                 memmove(refs + from + 1, refs + from,
7433                         (refs_size - from) * sizeof(*refs));
7434                 refs[from] = ref;
7435                 strncpy(ref->name, name, namelen);
7436                 refs_size++;
7437         }
7439         ref->head = head;
7440         ref->tag = tag;
7441         ref->ltag = ltag;
7442         ref->remote = remote;
7443         ref->tracked = tracked;
7444         string_copy_rev(ref->id, id);
7446         if (head)
7447                 refs_head = ref;
7448         return OK;
7451 static int
7452 load_refs(void)
7454         const char *head_argv[] = {
7455                 "git", "symbolic-ref", "HEAD", NULL
7456         };
7457         static const char *ls_remote_argv[SIZEOF_ARG] = {
7458                 "git", "ls-remote", opt_git_dir, NULL
7459         };
7460         static bool init = FALSE;
7461         size_t i;
7463         if (!init) {
7464                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7465                         die("TIG_LS_REMOTE contains too many arguments");
7466                 init = TRUE;
7467         }
7469         if (!*opt_git_dir)
7470                 return OK;
7472         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7473             !prefixcmp(opt_head, "refs/heads/")) {
7474                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7476                 memmove(opt_head, offset, strlen(offset) + 1);
7477         }
7479         refs_head = NULL;
7480         for (i = 0; i < refs_size; i++)
7481                 refs[i]->id[0] = 0;
7483         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7484                 return ERR;
7486         /* Update the ref lists to reflect changes. */
7487         for (i = 0; i < ref_lists_size; i++) {
7488                 struct ref_list *list = ref_lists[i];
7489                 size_t old, new;
7491                 for (old = new = 0; old < list->size; old++)
7492                         if (!strcmp(list->id, list->refs[old]->id))
7493                                 list->refs[new++] = list->refs[old];
7494                 list->size = new;
7495         }
7497         return OK;
7500 static void
7501 set_remote_branch(const char *name, const char *value, size_t valuelen)
7503         if (!strcmp(name, ".remote")) {
7504                 string_ncopy(opt_remote, value, valuelen);
7506         } else if (*opt_remote && !strcmp(name, ".merge")) {
7507                 size_t from = strlen(opt_remote);
7509                 if (!prefixcmp(value, "refs/heads/"))
7510                         value += STRING_SIZE("refs/heads/");
7512                 if (!string_format_from(opt_remote, &from, "/%s", value))
7513                         opt_remote[0] = 0;
7514         }
7517 static void
7518 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7520         const char *argv[SIZEOF_ARG] = { name, "=" };
7521         int argc = 1 + (cmd == option_set_command);
7522         int error = ERR;
7524         if (!argv_from_string(argv, &argc, value))
7525                 config_msg = "Too many option arguments";
7526         else
7527                 error = cmd(argc, argv);
7529         if (error == ERR)
7530                 warn("Option 'tig.%s': %s", name, config_msg);
7533 static bool
7534 set_environment_variable(const char *name, const char *value)
7536         size_t len = strlen(name) + 1 + strlen(value) + 1;
7537         char *env = malloc(len);
7539         if (env &&
7540             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7541             putenv(env) == 0)
7542                 return TRUE;
7543         free(env);
7544         return FALSE;
7547 static void
7548 set_work_tree(const char *value)
7550         char cwd[SIZEOF_STR];
7552         if (!getcwd(cwd, sizeof(cwd)))
7553                 die("Failed to get cwd path: %s", strerror(errno));
7554         if (chdir(opt_git_dir) < 0)
7555                 die("Failed to chdir(%s): %s", strerror(errno));
7556         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7557                 die("Failed to get git path: %s", strerror(errno));
7558         if (chdir(cwd) < 0)
7559                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7560         if (chdir(value) < 0)
7561                 die("Failed to chdir(%s): %s", value, strerror(errno));
7562         if (!getcwd(cwd, sizeof(cwd)))
7563                 die("Failed to get cwd path: %s", strerror(errno));
7564         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7565                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7566         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7567                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7568         opt_is_inside_work_tree = TRUE;
7571 static int
7572 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7574         if (!strcmp(name, "i18n.commitencoding"))
7575                 string_ncopy(opt_encoding, value, valuelen);
7577         else if (!strcmp(name, "core.editor"))
7578                 string_ncopy(opt_editor, value, valuelen);
7580         else if (!strcmp(name, "core.worktree"))
7581                 set_work_tree(value);
7583         else if (!prefixcmp(name, "tig.color."))
7584                 set_repo_config_option(name + 10, value, option_color_command);
7586         else if (!prefixcmp(name, "tig.bind."))
7587                 set_repo_config_option(name + 9, value, option_bind_command);
7589         else if (!prefixcmp(name, "tig."))
7590                 set_repo_config_option(name + 4, value, option_set_command);
7592         else if (*opt_head && !prefixcmp(name, "branch.") &&
7593                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7594                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7596         return OK;
7599 static int
7600 load_git_config(void)
7602         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7604         return io_run_load(config_list_argv, "=", read_repo_config_option);
7607 static int
7608 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7610         if (!opt_git_dir[0]) {
7611                 string_ncopy(opt_git_dir, name, namelen);
7613         } else if (opt_is_inside_work_tree == -1) {
7614                 /* This can be 3 different values depending on the
7615                  * version of git being used. If git-rev-parse does not
7616                  * understand --is-inside-work-tree it will simply echo
7617                  * the option else either "true" or "false" is printed.
7618                  * Default to true for the unknown case. */
7619                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7621         } else if (*name == '.') {
7622                 string_ncopy(opt_cdup, name, namelen);
7624         } else {
7625                 string_ncopy(opt_prefix, name, namelen);
7626         }
7628         return OK;
7631 static int
7632 load_repo_info(void)
7634         const char *rev_parse_argv[] = {
7635                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7636                         "--show-cdup", "--show-prefix", NULL
7637         };
7639         return io_run_load(rev_parse_argv, "=", read_repo_info);
7643 /*
7644  * Main
7645  */
7647 static const char usage[] =
7648 "tig " TIG_VERSION " (" __DATE__ ")\n"
7649 "\n"
7650 "Usage: tig        [options] [revs] [--] [paths]\n"
7651 "   or: tig show   [options] [revs] [--] [paths]\n"
7652 "   or: tig blame  [rev] path\n"
7653 "   or: tig status\n"
7654 "   or: tig <      [git command output]\n"
7655 "\n"
7656 "Options:\n"
7657 "  -v, --version   Show version and exit\n"
7658 "  -h, --help      Show help message and exit";
7660 static void __NORETURN
7661 quit(int sig)
7663         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7664         if (cursed)
7665                 endwin();
7666         exit(0);
7669 static void __NORETURN
7670 die(const char *err, ...)
7672         va_list args;
7674         endwin();
7676         va_start(args, err);
7677         fputs("tig: ", stderr);
7678         vfprintf(stderr, err, args);
7679         fputs("\n", stderr);
7680         va_end(args);
7682         exit(1);
7685 static void
7686 warn(const char *msg, ...)
7688         va_list args;
7690         va_start(args, msg);
7691         fputs("tig warning: ", stderr);
7692         vfprintf(stderr, msg, args);
7693         fputs("\n", stderr);
7694         va_end(args);
7697 static enum request
7698 parse_options(int argc, const char *argv[])
7700         enum request request = REQ_VIEW_MAIN;
7701         const char *subcommand;
7702         bool seen_dashdash = FALSE;
7703         /* XXX: This is vulnerable to the user overriding options
7704          * required for the main view parser. */
7705         const char *custom_argv[SIZEOF_ARG] = {
7706                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7707                         "--topo-order", NULL
7708         };
7709         int i, j = 6;
7711         if (!isatty(STDIN_FILENO)) {
7712                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7713                 return REQ_VIEW_PAGER;
7714         }
7716         if (argc <= 1)
7717                 return REQ_NONE;
7719         subcommand = argv[1];
7720         if (!strcmp(subcommand, "status")) {
7721                 if (argc > 2)
7722                         warn("ignoring arguments after `%s'", subcommand);
7723                 return REQ_VIEW_STATUS;
7725         } else if (!strcmp(subcommand, "blame")) {
7726                 if (argc <= 2 || argc > 4)
7727                         die("invalid number of options to blame\n\n%s", usage);
7729                 i = 2;
7730                 if (argc == 4) {
7731                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7732                         i++;
7733                 }
7735                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7736                 return REQ_VIEW_BLAME;
7738         } else if (!strcmp(subcommand, "show")) {
7739                 request = REQ_VIEW_DIFF;
7741         } else {
7742                 subcommand = NULL;
7743         }
7745         if (subcommand) {
7746                 custom_argv[1] = subcommand;
7747                 j = 2;
7748         }
7750         for (i = 1 + !!subcommand; i < argc; i++) {
7751                 const char *opt = argv[i];
7753                 if (seen_dashdash || !strcmp(opt, "--")) {
7754                         seen_dashdash = TRUE;
7756                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7757                         printf("tig version %s\n", TIG_VERSION);
7758                         quit(0);
7760                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7761                         printf("%s\n", usage);
7762                         quit(0);
7763                 }
7765                 custom_argv[j++] = opt;
7766                 if (j >= ARRAY_SIZE(custom_argv))
7767                         die("command too long");
7768         }
7770         if (!prepare_update(VIEW(request), custom_argv, NULL))
7771                 die("Failed to format arguments");
7773         return request;
7776 int
7777 main(int argc, const char *argv[])
7779         const char *codeset = "UTF-8";
7780         enum request request = parse_options(argc, argv);
7781         struct view *view;
7782         size_t i;
7784         signal(SIGINT, quit);
7785         signal(SIGPIPE, SIG_IGN);
7787         if (setlocale(LC_ALL, "")) {
7788                 codeset = nl_langinfo(CODESET);
7789         }
7791         if (load_repo_info() == ERR)
7792                 die("Failed to load repo info.");
7794         if (load_options() == ERR)
7795                 die("Failed to load user config.");
7797         if (load_git_config() == ERR)
7798                 die("Failed to load repo config.");
7800         /* Require a git repository unless when running in pager mode. */
7801         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7802                 die("Not a git repository");
7804         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7805                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7806                 if (opt_iconv_in == ICONV_NONE)
7807                         die("Failed to initialize character set conversion");
7808         }
7810         if (codeset && strcmp(codeset, "UTF-8")) {
7811                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7812                 if (opt_iconv_out == ICONV_NONE)
7813                         die("Failed to initialize character set conversion");
7814         }
7816         if (load_refs() == ERR)
7817                 die("Failed to load refs.");
7819         foreach_view (view, i)
7820                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7821                         die("Too many arguments in the `%s` environment variable",
7822                             view->cmd_env);
7824         init_display();
7826         if (request != REQ_NONE)
7827                 open_view(NULL, request, OPEN_PREPARED);
7828         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7830         while (view_driver(display[current_view], request)) {
7831                 int key = get_input(0);
7833                 view = display[current_view];
7834                 request = get_keybinding(view->keymap, key);
7836                 /* Some low-level request handling. This keeps access to
7837                  * status_win restricted. */
7838                 switch (request) {
7839                 case REQ_NONE:
7840                         report("Unknown key, press %s for help",
7841                                get_key(view->keymap, REQ_VIEW_HELP));
7842                         break;
7843                 case REQ_PROMPT:
7844                 {
7845                         char *cmd = read_prompt(":");
7847                         if (cmd && isdigit(*cmd)) {
7848                                 int lineno = view->lineno + 1;
7850                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7851                                         select_view_line(view, lineno - 1);
7852                                         report("");
7853                                 } else {
7854                                         report("Unable to parse '%s' as a line number", cmd);
7855                                 }
7857                         } else if (cmd) {
7858                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7859                                 const char *argv[SIZEOF_ARG] = { "git" };
7860                                 int argc = 1;
7862                                 /* When running random commands, initially show the
7863                                  * command in the title. However, it maybe later be
7864                                  * overwritten if a commit line is selected. */
7865                                 string_ncopy(next->ref, cmd, strlen(cmd));
7867                                 if (!argv_from_string(argv, &argc, cmd)) {
7868                                         report("Too many arguments");
7869                                 } else if (!prepare_update(next, argv, NULL)) {
7870                                         report("Failed to format command");
7871                                 } else {
7872                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7873                                 }
7874                         }
7876                         request = REQ_NONE;
7877                         break;
7878                 }
7879                 case REQ_SEARCH:
7880                 case REQ_SEARCH_BACK:
7881                 {
7882                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7883                         char *search = read_prompt(prompt);
7885                         if (search)
7886                                 string_ncopy(opt_search, search, strlen(search));
7887                         else if (*opt_search)
7888                                 request = request == REQ_SEARCH ?
7889                                         REQ_FIND_NEXT :
7890                                         REQ_FIND_PREV;
7891                         else
7892                                 request = REQ_NONE;
7893                         break;
7894                 }
7895                 default:
7896                         break;
7897                 }
7898         }
7900         quit(0);
7902         return 0;