Code

Add view->prev to track history leaving view->parent for split 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 #define view_has_parent(view, child_type, parent_type) \
2324         (view->type == child_type && view->parent && view->parent->type == parent_type)
2326 static inline void
2327 set_view_attr(struct view *view, enum line_type type)
2329         if (!view->curline->selected && view->curtype != type) {
2330                 (void) wattrset(view->win, get_line_attr(type));
2331                 wchgat(view->win, -1, 0, type, NULL);
2332                 view->curtype = type;
2333         }
2336 static int
2337 draw_chars(struct view *view, enum line_type type, const char *string,
2338            int max_len, bool use_tilde)
2340         static char out_buffer[BUFSIZ * 2];
2341         int len = 0;
2342         int col = 0;
2343         int trimmed = FALSE;
2344         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2346         if (max_len <= 0)
2347                 return 0;
2349         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2351         set_view_attr(view, type);
2352         if (len > 0) {
2353                 if (opt_iconv_out != ICONV_NONE) {
2354                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2355                         size_t inlen = len + 1;
2357                         char *outbuf = out_buffer;
2358                         size_t outlen = sizeof(out_buffer);
2360                         size_t ret;
2362                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2363                         if (ret != (size_t) -1) {
2364                                 string = out_buffer;
2365                                 len = sizeof(out_buffer) - outlen;
2366                         }
2367                 }
2369                 waddnstr(view->win, string, len);
2370         }
2371         if (trimmed && use_tilde) {
2372                 set_view_attr(view, LINE_DELIMITER);
2373                 waddch(view->win, '~');
2374                 col++;
2375         }
2377         return col;
2380 static int
2381 draw_space(struct view *view, enum line_type type, int max, int spaces)
2383         static char space[] = "                    ";
2384         int col = 0;
2386         spaces = MIN(max, spaces);
2388         while (spaces > 0) {
2389                 int len = MIN(spaces, sizeof(space) - 1);
2391                 col += draw_chars(view, type, space, len, FALSE);
2392                 spaces -= len;
2393         }
2395         return col;
2398 static bool
2399 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2401         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2402         return view->width + view->yoffset <= view->col;
2405 static bool
2406 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2408         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2409         int max = view->width + view->yoffset - view->col;
2410         int i;
2412         if (max < size)
2413                 size = max;
2415         set_view_attr(view, type);
2416         /* Using waddch() instead of waddnstr() ensures that
2417          * they'll be rendered correctly for the cursor line. */
2418         for (i = skip; i < size; i++)
2419                 waddch(view->win, graphic[i]);
2421         view->col += size;
2422         if (size < max && skip <= size)
2423                 waddch(view->win, ' ');
2424         view->col++;
2426         return view->width + view->yoffset <= view->col;
2429 static bool
2430 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2432         int max = MIN(view->width + view->yoffset - view->col, len);
2433         int col;
2435         if (text)
2436                 col = draw_chars(view, type, text, max - 1, trim);
2437         else
2438                 col = draw_space(view, type, max - 1, max - 1);
2440         view->col += col;
2441         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2442         return view->width + view->yoffset <= view->col;
2445 static bool
2446 draw_date(struct view *view, struct time *time)
2448         const char *date = mkdate(time, opt_date);
2449         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2451         return draw_field(view, LINE_DATE, date, cols, FALSE);
2454 static bool
2455 draw_author(struct view *view, const char *author)
2457         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2458         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2460         if (abbreviate && author)
2461                 author = get_author_initials(author);
2463         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2466 static bool
2467 draw_mode(struct view *view, mode_t mode)
2469         const char *str;
2471         if (S_ISDIR(mode))
2472                 str = "drwxr-xr-x";
2473         else if (S_ISLNK(mode))
2474                 str = "lrwxrwxrwx";
2475         else if (S_ISGITLINK(mode))
2476                 str = "m---------";
2477         else if (S_ISREG(mode) && mode & S_IXUSR)
2478                 str = "-rwxr-xr-x";
2479         else if (S_ISREG(mode))
2480                 str = "-rw-r--r--";
2481         else
2482                 str = "----------";
2484         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2487 static bool
2488 draw_lineno(struct view *view, unsigned int lineno)
2490         char number[10];
2491         int digits3 = view->digits < 3 ? 3 : view->digits;
2492         int max = MIN(view->width + view->yoffset - view->col, digits3);
2493         char *text = NULL;
2494         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2496         lineno += view->offset + 1;
2497         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2498                 static char fmt[] = "%1ld";
2500                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2501                 if (string_format(number, fmt, lineno))
2502                         text = number;
2503         }
2504         if (text)
2505                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2506         else
2507                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2508         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2511 static bool
2512 draw_view_line(struct view *view, unsigned int lineno)
2514         struct line *line;
2515         bool selected = (view->offset + lineno == view->lineno);
2517         assert(view_is_displayed(view));
2519         if (view->offset + lineno >= view->lines)
2520                 return FALSE;
2522         line = &view->line[view->offset + lineno];
2524         wmove(view->win, lineno, 0);
2525         if (line->cleareol)
2526                 wclrtoeol(view->win);
2527         view->col = 0;
2528         view->curline = line;
2529         view->curtype = LINE_NONE;
2530         line->selected = FALSE;
2531         line->dirty = line->cleareol = 0;
2533         if (selected) {
2534                 set_view_attr(view, LINE_CURSOR);
2535                 line->selected = TRUE;
2536                 view->ops->select(view, line);
2537         }
2539         return view->ops->draw(view, line, lineno);
2542 static void
2543 redraw_view_dirty(struct view *view)
2545         bool dirty = FALSE;
2546         int lineno;
2548         for (lineno = 0; lineno < view->height; lineno++) {
2549                 if (view->offset + lineno >= view->lines)
2550                         break;
2551                 if (!view->line[view->offset + lineno].dirty)
2552                         continue;
2553                 dirty = TRUE;
2554                 if (!draw_view_line(view, lineno))
2555                         break;
2556         }
2558         if (!dirty)
2559                 return;
2560         wnoutrefresh(view->win);
2563 static void
2564 redraw_view_from(struct view *view, int lineno)
2566         assert(0 <= lineno && lineno < view->height);
2568         for (; lineno < view->height; lineno++) {
2569                 if (!draw_view_line(view, lineno))
2570                         break;
2571         }
2573         wnoutrefresh(view->win);
2576 static void
2577 redraw_view(struct view *view)
2579         werase(view->win);
2580         redraw_view_from(view, 0);
2584 static void
2585 update_view_title(struct view *view)
2587         char buf[SIZEOF_STR];
2588         char state[SIZEOF_STR];
2589         size_t bufpos = 0, statelen = 0;
2591         assert(view_is_displayed(view));
2593         if (view->type != VIEW_STATUS && view->lines) {
2594                 unsigned int view_lines = view->offset + view->height;
2595                 unsigned int lines = view->lines
2596                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2597                                    : 0;
2599                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2600                                    view->ops->type,
2601                                    view->lineno + 1,
2602                                    view->lines,
2603                                    lines);
2605         }
2607         if (view->pipe) {
2608                 time_t secs = time(NULL) - view->start_time;
2610                 /* Three git seconds are a long time ... */
2611                 if (secs > 2)
2612                         string_format_from(state, &statelen, " loading %lds", secs);
2613         }
2615         string_format_from(buf, &bufpos, "[%s]", view->name);
2616         if (*view->ref && bufpos < view->width) {
2617                 size_t refsize = strlen(view->ref);
2618                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2620                 if (minsize < view->width)
2621                         refsize = view->width - minsize + 7;
2622                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2623         }
2625         if (statelen && bufpos < view->width) {
2626                 string_format_from(buf, &bufpos, "%s", state);
2627         }
2629         if (view == display[current_view])
2630                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2631         else
2632                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2634         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2635         wclrtoeol(view->title);
2636         wnoutrefresh(view->title);
2639 static int
2640 apply_step(double step, int value)
2642         if (step >= 1)
2643                 return (int) step;
2644         value *= step + 0.01;
2645         return value ? value : 1;
2648 static void
2649 resize_display(void)
2651         int offset, i;
2652         struct view *base = display[0];
2653         struct view *view = display[1] ? display[1] : display[0];
2655         /* Setup window dimensions */
2657         getmaxyx(stdscr, base->height, base->width);
2659         /* Make room for the status window. */
2660         base->height -= 1;
2662         if (view != base) {
2663                 /* Horizontal split. */
2664                 view->width   = base->width;
2665                 view->height  = apply_step(opt_scale_split_view, base->height);
2666                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2667                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2668                 base->height -= view->height;
2670                 /* Make room for the title bar. */
2671                 view->height -= 1;
2672         }
2674         /* Make room for the title bar. */
2675         base->height -= 1;
2677         offset = 0;
2679         foreach_displayed_view (view, i) {
2680                 if (!view->win) {
2681                         view->win = newwin(view->height, 0, offset, 0);
2682                         if (!view->win)
2683                                 die("Failed to create %s view", view->name);
2685                         scrollok(view->win, FALSE);
2687                         view->title = newwin(1, 0, offset + view->height, 0);
2688                         if (!view->title)
2689                                 die("Failed to create title window");
2691                 } else {
2692                         wresize(view->win, view->height, view->width);
2693                         mvwin(view->win,   offset, 0);
2694                         mvwin(view->title, offset + view->height, 0);
2695                 }
2697                 offset += view->height + 1;
2698         }
2701 static void
2702 redraw_display(bool clear)
2704         struct view *view;
2705         int i;
2707         foreach_displayed_view (view, i) {
2708                 if (clear)
2709                         wclear(view->win);
2710                 redraw_view(view);
2711                 update_view_title(view);
2712         }
2715 static void
2716 toggle_enum_option_do(unsigned int *opt, const char *help,
2717                       const struct enum_map *map, size_t size)
2719         *opt = (*opt + 1) % size;
2720         redraw_display(FALSE);
2721         report("Displaying %s %s", enum_name(map[*opt]), help);
2724 #define toggle_enum_option(opt, help, map) \
2725         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2727 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2728 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2730 static void
2731 toggle_view_option(bool *option, const char *help)
2733         *option = !*option;
2734         redraw_display(FALSE);
2735         report("%sabling %s", *option ? "En" : "Dis", help);
2738 static void
2739 open_option_menu(void)
2741         const struct menu_item menu[] = {
2742                 { '.', "line numbers", &opt_line_number },
2743                 { 'D', "date display", &opt_date },
2744                 { 'A', "author display", &opt_author },
2745                 { 'g', "revision graph display", &opt_rev_graph },
2746                 { 'F', "reference display", &opt_show_refs },
2747                 { 0 }
2748         };
2749         int selected = 0;
2751         if (prompt_menu("Toggle option", menu, &selected)) {
2752                 if (menu[selected].data == &opt_date)
2753                         toggle_date();
2754                 else if (menu[selected].data == &opt_author)
2755                         toggle_author();
2756                 else
2757                         toggle_view_option(menu[selected].data, menu[selected].text);
2758         }
2761 static void
2762 maximize_view(struct view *view)
2764         memset(display, 0, sizeof(display));
2765         current_view = 0;
2766         display[current_view] = view;
2767         resize_display();
2768         redraw_display(FALSE);
2769         report("");
2773 /*
2774  * Navigation
2775  */
2777 static bool
2778 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2780         if (lineno >= view->lines)
2781                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2783         if (offset > lineno || offset + view->height <= lineno) {
2784                 unsigned long half = view->height / 2;
2786                 if (lineno > half)
2787                         offset = lineno - half;
2788                 else
2789                         offset = 0;
2790         }
2792         if (offset != view->offset || lineno != view->lineno) {
2793                 view->offset = offset;
2794                 view->lineno = lineno;
2795                 return TRUE;
2796         }
2798         return FALSE;
2801 /* Scrolling backend */
2802 static void
2803 do_scroll_view(struct view *view, int lines)
2805         bool redraw_current_line = FALSE;
2807         /* The rendering expects the new offset. */
2808         view->offset += lines;
2810         assert(0 <= view->offset && view->offset < view->lines);
2811         assert(lines);
2813         /* Move current line into the view. */
2814         if (view->lineno < view->offset) {
2815                 view->lineno = view->offset;
2816                 redraw_current_line = TRUE;
2817         } else if (view->lineno >= view->offset + view->height) {
2818                 view->lineno = view->offset + view->height - 1;
2819                 redraw_current_line = TRUE;
2820         }
2822         assert(view->offset <= view->lineno && view->lineno < view->lines);
2824         /* Redraw the whole screen if scrolling is pointless. */
2825         if (view->height < ABS(lines)) {
2826                 redraw_view(view);
2828         } else {
2829                 int line = lines > 0 ? view->height - lines : 0;
2830                 int end = line + ABS(lines);
2832                 scrollok(view->win, TRUE);
2833                 wscrl(view->win, lines);
2834                 scrollok(view->win, FALSE);
2836                 while (line < end && draw_view_line(view, line))
2837                         line++;
2839                 if (redraw_current_line)
2840                         draw_view_line(view, view->lineno - view->offset);
2841                 wnoutrefresh(view->win);
2842         }
2844         view->has_scrolled = TRUE;
2845         report("");
2848 /* Scroll frontend */
2849 static void
2850 scroll_view(struct view *view, enum request request)
2852         int lines = 1;
2854         assert(view_is_displayed(view));
2856         switch (request) {
2857         case REQ_SCROLL_LEFT:
2858                 if (view->yoffset == 0) {
2859                         report("Cannot scroll beyond the first column");
2860                         return;
2861                 }
2862                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2863                         view->yoffset = 0;
2864                 else
2865                         view->yoffset -= apply_step(opt_hscroll, view->width);
2866                 redraw_view_from(view, 0);
2867                 report("");
2868                 return;
2869         case REQ_SCROLL_RIGHT:
2870                 view->yoffset += apply_step(opt_hscroll, view->width);
2871                 redraw_view(view);
2872                 report("");
2873                 return;
2874         case REQ_SCROLL_PAGE_DOWN:
2875                 lines = view->height;
2876         case REQ_SCROLL_LINE_DOWN:
2877                 if (view->offset + lines > view->lines)
2878                         lines = view->lines - view->offset;
2880                 if (lines == 0 || view->offset + view->height >= view->lines) {
2881                         report("Cannot scroll beyond the last line");
2882                         return;
2883                 }
2884                 break;
2886         case REQ_SCROLL_PAGE_UP:
2887                 lines = view->height;
2888         case REQ_SCROLL_LINE_UP:
2889                 if (lines > view->offset)
2890                         lines = view->offset;
2892                 if (lines == 0) {
2893                         report("Cannot scroll beyond the first line");
2894                         return;
2895                 }
2897                 lines = -lines;
2898                 break;
2900         default:
2901                 die("request %d not handled in switch", request);
2902         }
2904         do_scroll_view(view, lines);
2907 /* Cursor moving */
2908 static void
2909 move_view(struct view *view, enum request request)
2911         int scroll_steps = 0;
2912         int steps;
2914         switch (request) {
2915         case REQ_MOVE_FIRST_LINE:
2916                 steps = -view->lineno;
2917                 break;
2919         case REQ_MOVE_LAST_LINE:
2920                 steps = view->lines - view->lineno - 1;
2921                 break;
2923         case REQ_MOVE_PAGE_UP:
2924                 steps = view->height > view->lineno
2925                       ? -view->lineno : -view->height;
2926                 break;
2928         case REQ_MOVE_PAGE_DOWN:
2929                 steps = view->lineno + view->height >= view->lines
2930                       ? view->lines - view->lineno - 1 : view->height;
2931                 break;
2933         case REQ_MOVE_UP:
2934                 steps = -1;
2935                 break;
2937         case REQ_MOVE_DOWN:
2938                 steps = 1;
2939                 break;
2941         default:
2942                 die("request %d not handled in switch", request);
2943         }
2945         if (steps <= 0 && view->lineno == 0) {
2946                 report("Cannot move beyond the first line");
2947                 return;
2949         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2950                 report("Cannot move beyond the last line");
2951                 return;
2952         }
2954         /* Move the current line */
2955         view->lineno += steps;
2956         assert(0 <= view->lineno && view->lineno < view->lines);
2958         /* Check whether the view needs to be scrolled */
2959         if (view->lineno < view->offset ||
2960             view->lineno >= view->offset + view->height) {
2961                 scroll_steps = steps;
2962                 if (steps < 0 && -steps > view->offset) {
2963                         scroll_steps = -view->offset;
2965                 } else if (steps > 0) {
2966                         if (view->lineno == view->lines - 1 &&
2967                             view->lines > view->height) {
2968                                 scroll_steps = view->lines - view->offset - 1;
2969                                 if (scroll_steps >= view->height)
2970                                         scroll_steps -= view->height - 1;
2971                         }
2972                 }
2973         }
2975         if (!view_is_displayed(view)) {
2976                 view->offset += scroll_steps;
2977                 assert(0 <= view->offset && view->offset < view->lines);
2978                 view->ops->select(view, &view->line[view->lineno]);
2979                 return;
2980         }
2982         /* Repaint the old "current" line if we be scrolling */
2983         if (ABS(steps) < view->height)
2984                 draw_view_line(view, view->lineno - steps - view->offset);
2986         if (scroll_steps) {
2987                 do_scroll_view(view, scroll_steps);
2988                 return;
2989         }
2991         /* Draw the current line */
2992         draw_view_line(view, view->lineno - view->offset);
2994         wnoutrefresh(view->win);
2995         report("");
2999 /*
3000  * Searching
3001  */
3003 static void search_view(struct view *view, enum request request);
3005 static bool
3006 grep_text(struct view *view, const char *text[])
3008         regmatch_t pmatch;
3009         size_t i;
3011         for (i = 0; text[i]; i++)
3012                 if (*text[i] &&
3013                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3014                         return TRUE;
3015         return FALSE;
3018 static void
3019 select_view_line(struct view *view, unsigned long lineno)
3021         unsigned long old_lineno = view->lineno;
3022         unsigned long old_offset = view->offset;
3024         if (goto_view_line(view, view->offset, lineno)) {
3025                 if (view_is_displayed(view)) {
3026                         if (old_offset != view->offset) {
3027                                 redraw_view(view);
3028                         } else {
3029                                 draw_view_line(view, old_lineno - view->offset);
3030                                 draw_view_line(view, view->lineno - view->offset);
3031                                 wnoutrefresh(view->win);
3032                         }
3033                 } else {
3034                         view->ops->select(view, &view->line[view->lineno]);
3035                 }
3036         }
3039 static void
3040 find_next(struct view *view, enum request request)
3042         unsigned long lineno = view->lineno;
3043         int direction;
3045         if (!*view->grep) {
3046                 if (!*opt_search)
3047                         report("No previous search");
3048                 else
3049                         search_view(view, request);
3050                 return;
3051         }
3053         switch (request) {
3054         case REQ_SEARCH:
3055         case REQ_FIND_NEXT:
3056                 direction = 1;
3057                 break;
3059         case REQ_SEARCH_BACK:
3060         case REQ_FIND_PREV:
3061                 direction = -1;
3062                 break;
3064         default:
3065                 return;
3066         }
3068         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3069                 lineno += direction;
3071         /* Note, lineno is unsigned long so will wrap around in which case it
3072          * will become bigger than view->lines. */
3073         for (; lineno < view->lines; lineno += direction) {
3074                 if (view->ops->grep(view, &view->line[lineno])) {
3075                         select_view_line(view, lineno);
3076                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3077                         return;
3078                 }
3079         }
3081         report("No match found for '%s'", view->grep);
3084 static void
3085 search_view(struct view *view, enum request request)
3087         int regex_err;
3089         if (view->regex) {
3090                 regfree(view->regex);
3091                 *view->grep = 0;
3092         } else {
3093                 view->regex = calloc(1, sizeof(*view->regex));
3094                 if (!view->regex)
3095                         return;
3096         }
3098         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3099         if (regex_err != 0) {
3100                 char buf[SIZEOF_STR] = "unknown error";
3102                 regerror(regex_err, view->regex, buf, sizeof(buf));
3103                 report("Search failed: %s", buf);
3104                 return;
3105         }
3107         string_copy(view->grep, opt_search);
3109         find_next(view, request);
3112 /*
3113  * Incremental updating
3114  */
3116 static void
3117 reset_view(struct view *view)
3119         int i;
3121         for (i = 0; i < view->lines; i++)
3122                 free(view->line[i].data);
3123         free(view->line);
3125         view->p_offset = view->offset;
3126         view->p_yoffset = view->yoffset;
3127         view->p_lineno = view->lineno;
3129         view->line = NULL;
3130         view->offset = 0;
3131         view->yoffset = 0;
3132         view->lines  = 0;
3133         view->lineno = 0;
3134         view->vid[0] = 0;
3135         view->update_secs = 0;
3138 static void
3139 free_argv(const char *argv[])
3141         int argc;
3143         for (argc = 0; argv[argc]; argc++)
3144                 free((void *) argv[argc]);
3147 static const char *
3148 format_arg(const char *name)
3150         static struct {
3151                 const char *name;
3152                 size_t namelen;
3153                 const char *value;
3154                 const char *value_if_empty;
3155         } vars[] = {
3156 #define FORMAT_VAR(name, value, value_if_empty) \
3157         { name, STRING_SIZE(name), value, value_if_empty }
3158                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3159                 FORMAT_VAR("%(file)",           opt_file,       ""),
3160                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3161                 FORMAT_VAR("%(head)",           ref_head,       ""),
3162                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3163                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3164                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3165         };
3166         int i;
3168         for (i = 0; i < ARRAY_SIZE(vars); i++)
3169                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3170                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3172         report("Unknown replacement: `%s`", name);
3173         return NULL;
3176 static bool
3177 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3179         char buf[SIZEOF_STR];
3180         int argc;
3181         bool noreplace = flags == FORMAT_NONE;
3183         free_argv(dst_argv);
3185         for (argc = 0; src_argv[argc]; argc++) {
3186                 const char *arg = src_argv[argc];
3187                 size_t bufpos = 0;
3189                 while (arg) {
3190                         char *next = strstr(arg, "%(");
3191                         int len = next - arg;
3192                         const char *value;
3194                         if (!next || noreplace) {
3195                                 len = strlen(arg);
3196                                 value = "";
3198                         } else {
3199                                 value = format_arg(next);
3201                                 if (!value) {
3202                                         return FALSE;
3203                                 }
3204                         }
3206                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3207                                 return FALSE;
3209                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3210                 }
3212                 dst_argv[argc] = strdup(buf);
3213                 if (!dst_argv[argc])
3214                         break;
3215         }
3217         dst_argv[argc] = NULL;
3219         return src_argv[argc] == NULL;
3222 static bool
3223 restore_view_position(struct view *view)
3225         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3226                 return FALSE;
3228         /* Changing the view position cancels the restoring. */
3229         /* FIXME: Changing back to the first line is not detected. */
3230         if (view->offset != 0 || view->lineno != 0) {
3231                 view->p_restore = FALSE;
3232                 return FALSE;
3233         }
3235         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3236             view_is_displayed(view))
3237                 werase(view->win);
3239         view->yoffset = view->p_yoffset;
3240         view->p_restore = FALSE;
3242         return TRUE;
3245 static void
3246 end_update(struct view *view, bool force)
3248         if (!view->pipe)
3249                 return;
3250         while (!view->ops->read(view, NULL))
3251                 if (!force)
3252                         return;
3253         if (force)
3254                 io_kill(view->pipe);
3255         io_done(view->pipe);
3256         view->pipe = NULL;
3259 static void
3260 setup_update(struct view *view, const char *vid)
3262         reset_view(view);
3263         string_copy_rev(view->vid, vid);
3264         view->pipe = &view->io;
3265         view->start_time = time(NULL);
3268 static bool
3269 prepare_update(struct view *view, const char *argv[], const char *dir)
3271         if (view->pipe)
3272                 end_update(view, TRUE);
3273         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3276 static bool
3277 prepare_update_file(struct view *view, const char *name)
3279         if (view->pipe)
3280                 end_update(view, TRUE);
3281         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3284 static bool
3285 begin_update(struct view *view, bool refresh)
3287         if (view->pipe)
3288                 end_update(view, TRUE);
3290         if (!refresh) {
3291                 if (view->ops->prepare) {
3292                         if (!view->ops->prepare(view))
3293                                 return FALSE;
3294                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3295                         return FALSE;
3296                 }
3298                 /* Put the current ref_* value to the view title ref
3299                  * member. This is needed by the blob view. Most other
3300                  * views sets it automatically after loading because the
3301                  * first line is a commit line. */
3302                 string_copy_rev(view->ref, view->id);
3303         }
3305         if (!io_start(&view->io))
3306                 return FALSE;
3308         setup_update(view, view->id);
3310         return TRUE;
3313 static bool
3314 update_view(struct view *view)
3316         char out_buffer[BUFSIZ * 2];
3317         char *line;
3318         /* Clear the view and redraw everything since the tree sorting
3319          * might have rearranged things. */
3320         bool redraw = view->lines == 0;
3321         bool can_read = TRUE;
3323         if (!view->pipe)
3324                 return TRUE;
3326         if (!io_can_read(view->pipe)) {
3327                 if (view->lines == 0 && view_is_displayed(view)) {
3328                         time_t secs = time(NULL) - view->start_time;
3330                         if (secs > 1 && secs > view->update_secs) {
3331                                 if (view->update_secs == 0)
3332                                         redraw_view(view);
3333                                 update_view_title(view);
3334                                 view->update_secs = secs;
3335                         }
3336                 }
3337                 return TRUE;
3338         }
3340         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3341                 if (opt_iconv_in != ICONV_NONE) {
3342                         ICONV_CONST char *inbuf = line;
3343                         size_t inlen = strlen(line) + 1;
3345                         char *outbuf = out_buffer;
3346                         size_t outlen = sizeof(out_buffer);
3348                         size_t ret;
3350                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3351                         if (ret != (size_t) -1)
3352                                 line = out_buffer;
3353                 }
3355                 if (!view->ops->read(view, line)) {
3356                         report("Allocation failure");
3357                         end_update(view, TRUE);
3358                         return FALSE;
3359                 }
3360         }
3362         {
3363                 unsigned long lines = view->lines;
3364                 int digits;
3366                 for (digits = 0; lines; digits++)
3367                         lines /= 10;
3369                 /* Keep the displayed view in sync with line number scaling. */
3370                 if (digits != view->digits) {
3371                         view->digits = digits;
3372                         if (opt_line_number || view->type == VIEW_BLAME)
3373                                 redraw = TRUE;
3374                 }
3375         }
3377         if (io_error(view->pipe)) {
3378                 report("Failed to read: %s", io_strerror(view->pipe));
3379                 end_update(view, TRUE);
3381         } else if (io_eof(view->pipe)) {
3382                 report("");
3383                 end_update(view, FALSE);
3384         }
3386         if (restore_view_position(view))
3387                 redraw = TRUE;
3389         if (!view_is_displayed(view))
3390                 return TRUE;
3392         if (redraw)
3393                 redraw_view_from(view, 0);
3394         else
3395                 redraw_view_dirty(view);
3397         /* Update the title _after_ the redraw so that if the redraw picks up a
3398          * commit reference in view->ref it'll be available here. */
3399         update_view_title(view);
3400         return TRUE;
3403 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3405 static struct line *
3406 add_line_data(struct view *view, void *data, enum line_type type)
3408         struct line *line;
3410         if (!realloc_lines(&view->line, view->lines, 1))
3411                 return NULL;
3413         line = &view->line[view->lines++];
3414         memset(line, 0, sizeof(*line));
3415         line->type = type;
3416         line->data = data;
3417         line->dirty = 1;
3419         return line;
3422 static struct line *
3423 add_line_text(struct view *view, const char *text, enum line_type type)
3425         char *data = text ? strdup(text) : NULL;
3427         return data ? add_line_data(view, data, type) : NULL;
3430 static struct line *
3431 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3433         char buf[SIZEOF_STR];
3434         va_list args;
3436         va_start(args, fmt);
3437         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3438                 buf[0] = 0;
3439         va_end(args);
3441         return buf[0] ? add_line_text(view, buf, type) : NULL;
3444 /*
3445  * View opening
3446  */
3448 enum open_flags {
3449         OPEN_DEFAULT = 0,       /* Use default view switching. */
3450         OPEN_SPLIT = 1,         /* Split current view. */
3451         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3452         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3453         OPEN_PREPARED = 32,     /* Open already prepared command. */
3454 };
3456 static void
3457 open_view(struct view *prev, enum request request, enum open_flags flags)
3459         bool split = !!(flags & OPEN_SPLIT);
3460         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3461         bool nomaximize = !!(flags & OPEN_REFRESH);
3462         struct view *view = VIEW(request);
3463         int nviews = displayed_views();
3464         struct view *base_view = display[0];
3466         if (view == prev && nviews == 1 && !reload) {
3467                 report("Already in %s view", view->name);
3468                 return;
3469         }
3471         if (view->git_dir && !opt_git_dir[0]) {
3472                 report("The %s view is disabled in pager view", view->name);
3473                 return;
3474         }
3476         if (split) {
3477                 display[1] = view;
3478                 current_view = 1;
3479                 view->parent = prev;
3480         } else if (!nomaximize) {
3481                 /* Maximize the current view. */
3482                 memset(display, 0, sizeof(display));
3483                 current_view = 0;
3484                 display[current_view] = view;
3485         }
3487         /* No prev signals that this is the first loaded view. */
3488         if (prev && view != prev) {
3489                 view->prev = prev;
3490         }
3492         /* Resize the view when switching between split- and full-screen,
3493          * or when switching between two different full-screen views. */
3494         if (nviews != displayed_views() ||
3495             (nviews == 1 && base_view != display[0]))
3496                 resize_display();
3498         if (view->ops->open) {
3499                 if (view->pipe)
3500                         end_update(view, TRUE);
3501                 if (!view->ops->open(view)) {
3502                         report("Failed to load %s view", view->name);
3503                         return;
3504                 }
3505                 restore_view_position(view);
3507         } else if ((reload || strcmp(view->vid, view->id)) &&
3508                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3509                 report("Failed to load %s view", view->name);
3510                 return;
3511         }
3513         if (split && prev->lineno - prev->offset >= prev->height) {
3514                 /* Take the title line into account. */
3515                 int lines = prev->lineno - prev->offset - prev->height + 1;
3517                 /* Scroll the view that was split if the current line is
3518                  * outside the new limited view. */
3519                 do_scroll_view(prev, lines);
3520         }
3522         if (prev && view != prev && split && view_is_displayed(prev)) {
3523                 /* "Blur" the previous view. */
3524                 update_view_title(prev);
3525         }
3527         if (view->pipe && view->lines == 0) {
3528                 /* Clear the old view and let the incremental updating refill
3529                  * the screen. */
3530                 werase(view->win);
3531                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3532                 report("");
3533         } else if (view_is_displayed(view)) {
3534                 redraw_view(view);
3535                 report("");
3536         }
3539 static void
3540 open_external_viewer(const char *argv[], const char *dir)
3542         def_prog_mode();           /* save current tty modes */
3543         endwin();                  /* restore original tty modes */
3544         io_run_fg(argv, dir);
3545         fprintf(stderr, "Press Enter to continue");
3546         getc(opt_tty);
3547         reset_prog_mode();
3548         redraw_display(TRUE);
3551 static void
3552 open_mergetool(const char *file)
3554         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3556         open_external_viewer(mergetool_argv, opt_cdup);
3559 static void
3560 open_editor(const char *file)
3562         const char *editor_argv[] = { "vi", file, NULL };
3563         const char *editor;
3565         editor = getenv("GIT_EDITOR");
3566         if (!editor && *opt_editor)
3567                 editor = opt_editor;
3568         if (!editor)
3569                 editor = getenv("VISUAL");
3570         if (!editor)
3571                 editor = getenv("EDITOR");
3572         if (!editor)
3573                 editor = "vi";
3575         editor_argv[0] = editor;
3576         open_external_viewer(editor_argv, opt_cdup);
3579 static void
3580 open_run_request(enum request request)
3582         struct run_request *req = get_run_request(request);
3583         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3585         if (!req) {
3586                 report("Unknown run request");
3587                 return;
3588         }
3590         if (format_argv(argv, req->argv, FORMAT_ALL))
3591                 open_external_viewer(argv, NULL);
3592         free_argv(argv);
3595 /*
3596  * User request switch noodle
3597  */
3599 static int
3600 view_driver(struct view *view, enum request request)
3602         int i;
3604         if (request == REQ_NONE)
3605                 return TRUE;
3607         if (request > REQ_NONE) {
3608                 open_run_request(request);
3609                 /* FIXME: When all views can refresh always do this. */
3610                 if (view->refresh)
3611                         request = REQ_REFRESH;
3612                 else
3613                         return TRUE;
3614         }
3616         if (view && view->lines) {
3617                 request = view->ops->request(view, request, &view->line[view->lineno]);
3618                 if (request == REQ_NONE)
3619                         return TRUE;
3620         }
3622         switch (request) {
3623         case REQ_MOVE_UP:
3624         case REQ_MOVE_DOWN:
3625         case REQ_MOVE_PAGE_UP:
3626         case REQ_MOVE_PAGE_DOWN:
3627         case REQ_MOVE_FIRST_LINE:
3628         case REQ_MOVE_LAST_LINE:
3629                 move_view(view, request);
3630                 break;
3632         case REQ_SCROLL_LEFT:
3633         case REQ_SCROLL_RIGHT:
3634         case REQ_SCROLL_LINE_DOWN:
3635         case REQ_SCROLL_LINE_UP:
3636         case REQ_SCROLL_PAGE_DOWN:
3637         case REQ_SCROLL_PAGE_UP:
3638                 scroll_view(view, request);
3639                 break;
3641         case REQ_VIEW_BLAME:
3642                 if (!opt_file[0]) {
3643                         report("No file chosen, press %s to open tree view",
3644                                get_key(view->keymap, REQ_VIEW_TREE));
3645                         break;
3646                 }
3647                 open_view(view, request, OPEN_DEFAULT);
3648                 break;
3650         case REQ_VIEW_BLOB:
3651                 if (!ref_blob[0]) {
3652                         report("No file chosen, press %s to open tree view",
3653                                get_key(view->keymap, REQ_VIEW_TREE));
3654                         break;
3655                 }
3656                 open_view(view, request, OPEN_DEFAULT);
3657                 break;
3659         case REQ_VIEW_PAGER:
3660                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3661                         report("No pager content, press %s to run command from prompt",
3662                                get_key(view->keymap, REQ_PROMPT));
3663                         break;
3664                 }
3665                 open_view(view, request, OPEN_DEFAULT);
3666                 break;
3668         case REQ_VIEW_STAGE:
3669                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3670                         report("No stage content, press %s to open the status view and choose file",
3671                                get_key(view->keymap, REQ_VIEW_STATUS));
3672                         break;
3673                 }
3674                 open_view(view, request, OPEN_DEFAULT);
3675                 break;
3677         case REQ_VIEW_STATUS:
3678                 if (opt_is_inside_work_tree == FALSE) {
3679                         report("The status view requires a working tree");
3680                         break;
3681                 }
3682                 open_view(view, request, OPEN_DEFAULT);
3683                 break;
3685         case REQ_VIEW_MAIN:
3686         case REQ_VIEW_DIFF:
3687         case REQ_VIEW_LOG:
3688         case REQ_VIEW_TREE:
3689         case REQ_VIEW_HELP:
3690         case REQ_VIEW_BRANCH:
3691                 open_view(view, request, OPEN_DEFAULT);
3692                 break;
3694         case REQ_NEXT:
3695         case REQ_PREVIOUS:
3696                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3698                 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3699                     view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3700                     view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3701                     view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3702                     view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3703                         int line;
3705                         view = view->parent;
3706                         line = view->lineno;
3707                         move_view(view, request);
3708                         if (view_is_displayed(view))
3709                                 update_view_title(view);
3710                         if (line != view->lineno)
3711                                 view->ops->request(view, REQ_ENTER,
3712                                                    &view->line[view->lineno]);
3714                 } else {
3715                         move_view(view, request);
3716                 }
3717                 break;
3719         case REQ_VIEW_NEXT:
3720         {
3721                 int nviews = displayed_views();
3722                 int next_view = (current_view + 1) % nviews;
3724                 if (next_view == current_view) {
3725                         report("Only one view is displayed");
3726                         break;
3727                 }
3729                 current_view = next_view;
3730                 /* Blur out the title of the previous view. */
3731                 update_view_title(view);
3732                 report("");
3733                 break;
3734         }
3735         case REQ_REFRESH:
3736                 report("Refreshing is not yet supported for the %s view", view->name);
3737                 break;
3739         case REQ_MAXIMIZE:
3740                 if (displayed_views() == 2)
3741                         maximize_view(view);
3742                 break;
3744         case REQ_OPTIONS:
3745                 open_option_menu();
3746                 break;
3748         case REQ_TOGGLE_LINENO:
3749                 toggle_view_option(&opt_line_number, "line numbers");
3750                 break;
3752         case REQ_TOGGLE_DATE:
3753                 toggle_date();
3754                 break;
3756         case REQ_TOGGLE_AUTHOR:
3757                 toggle_author();
3758                 break;
3760         case REQ_TOGGLE_REV_GRAPH:
3761                 toggle_view_option(&opt_rev_graph, "revision graph display");
3762                 break;
3764         case REQ_TOGGLE_REFS:
3765                 toggle_view_option(&opt_show_refs, "reference display");
3766                 break;
3768         case REQ_TOGGLE_SORT_FIELD:
3769         case REQ_TOGGLE_SORT_ORDER:
3770                 report("Sorting is not yet supported for the %s view", view->name);
3771                 break;
3773         case REQ_SEARCH:
3774         case REQ_SEARCH_BACK:
3775                 search_view(view, request);
3776                 break;
3778         case REQ_FIND_NEXT:
3779         case REQ_FIND_PREV:
3780                 find_next(view, request);
3781                 break;
3783         case REQ_STOP_LOADING:
3784                 foreach_view(view, i) {
3785                         if (view->pipe)
3786                                 report("Stopped loading the %s view", view->name),
3787                         end_update(view, TRUE);
3788                 }
3789                 break;
3791         case REQ_SHOW_VERSION:
3792                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3793                 return TRUE;
3795         case REQ_SCREEN_REDRAW:
3796                 redraw_display(TRUE);
3797                 break;
3799         case REQ_EDIT:
3800                 report("Nothing to edit");
3801                 break;
3803         case REQ_ENTER:
3804                 report("Nothing to enter");
3805                 break;
3807         case REQ_VIEW_CLOSE:
3808                 /* XXX: Mark closed views by letting view->prev point to the
3809                  * view itself. Parents to closed view should never be
3810                  * followed. */
3811                 if (view->prev && view->prev != view) {
3812                         maximize_view(view->prev);
3813                         view->prev = view;
3814                         break;
3815                 }
3816                 /* Fall-through */
3817         case REQ_QUIT:
3818                 return FALSE;
3820         default:
3821                 report("Unknown key, press %s for help",
3822                        get_key(view->keymap, REQ_VIEW_HELP));
3823                 return TRUE;
3824         }
3826         return TRUE;
3830 /*
3831  * View backend utilities
3832  */
3834 enum sort_field {
3835         ORDERBY_NAME,
3836         ORDERBY_DATE,
3837         ORDERBY_AUTHOR,
3838 };
3840 struct sort_state {
3841         const enum sort_field *fields;
3842         size_t size, current;
3843         bool reverse;
3844 };
3846 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3847 #define get_sort_field(state) ((state).fields[(state).current])
3848 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3850 static void
3851 sort_view(struct view *view, enum request request, struct sort_state *state,
3852           int (*compare)(const void *, const void *))
3854         switch (request) {
3855         case REQ_TOGGLE_SORT_FIELD:
3856                 state->current = (state->current + 1) % state->size;
3857                 break;
3859         case REQ_TOGGLE_SORT_ORDER:
3860                 state->reverse = !state->reverse;
3861                 break;
3862         default:
3863                 die("Not a sort request");
3864         }
3866         qsort(view->line, view->lines, sizeof(*view->line), compare);
3867         redraw_view(view);
3870 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3872 /* Small author cache to reduce memory consumption. It uses binary
3873  * search to lookup or find place to position new entries. No entries
3874  * are ever freed. */
3875 static const char *
3876 get_author(const char *name)
3878         static const char **authors;
3879         static size_t authors_size;
3880         int from = 0, to = authors_size - 1;
3882         while (from <= to) {
3883                 size_t pos = (to + from) / 2;
3884                 int cmp = strcmp(name, authors[pos]);
3886                 if (!cmp)
3887                         return authors[pos];
3889                 if (cmp < 0)
3890                         to = pos - 1;
3891                 else
3892                         from = pos + 1;
3893         }
3895         if (!realloc_authors(&authors, authors_size, 1))
3896                 return NULL;
3897         name = strdup(name);
3898         if (!name)
3899                 return NULL;
3901         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3902         authors[from] = name;
3903         authors_size++;
3905         return name;
3908 static void
3909 parse_timesec(struct time *time, const char *sec)
3911         time->sec = (time_t) atol(sec);
3914 static void
3915 parse_timezone(struct time *time, const char *zone)
3917         long tz;
3919         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3920         tz += ('0' - zone[2]) * 60 * 60;
3921         tz += ('0' - zone[3]) * 60 * 10;
3922         tz += ('0' - zone[4]) * 60;
3924         if (zone[0] == '-')
3925                 tz = -tz;
3927         time->tz = tz;
3928         time->sec -= tz;
3931 /* Parse author lines where the name may be empty:
3932  *      author  <email@address.tld> 1138474660 +0100
3933  */
3934 static void
3935 parse_author_line(char *ident, const char **author, struct time *time)
3937         char *nameend = strchr(ident, '<');
3938         char *emailend = strchr(ident, '>');
3940         if (nameend && emailend)
3941                 *nameend = *emailend = 0;
3942         ident = chomp_string(ident);
3943         if (!*ident) {
3944                 if (nameend)
3945                         ident = chomp_string(nameend + 1);
3946                 if (!*ident)
3947                         ident = "Unknown";
3948         }
3950         *author = get_author(ident);
3952         /* Parse epoch and timezone */
3953         if (emailend && emailend[1] == ' ') {
3954                 char *secs = emailend + 2;
3955                 char *zone = strchr(secs, ' ');
3957                 parse_timesec(time, secs);
3959                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3960                         parse_timezone(time, zone + 1);
3961         }
3964 static bool
3965 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3967         char rev[SIZEOF_REV];
3968         const char *revlist_argv[] = {
3969                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3970         };
3971         struct menu_item *items;
3972         char text[SIZEOF_STR];
3973         bool ok = TRUE;
3974         int i;
3976         items = calloc(*parents + 1, sizeof(*items));
3977         if (!items)
3978                 return FALSE;
3980         for (i = 0; i < *parents; i++) {
3981                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3982                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3983                     !(items[i].text = strdup(text))) {
3984                         ok = FALSE;
3985                         break;
3986                 }
3987         }
3989         if (ok) {
3990                 *parents = 0;
3991                 ok = prompt_menu("Select parent", items, parents);
3992         }
3993         for (i = 0; items[i].text; i++)
3994                 free((char *) items[i].text);
3995         free(items);
3996         return ok;
3999 static bool
4000 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4002         char buf[SIZEOF_STR * 4];
4003         const char *revlist_argv[] = {
4004                 "git", "log", "--no-color", "-1",
4005                         "--pretty=format:%P", id, "--", path, NULL
4006         };
4007         int parents;
4009         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4010             (parents = strlen(buf) / 40) < 0) {
4011                 report("Failed to get parent information");
4012                 return FALSE;
4014         } else if (parents == 0) {
4015                 if (path)
4016                         report("Path '%s' does not exist in the parent", path);
4017                 else
4018                         report("The selected commit has no parents");
4019                 return FALSE;
4020         }
4022         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
4023                 return FALSE;
4025         string_copy_rev(rev, &buf[41 * parents]);
4026         return TRUE;
4029 /*
4030  * Pager backend
4031  */
4033 static bool
4034 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4036         char text[SIZEOF_STR];
4038         if (opt_line_number && draw_lineno(view, lineno))
4039                 return TRUE;
4041         string_expand(text, sizeof(text), line->data, opt_tab_size);
4042         draw_text(view, line->type, text, TRUE);
4043         return TRUE;
4046 static bool
4047 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4049         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4050         char ref[SIZEOF_STR];
4052         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4053                 return TRUE;
4055         /* This is the only fatal call, since it can "corrupt" the buffer. */
4056         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4057                 return FALSE;
4059         return TRUE;
4062 static void
4063 add_pager_refs(struct view *view, struct line *line)
4065         char buf[SIZEOF_STR];
4066         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4067         struct ref_list *list;
4068         size_t bufpos = 0, i;
4069         const char *sep = "Refs: ";
4070         bool is_tag = FALSE;
4072         assert(line->type == LINE_COMMIT);
4074         list = get_ref_list(commit_id);
4075         if (!list) {
4076                 if (view->type == VIEW_DIFF)
4077                         goto try_add_describe_ref;
4078                 return;
4079         }
4081         for (i = 0; i < list->size; i++) {
4082                 struct ref *ref = list->refs[i];
4083                 const char *fmt = ref->tag    ? "%s[%s]" :
4084                                   ref->remote ? "%s<%s>" : "%s%s";
4086                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4087                         return;
4088                 sep = ", ";
4089                 if (ref->tag)
4090                         is_tag = TRUE;
4091         }
4093         if (!is_tag && view->type == VIEW_DIFF) {
4094 try_add_describe_ref:
4095                 /* Add <tag>-g<commit_id> "fake" reference. */
4096                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4097                         return;
4098         }
4100         if (bufpos == 0)
4101                 return;
4103         add_line_text(view, buf, LINE_PP_REFS);
4106 static bool
4107 pager_read(struct view *view, char *data)
4109         struct line *line;
4111         if (!data)
4112                 return TRUE;
4114         line = add_line_text(view, data, get_line_type(data));
4115         if (!line)
4116                 return FALSE;
4118         if (line->type == LINE_COMMIT &&
4119             (view->type == VIEW_DIFF ||
4120              view->type == VIEW_LOG))
4121                 add_pager_refs(view, line);
4123         return TRUE;
4126 static enum request
4127 pager_request(struct view *view, enum request request, struct line *line)
4129         int split = 0;
4131         if (request != REQ_ENTER)
4132                 return request;
4134         if (line->type == LINE_COMMIT &&
4135            (view->type == VIEW_LOG ||
4136             view->type == VIEW_PAGER)) {
4137                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4138                 split = 1;
4139         }
4141         /* Always scroll the view even if it was split. That way
4142          * you can use Enter to scroll through the log view and
4143          * split open each commit diff. */
4144         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4146         /* FIXME: A minor workaround. Scrolling the view will call report("")
4147          * but if we are scrolling a non-current view this won't properly
4148          * update the view title. */
4149         if (split)
4150                 update_view_title(view);
4152         return REQ_NONE;
4155 static bool
4156 pager_grep(struct view *view, struct line *line)
4158         const char *text[] = { line->data, NULL };
4160         return grep_text(view, text);
4163 static void
4164 pager_select(struct view *view, struct line *line)
4166         if (line->type == LINE_COMMIT) {
4167                 char *text = (char *)line->data + STRING_SIZE("commit ");
4169                 if (view->type != VIEW_PAGER)
4170                         string_copy_rev(view->ref, text);
4171                 string_copy_rev(ref_commit, text);
4172         }
4175 static struct view_ops pager_ops = {
4176         "line",
4177         NULL,
4178         NULL,
4179         pager_read,
4180         pager_draw,
4181         pager_request,
4182         pager_grep,
4183         pager_select,
4184 };
4186 static const char *log_argv[SIZEOF_ARG] = {
4187         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4188 };
4190 static enum request
4191 log_request(struct view *view, enum request request, struct line *line)
4193         switch (request) {
4194         case REQ_REFRESH:
4195                 load_refs();
4196                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4197                 return REQ_NONE;
4198         default:
4199                 return pager_request(view, request, line);
4200         }
4203 static struct view_ops log_ops = {
4204         "line",
4205         log_argv,
4206         NULL,
4207         pager_read,
4208         pager_draw,
4209         log_request,
4210         pager_grep,
4211         pager_select,
4212 };
4214 static const char *diff_argv[SIZEOF_ARG] = {
4215         "git", "show", "--pretty=fuller", "--no-color", "--root",
4216                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4217 };
4219 static struct view_ops diff_ops = {
4220         "line",
4221         diff_argv,
4222         NULL,
4223         pager_read,
4224         pager_draw,
4225         pager_request,
4226         pager_grep,
4227         pager_select,
4228 };
4230 /*
4231  * Help backend
4232  */
4234 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4236 static bool
4237 help_open_keymap_title(struct view *view, enum keymap keymap)
4239         struct line *line;
4241         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4242                                help_keymap_hidden[keymap] ? '+' : '-',
4243                                enum_name(keymap_table[keymap]));
4244         if (line)
4245                 line->other = keymap;
4247         return help_keymap_hidden[keymap];
4250 static void
4251 help_open_keymap(struct view *view, enum keymap keymap)
4253         const char *group = NULL;
4254         char buf[SIZEOF_STR];
4255         size_t bufpos;
4256         bool add_title = TRUE;
4257         int i;
4259         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4260                 const char *key = NULL;
4262                 if (req_info[i].request == REQ_NONE)
4263                         continue;
4265                 if (!req_info[i].request) {
4266                         group = req_info[i].help;
4267                         continue;
4268                 }
4270                 key = get_keys(keymap, req_info[i].request, TRUE);
4271                 if (!key || !*key)
4272                         continue;
4274                 if (add_title && help_open_keymap_title(view, keymap))
4275                         return;
4276                 add_title = FALSE;
4278                 if (group) {
4279                         add_line_text(view, group, LINE_HELP_GROUP);
4280                         group = NULL;
4281                 }
4283                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4284                                 enum_name(req_info[i]), req_info[i].help);
4285         }
4287         group = "External commands:";
4289         for (i = 0; i < run_requests; i++) {
4290                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4291                 const char *key;
4292                 int argc;
4294                 if (!req || req->keymap != keymap)
4295                         continue;
4297                 key = get_key_name(req->key);
4298                 if (!*key)
4299                         key = "(no key defined)";
4301                 if (add_title && help_open_keymap_title(view, keymap))
4302                         return;
4303                 if (group) {
4304                         add_line_text(view, group, LINE_HELP_GROUP);
4305                         group = NULL;
4306                 }
4308                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4309                         if (!string_format_from(buf, &bufpos, "%s%s",
4310                                                 argc ? " " : "", req->argv[argc]))
4311                                 return;
4313                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4314         }
4317 static bool
4318 help_open(struct view *view)
4320         enum keymap keymap;
4322         reset_view(view);
4323         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4324         add_line_text(view, "", LINE_DEFAULT);
4326         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4327                 help_open_keymap(view, keymap);
4329         return TRUE;
4332 static enum request
4333 help_request(struct view *view, enum request request, struct line *line)
4335         switch (request) {
4336         case REQ_ENTER:
4337                 if (line->type == LINE_HELP_KEYMAP) {
4338                         help_keymap_hidden[line->other] =
4339                                 !help_keymap_hidden[line->other];
4340                         view->p_restore = TRUE;
4341                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4342                 }
4344                 return REQ_NONE;
4345         default:
4346                 return pager_request(view, request, line);
4347         }
4350 static struct view_ops help_ops = {
4351         "line",
4352         NULL,
4353         help_open,
4354         NULL,
4355         pager_draw,
4356         help_request,
4357         pager_grep,
4358         pager_select,
4359 };
4362 /*
4363  * Tree backend
4364  */
4366 struct tree_stack_entry {
4367         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4368         unsigned long lineno;           /* Line number to restore */
4369         char *name;                     /* Position of name in opt_path */
4370 };
4372 /* The top of the path stack. */
4373 static struct tree_stack_entry *tree_stack = NULL;
4374 unsigned long tree_lineno = 0;
4376 static void
4377 pop_tree_stack_entry(void)
4379         struct tree_stack_entry *entry = tree_stack;
4381         tree_lineno = entry->lineno;
4382         entry->name[0] = 0;
4383         tree_stack = entry->prev;
4384         free(entry);
4387 static void
4388 push_tree_stack_entry(const char *name, unsigned long lineno)
4390         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4391         size_t pathlen = strlen(opt_path);
4393         if (!entry)
4394                 return;
4396         entry->prev = tree_stack;
4397         entry->name = opt_path + pathlen;
4398         tree_stack = entry;
4400         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4401                 pop_tree_stack_entry();
4402                 return;
4403         }
4405         /* Move the current line to the first tree entry. */
4406         tree_lineno = 1;
4407         entry->lineno = lineno;
4410 /* Parse output from git-ls-tree(1):
4411  *
4412  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4413  */
4415 #define SIZEOF_TREE_ATTR \
4416         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4418 #define SIZEOF_TREE_MODE \
4419         STRING_SIZE("100644 ")
4421 #define TREE_ID_OFFSET \
4422         STRING_SIZE("100644 blob ")
4424 struct tree_entry {
4425         char id[SIZEOF_REV];
4426         mode_t mode;
4427         struct time time;               /* Date from the author ident. */
4428         const char *author;             /* Author of the commit. */
4429         char name[1];
4430 };
4432 static const char *
4433 tree_path(const struct line *line)
4435         return ((struct tree_entry *) line->data)->name;
4438 static int
4439 tree_compare_entry(const struct line *line1, const struct line *line2)
4441         if (line1->type != line2->type)
4442                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4443         return strcmp(tree_path(line1), tree_path(line2));
4446 static const enum sort_field tree_sort_fields[] = {
4447         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4448 };
4449 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4451 static int
4452 tree_compare(const void *l1, const void *l2)
4454         const struct line *line1 = (const struct line *) l1;
4455         const struct line *line2 = (const struct line *) l2;
4456         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4457         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4459         if (line1->type == LINE_TREE_HEAD)
4460                 return -1;
4461         if (line2->type == LINE_TREE_HEAD)
4462                 return 1;
4464         switch (get_sort_field(tree_sort_state)) {
4465         case ORDERBY_DATE:
4466                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4468         case ORDERBY_AUTHOR:
4469                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4471         case ORDERBY_NAME:
4472         default:
4473                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4474         }
4478 static struct line *
4479 tree_entry(struct view *view, enum line_type type, const char *path,
4480            const char *mode, const char *id)
4482         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4483         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4485         if (!entry || !line) {
4486                 free(entry);
4487                 return NULL;
4488         }
4490         strncpy(entry->name, path, strlen(path));
4491         if (mode)
4492                 entry->mode = strtoul(mode, NULL, 8);
4493         if (id)
4494                 string_copy_rev(entry->id, id);
4496         return line;
4499 static bool
4500 tree_read_date(struct view *view, char *text, bool *read_date)
4502         static const char *author_name;
4503         static struct time author_time;
4505         if (!text && *read_date) {
4506                 *read_date = FALSE;
4507                 return TRUE;
4509         } else if (!text) {
4510                 char *path = *opt_path ? opt_path : ".";
4511                 /* Find next entry to process */
4512                 const char *log_file[] = {
4513                         "git", "log", "--no-color", "--pretty=raw",
4514                                 "--cc", "--raw", view->id, "--", path, NULL
4515                 };
4516                 struct io io = {};
4518                 if (!view->lines) {
4519                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4520                         report("Tree is empty");
4521                         return TRUE;
4522                 }
4524                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4525                         report("Failed to load tree data");
4526                         return TRUE;
4527                 }
4529                 io_done(view->pipe);
4530                 view->io = io;
4531                 *read_date = TRUE;
4532                 return FALSE;
4534         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4535                 parse_author_line(text + STRING_SIZE("author "),
4536                                   &author_name, &author_time);
4538         } else if (*text == ':') {
4539                 char *pos;
4540                 size_t annotated = 1;
4541                 size_t i;
4543                 pos = strchr(text, '\t');
4544                 if (!pos)
4545                         return TRUE;
4546                 text = pos + 1;
4547                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4548                         text += strlen(opt_path);
4549                 pos = strchr(text, '/');
4550                 if (pos)
4551                         *pos = 0;
4553                 for (i = 1; i < view->lines; i++) {
4554                         struct line *line = &view->line[i];
4555                         struct tree_entry *entry = line->data;
4557                         annotated += !!entry->author;
4558                         if (entry->author || strcmp(entry->name, text))
4559                                 continue;
4561                         entry->author = author_name;
4562                         entry->time = author_time;
4563                         line->dirty = 1;
4564                         break;
4565                 }
4567                 if (annotated == view->lines)
4568                         io_kill(view->pipe);
4569         }
4570         return TRUE;
4573 static bool
4574 tree_read(struct view *view, char *text)
4576         static bool read_date = FALSE;
4577         struct tree_entry *data;
4578         struct line *entry, *line;
4579         enum line_type type;
4580         size_t textlen = text ? strlen(text) : 0;
4581         char *path = text + SIZEOF_TREE_ATTR;
4583         if (read_date || !text)
4584                 return tree_read_date(view, text, &read_date);
4586         if (textlen <= SIZEOF_TREE_ATTR)
4587                 return FALSE;
4588         if (view->lines == 0 &&
4589             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4590                 return FALSE;
4592         /* Strip the path part ... */
4593         if (*opt_path) {
4594                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4595                 size_t striplen = strlen(opt_path);
4597                 if (pathlen > striplen)
4598                         memmove(path, path + striplen,
4599                                 pathlen - striplen + 1);
4601                 /* Insert "link" to parent directory. */
4602                 if (view->lines == 1 &&
4603                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4604                         return FALSE;
4605         }
4607         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4608         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4609         if (!entry)
4610                 return FALSE;
4611         data = entry->data;
4613         /* Skip "Directory ..." and ".." line. */
4614         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4615                 if (tree_compare_entry(line, entry) <= 0)
4616                         continue;
4618                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4620                 line->data = data;
4621                 line->type = type;
4622                 for (; line <= entry; line++)
4623                         line->dirty = line->cleareol = 1;
4624                 return TRUE;
4625         }
4627         if (tree_lineno > view->lineno) {
4628                 view->lineno = tree_lineno;
4629                 tree_lineno = 0;
4630         }
4632         return TRUE;
4635 static bool
4636 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4638         struct tree_entry *entry = line->data;
4640         if (line->type == LINE_TREE_HEAD) {
4641                 if (draw_text(view, line->type, "Directory path /", TRUE))
4642                         return TRUE;
4643         } else {
4644                 if (draw_mode(view, entry->mode))
4645                         return TRUE;
4647                 if (opt_author && draw_author(view, entry->author))
4648                         return TRUE;
4650                 if (opt_date && draw_date(view, &entry->time))
4651                         return TRUE;
4652         }
4653         if (draw_text(view, line->type, entry->name, TRUE))
4654                 return TRUE;
4655         return TRUE;
4658 static void
4659 open_blob_editor()
4661         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4662         int fd = mkstemp(file);
4664         if (fd == -1)
4665                 report("Failed to create temporary file");
4666         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4667                 report("Failed to save blob data to file");
4668         else
4669                 open_editor(file);
4670         if (fd != -1)
4671                 unlink(file);
4674 static enum request
4675 tree_request(struct view *view, enum request request, struct line *line)
4677         enum open_flags flags;
4679         switch (request) {
4680         case REQ_VIEW_BLAME:
4681                 if (line->type != LINE_TREE_FILE) {
4682                         report("Blame only supported for files");
4683                         return REQ_NONE;
4684                 }
4686                 string_copy(opt_ref, view->vid);
4687                 return request;
4689         case REQ_EDIT:
4690                 if (line->type != LINE_TREE_FILE) {
4691                         report("Edit only supported for files");
4692                 } else if (!is_head_commit(view->vid)) {
4693                         open_blob_editor();
4694                 } else {
4695                         open_editor(opt_file);
4696                 }
4697                 return REQ_NONE;
4699         case REQ_TOGGLE_SORT_FIELD:
4700         case REQ_TOGGLE_SORT_ORDER:
4701                 sort_view(view, request, &tree_sort_state, tree_compare);
4702                 return REQ_NONE;
4704         case REQ_PARENT:
4705                 if (!*opt_path) {
4706                         /* quit view if at top of tree */
4707                         return REQ_VIEW_CLOSE;
4708                 }
4709                 /* fake 'cd  ..' */
4710                 line = &view->line[1];
4711                 break;
4713         case REQ_ENTER:
4714                 break;
4716         default:
4717                 return request;
4718         }
4720         /* Cleanup the stack if the tree view is at a different tree. */
4721         while (!*opt_path && tree_stack)
4722                 pop_tree_stack_entry();
4724         switch (line->type) {
4725         case LINE_TREE_DIR:
4726                 /* Depending on whether it is a subdirectory or parent link
4727                  * mangle the path buffer. */
4728                 if (line == &view->line[1] && *opt_path) {
4729                         pop_tree_stack_entry();
4731                 } else {
4732                         const char *basename = tree_path(line);
4734                         push_tree_stack_entry(basename, view->lineno);
4735                 }
4737                 /* Trees and subtrees share the same ID, so they are not not
4738                  * unique like blobs. */
4739                 flags = OPEN_RELOAD;
4740                 request = REQ_VIEW_TREE;
4741                 break;
4743         case LINE_TREE_FILE:
4744                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4745                 request = REQ_VIEW_BLOB;
4746                 break;
4748         default:
4749                 return REQ_NONE;
4750         }
4752         open_view(view, request, flags);
4753         if (request == REQ_VIEW_TREE)
4754                 view->lineno = tree_lineno;
4756         return REQ_NONE;
4759 static bool
4760 tree_grep(struct view *view, struct line *line)
4762         struct tree_entry *entry = line->data;
4763         const char *text[] = {
4764                 entry->name,
4765                 opt_author ? entry->author : "",
4766                 mkdate(&entry->time, opt_date),
4767                 NULL
4768         };
4770         return grep_text(view, text);
4773 static void
4774 tree_select(struct view *view, struct line *line)
4776         struct tree_entry *entry = line->data;
4778         if (line->type == LINE_TREE_FILE) {
4779                 string_copy_rev(ref_blob, entry->id);
4780                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4782         } else if (line->type != LINE_TREE_DIR) {
4783                 return;
4784         }
4786         string_copy_rev(view->ref, entry->id);
4789 static bool
4790 tree_prepare(struct view *view)
4792         if (view->lines == 0 && opt_prefix[0]) {
4793                 char *pos = opt_prefix;
4795                 while (pos && *pos) {
4796                         char *end = strchr(pos, '/');
4798                         if (end)
4799                                 *end = 0;
4800                         push_tree_stack_entry(pos, 0);
4801                         pos = end;
4802                         if (end) {
4803                                 *end = '/';
4804                                 pos++;
4805                         }
4806                 }
4808         } else if (strcmp(view->vid, view->id)) {
4809                 opt_path[0] = 0;
4810         }
4812         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4815 static const char *tree_argv[SIZEOF_ARG] = {
4816         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4817 };
4819 static struct view_ops tree_ops = {
4820         "file",
4821         tree_argv,
4822         NULL,
4823         tree_read,
4824         tree_draw,
4825         tree_request,
4826         tree_grep,
4827         tree_select,
4828         tree_prepare,
4829 };
4831 static bool
4832 blob_read(struct view *view, char *line)
4834         if (!line)
4835                 return TRUE;
4836         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4839 static enum request
4840 blob_request(struct view *view, enum request request, struct line *line)
4842         switch (request) {
4843         case REQ_EDIT:
4844                 open_blob_editor();
4845                 return REQ_NONE;
4846         default:
4847                 return pager_request(view, request, line);
4848         }
4851 static const char *blob_argv[SIZEOF_ARG] = {
4852         "git", "cat-file", "blob", "%(blob)", NULL
4853 };
4855 static struct view_ops blob_ops = {
4856         "line",
4857         blob_argv,
4858         NULL,
4859         blob_read,
4860         pager_draw,
4861         blob_request,
4862         pager_grep,
4863         pager_select,
4864 };
4866 /*
4867  * Blame backend
4868  *
4869  * Loading the blame view is a two phase job:
4870  *
4871  *  1. File content is read either using opt_file from the
4872  *     filesystem or using git-cat-file.
4873  *  2. Then blame information is incrementally added by
4874  *     reading output from git-blame.
4875  */
4877 static const char *blame_head_argv[] = {
4878         "git", "blame", "--incremental", "--", "%(file)", NULL
4879 };
4881 static const char *blame_ref_argv[] = {
4882         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4883 };
4885 static const char *blame_cat_file_argv[] = {
4886         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4887 };
4889 struct blame_commit {
4890         char id[SIZEOF_REV];            /* SHA1 ID. */
4891         char title[128];                /* First line of the commit message. */
4892         const char *author;             /* Author of the commit. */
4893         struct time time;               /* Date from the author ident. */
4894         char filename[128];             /* Name of file. */
4895         bool has_previous;              /* Was a "previous" line detected. */
4896 };
4898 struct blame {
4899         struct blame_commit *commit;
4900         unsigned long lineno;
4901         char text[1];
4902 };
4904 static bool
4905 blame_open(struct view *view)
4907         char path[SIZEOF_STR];
4909         if (!view->prev && *opt_prefix) {
4910                 string_copy(path, opt_file);
4911                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4912                         return FALSE;
4913         }
4915         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4916                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4917                         return FALSE;
4918         }
4920         setup_update(view, opt_file);
4921         string_format(view->ref, "%s ...", opt_file);
4923         return TRUE;
4926 static struct blame_commit *
4927 get_blame_commit(struct view *view, const char *id)
4929         size_t i;
4931         for (i = 0; i < view->lines; i++) {
4932                 struct blame *blame = view->line[i].data;
4934                 if (!blame->commit)
4935                         continue;
4937                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4938                         return blame->commit;
4939         }
4941         {
4942                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4944                 if (commit)
4945                         string_ncopy(commit->id, id, SIZEOF_REV);
4946                 return commit;
4947         }
4950 static bool
4951 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4953         const char *pos = *posref;
4955         *posref = NULL;
4956         pos = strchr(pos + 1, ' ');
4957         if (!pos || !isdigit(pos[1]))
4958                 return FALSE;
4959         *number = atoi(pos + 1);
4960         if (*number < min || *number > max)
4961                 return FALSE;
4963         *posref = pos;
4964         return TRUE;
4967 static struct blame_commit *
4968 parse_blame_commit(struct view *view, const char *text, int *blamed)
4970         struct blame_commit *commit;
4971         struct blame *blame;
4972         const char *pos = text + SIZEOF_REV - 2;
4973         size_t orig_lineno = 0;
4974         size_t lineno;
4975         size_t group;
4977         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4978                 return NULL;
4980         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4981             !parse_number(&pos, &lineno, 1, view->lines) ||
4982             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4983                 return NULL;
4985         commit = get_blame_commit(view, text);
4986         if (!commit)
4987                 return NULL;
4989         *blamed += group;
4990         while (group--) {
4991                 struct line *line = &view->line[lineno + group - 1];
4993                 blame = line->data;
4994                 blame->commit = commit;
4995                 blame->lineno = orig_lineno + group - 1;
4996                 line->dirty = 1;
4997         }
4999         return commit;
5002 static bool
5003 blame_read_file(struct view *view, const char *line, bool *read_file)
5005         if (!line) {
5006                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
5007                 struct io io = {};
5009                 if (view->lines == 0 && !view->prev)
5010                         die("No blame exist for %s", view->vid);
5012                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
5013                         report("Failed to load blame data");
5014                         return TRUE;
5015                 }
5017                 io_done(view->pipe);
5018                 view->io = io;
5019                 *read_file = FALSE;
5020                 return FALSE;
5022         } else {
5023                 size_t linelen = strlen(line);
5024                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5026                 if (!blame)
5027                         return FALSE;
5029                 blame->commit = NULL;
5030                 strncpy(blame->text, line, linelen);
5031                 blame->text[linelen] = 0;
5032                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5033         }
5036 static bool
5037 match_blame_header(const char *name, char **line)
5039         size_t namelen = strlen(name);
5040         bool matched = !strncmp(name, *line, namelen);
5042         if (matched)
5043                 *line += namelen;
5045         return matched;
5048 static bool
5049 blame_read(struct view *view, char *line)
5051         static struct blame_commit *commit = NULL;
5052         static int blamed = 0;
5053         static bool read_file = TRUE;
5055         if (read_file)
5056                 return blame_read_file(view, line, &read_file);
5058         if (!line) {
5059                 /* Reset all! */
5060                 commit = NULL;
5061                 blamed = 0;
5062                 read_file = TRUE;
5063                 string_format(view->ref, "%s", view->vid);
5064                 if (view_is_displayed(view)) {
5065                         update_view_title(view);
5066                         redraw_view_from(view, 0);
5067                 }
5068                 return TRUE;
5069         }
5071         if (!commit) {
5072                 commit = parse_blame_commit(view, line, &blamed);
5073                 string_format(view->ref, "%s %2d%%", view->vid,
5074                               view->lines ? blamed * 100 / view->lines : 0);
5076         } else if (match_blame_header("author ", &line)) {
5077                 commit->author = get_author(line);
5079         } else if (match_blame_header("author-time ", &line)) {
5080                 parse_timesec(&commit->time, line);
5082         } else if (match_blame_header("author-tz ", &line)) {
5083                 parse_timezone(&commit->time, line);
5085         } else if (match_blame_header("summary ", &line)) {
5086                 string_ncopy(commit->title, line, strlen(line));
5088         } else if (match_blame_header("previous ", &line)) {
5089                 commit->has_previous = TRUE;
5091         } else if (match_blame_header("filename ", &line)) {
5092                 string_ncopy(commit->filename, line, strlen(line));
5093                 commit = NULL;
5094         }
5096         return TRUE;
5099 static bool
5100 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5102         struct blame *blame = line->data;
5103         struct time *time = NULL;
5104         const char *id = NULL, *author = NULL;
5105         char text[SIZEOF_STR];
5107         if (blame->commit && *blame->commit->filename) {
5108                 id = blame->commit->id;
5109                 author = blame->commit->author;
5110                 time = &blame->commit->time;
5111         }
5113         if (opt_date && draw_date(view, time))
5114                 return TRUE;
5116         if (opt_author && draw_author(view, author))
5117                 return TRUE;
5119         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5120                 return TRUE;
5122         if (draw_lineno(view, lineno))
5123                 return TRUE;
5125         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5126         draw_text(view, LINE_DEFAULT, text, TRUE);
5127         return TRUE;
5130 static bool
5131 check_blame_commit(struct blame *blame, bool check_null_id)
5133         if (!blame->commit)
5134                 report("Commit data not loaded yet");
5135         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5136                 report("No commit exist for the selected line");
5137         else
5138                 return TRUE;
5139         return FALSE;
5142 static void
5143 setup_blame_parent_line(struct view *view, struct blame *blame)
5145         const char *diff_tree_argv[] = {
5146                 "git", "diff-tree", "-U0", blame->commit->id,
5147                         "--", blame->commit->filename, NULL
5148         };
5149         struct io io = {};
5150         int parent_lineno = -1;
5151         int blamed_lineno = -1;
5152         char *line;
5154         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5155                 return;
5157         while ((line = io_get(&io, '\n', TRUE))) {
5158                 if (*line == '@') {
5159                         char *pos = strchr(line, '+');
5161                         parent_lineno = atoi(line + 4);
5162                         if (pos)
5163                                 blamed_lineno = atoi(pos + 1);
5165                 } else if (*line == '+' && parent_lineno != -1) {
5166                         if (blame->lineno == blamed_lineno - 1 &&
5167                             !strcmp(blame->text, line + 1)) {
5168                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5169                                 break;
5170                         }
5171                         blamed_lineno++;
5172                 }
5173         }
5175         io_done(&io);
5178 static enum request
5179 blame_request(struct view *view, enum request request, struct line *line)
5181         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5182         struct blame *blame = line->data;
5184         switch (request) {
5185         case REQ_VIEW_BLAME:
5186                 if (check_blame_commit(blame, TRUE)) {
5187                         string_copy(opt_ref, blame->commit->id);
5188                         string_copy(opt_file, blame->commit->filename);
5189                         if (blame->lineno)
5190                                 view->lineno = blame->lineno;
5191                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5192                 }
5193                 break;
5195         case REQ_PARENT:
5196                 if (check_blame_commit(blame, TRUE) &&
5197                     select_commit_parent(blame->commit->id, opt_ref,
5198                                          blame->commit->filename)) {
5199                         string_copy(opt_file, blame->commit->filename);
5200                         setup_blame_parent_line(view, blame);
5201                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5202                 }
5203                 break;
5205         case REQ_ENTER:
5206                 if (!check_blame_commit(blame, FALSE))
5207                         break;
5209                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5210                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5211                         break;
5213                 if (!strcmp(blame->commit->id, NULL_ID)) {
5214                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5215                         const char *diff_index_argv[] = {
5216                                 "git", "diff-index", "--root", "--patch-with-stat",
5217                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5218                         };
5220                         if (!blame->commit->has_previous) {
5221                                 diff_index_argv[1] = "diff";
5222                                 diff_index_argv[2] = "--no-color";
5223                                 diff_index_argv[6] = "--";
5224                                 diff_index_argv[7] = "/dev/null";
5225                         }
5227                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5228                                 report("Failed to allocate diff command");
5229                                 break;
5230                         }
5231                         flags |= OPEN_PREPARED;
5232                 }
5234                 open_view(view, REQ_VIEW_DIFF, flags);
5235                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5236                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5237                 break;
5239         default:
5240                 return request;
5241         }
5243         return REQ_NONE;
5246 static bool
5247 blame_grep(struct view *view, struct line *line)
5249         struct blame *blame = line->data;
5250         struct blame_commit *commit = blame->commit;
5251         const char *text[] = {
5252                 blame->text,
5253                 commit ? commit->title : "",
5254                 commit ? commit->id : "",
5255                 commit && opt_author ? commit->author : "",
5256                 commit ? mkdate(&commit->time, opt_date) : "",
5257                 NULL
5258         };
5260         return grep_text(view, text);
5263 static void
5264 blame_select(struct view *view, struct line *line)
5266         struct blame *blame = line->data;
5267         struct blame_commit *commit = blame->commit;
5269         if (!commit)
5270                 return;
5272         if (!strcmp(commit->id, NULL_ID))
5273                 string_ncopy(ref_commit, "HEAD", 4);
5274         else
5275                 string_copy_rev(ref_commit, commit->id);
5278 static struct view_ops blame_ops = {
5279         "line",
5280         NULL,
5281         blame_open,
5282         blame_read,
5283         blame_draw,
5284         blame_request,
5285         blame_grep,
5286         blame_select,
5287 };
5289 /*
5290  * Branch backend
5291  */
5293 struct branch {
5294         const char *author;             /* Author of the last commit. */
5295         struct time time;               /* Date of the last activity. */
5296         const struct ref *ref;          /* Name and commit ID information. */
5297 };
5299 static const struct ref branch_all;
5301 static const enum sort_field branch_sort_fields[] = {
5302         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5303 };
5304 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5306 static int
5307 branch_compare(const void *l1, const void *l2)
5309         const struct branch *branch1 = ((const struct line *) l1)->data;
5310         const struct branch *branch2 = ((const struct line *) l2)->data;
5312         switch (get_sort_field(branch_sort_state)) {
5313         case ORDERBY_DATE:
5314                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5316         case ORDERBY_AUTHOR:
5317                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5319         case ORDERBY_NAME:
5320         default:
5321                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5322         }
5325 static bool
5326 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5328         struct branch *branch = line->data;
5329         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5331         if (opt_date && draw_date(view, &branch->time))
5332                 return TRUE;
5334         if (opt_author && draw_author(view, branch->author))
5335                 return TRUE;
5337         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5338         return TRUE;
5341 static enum request
5342 branch_request(struct view *view, enum request request, struct line *line)
5344         struct branch *branch = line->data;
5346         switch (request) {
5347         case REQ_REFRESH:
5348                 load_refs();
5349                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5350                 return REQ_NONE;
5352         case REQ_TOGGLE_SORT_FIELD:
5353         case REQ_TOGGLE_SORT_ORDER:
5354                 sort_view(view, request, &branch_sort_state, branch_compare);
5355                 return REQ_NONE;
5357         case REQ_ENTER:
5358                 if (branch->ref == &branch_all) {
5359                         const char *all_branches_argv[] = {
5360                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5361                                       "--topo-order", "--all", NULL
5362                         };
5363                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5365                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5366                                 report("Failed to load view of all branches");
5367                                 return REQ_NONE;
5368                         }
5369                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5370                 } else {
5371                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5372                 }
5373                 return REQ_NONE;
5375         default:
5376                 return request;
5377         }
5380 static bool
5381 branch_read(struct view *view, char *line)
5383         static char id[SIZEOF_REV];
5384         struct branch *reference;
5385         size_t i;
5387         if (!line)
5388                 return TRUE;
5390         switch (get_line_type(line)) {
5391         case LINE_COMMIT:
5392                 string_copy_rev(id, line + STRING_SIZE("commit "));
5393                 return TRUE;
5395         case LINE_AUTHOR:
5396                 for (i = 0, reference = NULL; i < view->lines; i++) {
5397                         struct branch *branch = view->line[i].data;
5399                         if (strcmp(branch->ref->id, id))
5400                                 continue;
5402                         view->line[i].dirty = TRUE;
5403                         if (reference) {
5404                                 branch->author = reference->author;
5405                                 branch->time = reference->time;
5406                                 continue;
5407                         }
5409                         parse_author_line(line + STRING_SIZE("author "),
5410                                           &branch->author, &branch->time);
5411                         reference = branch;
5412                 }
5413                 return TRUE;
5415         default:
5416                 return TRUE;
5417         }
5421 static bool
5422 branch_open_visitor(void *data, const struct ref *ref)
5424         struct view *view = data;
5425         struct branch *branch;
5427         if (ref->tag || ref->ltag || ref->remote)
5428                 return TRUE;
5430         branch = calloc(1, sizeof(*branch));
5431         if (!branch)
5432                 return FALSE;
5434         branch->ref = ref;
5435         return !!add_line_data(view, branch, LINE_DEFAULT);
5438 static bool
5439 branch_open(struct view *view)
5441         const char *branch_log[] = {
5442                 "git", "log", "--no-color", "--pretty=raw",
5443                         "--simplify-by-decoration", "--all", NULL
5444         };
5446         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5447                 report("Failed to load branch data");
5448                 return TRUE;
5449         }
5451         setup_update(view, view->id);
5452         branch_open_visitor(view, &branch_all);
5453         foreach_ref(branch_open_visitor, view);
5454         view->p_restore = TRUE;
5456         return TRUE;
5459 static bool
5460 branch_grep(struct view *view, struct line *line)
5462         struct branch *branch = line->data;
5463         const char *text[] = {
5464                 branch->ref->name,
5465                 branch->author,
5466                 NULL
5467         };
5469         return grep_text(view, text);
5472 static void
5473 branch_select(struct view *view, struct line *line)
5475         struct branch *branch = line->data;
5477         string_copy_rev(view->ref, branch->ref->id);
5478         string_copy_rev(ref_commit, branch->ref->id);
5479         string_copy_rev(ref_head, branch->ref->id);
5480         string_copy_rev(ref_branch, branch->ref->name);
5483 static struct view_ops branch_ops = {
5484         "branch",
5485         NULL,
5486         branch_open,
5487         branch_read,
5488         branch_draw,
5489         branch_request,
5490         branch_grep,
5491         branch_select,
5492 };
5494 /*
5495  * Status backend
5496  */
5498 struct status {
5499         char status;
5500         struct {
5501                 mode_t mode;
5502                 char rev[SIZEOF_REV];
5503                 char name[SIZEOF_STR];
5504         } old;
5505         struct {
5506                 mode_t mode;
5507                 char rev[SIZEOF_REV];
5508                 char name[SIZEOF_STR];
5509         } new;
5510 };
5512 static char status_onbranch[SIZEOF_STR];
5513 static struct status stage_status;
5514 static enum line_type stage_line_type;
5515 static size_t stage_chunks;
5516 static int *stage_chunk;
5518 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5520 /* This should work even for the "On branch" line. */
5521 static inline bool
5522 status_has_none(struct view *view, struct line *line)
5524         return line < view->line + view->lines && !line[1].data;
5527 /* Get fields from the diff line:
5528  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5529  */
5530 static inline bool
5531 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5533         const char *old_mode = buf +  1;
5534         const char *new_mode = buf +  8;
5535         const char *old_rev  = buf + 15;
5536         const char *new_rev  = buf + 56;
5537         const char *status   = buf + 97;
5539         if (bufsize < 98 ||
5540             old_mode[-1] != ':' ||
5541             new_mode[-1] != ' ' ||
5542             old_rev[-1]  != ' ' ||
5543             new_rev[-1]  != ' ' ||
5544             status[-1]   != ' ')
5545                 return FALSE;
5547         file->status = *status;
5549         string_copy_rev(file->old.rev, old_rev);
5550         string_copy_rev(file->new.rev, new_rev);
5552         file->old.mode = strtoul(old_mode, NULL, 8);
5553         file->new.mode = strtoul(new_mode, NULL, 8);
5555         file->old.name[0] = file->new.name[0] = 0;
5557         return TRUE;
5560 static bool
5561 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5563         struct status *unmerged = NULL;
5564         char *buf;
5565         struct io io = {};
5567         if (!io_run(&io, argv, opt_cdup, IO_RD))
5568                 return FALSE;
5570         add_line_data(view, NULL, type);
5572         while ((buf = io_get(&io, 0, TRUE))) {
5573                 struct status *file = unmerged;
5575                 if (!file) {
5576                         file = calloc(1, sizeof(*file));
5577                         if (!file || !add_line_data(view, file, type))
5578                                 goto error_out;
5579                 }
5581                 /* Parse diff info part. */
5582                 if (status) {
5583                         file->status = status;
5584                         if (status == 'A')
5585                                 string_copy(file->old.rev, NULL_ID);
5587                 } else if (!file->status || file == unmerged) {
5588                         if (!status_get_diff(file, buf, strlen(buf)))
5589                                 goto error_out;
5591                         buf = io_get(&io, 0, TRUE);
5592                         if (!buf)
5593                                 break;
5595                         /* Collapse all modified entries that follow an
5596                          * associated unmerged entry. */
5597                         if (unmerged == file) {
5598                                 unmerged->status = 'U';
5599                                 unmerged = NULL;
5600                         } else if (file->status == 'U') {
5601                                 unmerged = file;
5602                         }
5603                 }
5605                 /* Grab the old name for rename/copy. */
5606                 if (!*file->old.name &&
5607                     (file->status == 'R' || file->status == 'C')) {
5608                         string_ncopy(file->old.name, buf, strlen(buf));
5610                         buf = io_get(&io, 0, TRUE);
5611                         if (!buf)
5612                                 break;
5613                 }
5615                 /* git-ls-files just delivers a NUL separated list of
5616                  * file names similar to the second half of the
5617                  * git-diff-* output. */
5618                 string_ncopy(file->new.name, buf, strlen(buf));
5619                 if (!*file->old.name)
5620                         string_copy(file->old.name, file->new.name);
5621                 file = NULL;
5622         }
5624         if (io_error(&io)) {
5625 error_out:
5626                 io_done(&io);
5627                 return FALSE;
5628         }
5630         if (!view->line[view->lines - 1].data)
5631                 add_line_data(view, NULL, LINE_STAT_NONE);
5633         io_done(&io);
5634         return TRUE;
5637 /* Don't show unmerged entries in the staged section. */
5638 static const char *status_diff_index_argv[] = {
5639         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5640                              "--cached", "-M", "HEAD", NULL
5641 };
5643 static const char *status_diff_files_argv[] = {
5644         "git", "diff-files", "-z", NULL
5645 };
5647 static const char *status_list_other_argv[] = {
5648         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5649 };
5651 static const char *status_list_no_head_argv[] = {
5652         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5653 };
5655 static const char *update_index_argv[] = {
5656         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5657 };
5659 /* Restore the previous line number to stay in the context or select a
5660  * line with something that can be updated. */
5661 static void
5662 status_restore(struct view *view)
5664         if (view->p_lineno >= view->lines)
5665                 view->p_lineno = view->lines - 1;
5666         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5667                 view->p_lineno++;
5668         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5669                 view->p_lineno--;
5671         /* If the above fails, always skip the "On branch" line. */
5672         if (view->p_lineno < view->lines)
5673                 view->lineno = view->p_lineno;
5674         else
5675                 view->lineno = 1;
5677         if (view->lineno < view->offset)
5678                 view->offset = view->lineno;
5679         else if (view->offset + view->height <= view->lineno)
5680                 view->offset = view->lineno - view->height + 1;
5682         view->p_restore = FALSE;
5685 static void
5686 status_update_onbranch(void)
5688         static const char *paths[][2] = {
5689                 { "rebase-apply/rebasing",      "Rebasing" },
5690                 { "rebase-apply/applying",      "Applying mailbox" },
5691                 { "rebase-apply/",              "Rebasing mailbox" },
5692                 { "rebase-merge/interactive",   "Interactive rebase" },
5693                 { "rebase-merge/",              "Rebase merge" },
5694                 { "MERGE_HEAD",                 "Merging" },
5695                 { "BISECT_LOG",                 "Bisecting" },
5696                 { "HEAD",                       "On branch" },
5697         };
5698         char buf[SIZEOF_STR];
5699         struct stat stat;
5700         int i;
5702         if (is_initial_commit()) {
5703                 string_copy(status_onbranch, "Initial commit");
5704                 return;
5705         }
5707         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5708                 char *head = opt_head;
5710                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5711                     lstat(buf, &stat) < 0)
5712                         continue;
5714                 if (!*opt_head) {
5715                         struct io io = {};
5717                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5718                             io_read_buf(&io, buf, sizeof(buf))) {
5719                                 head = buf;
5720                                 if (!prefixcmp(head, "refs/heads/"))
5721                                         head += STRING_SIZE("refs/heads/");
5722                         }
5723                 }
5725                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5726                         string_copy(status_onbranch, opt_head);
5727                 return;
5728         }
5730         string_copy(status_onbranch, "Not currently on any branch");
5733 /* First parse staged info using git-diff-index(1), then parse unstaged
5734  * info using git-diff-files(1), and finally untracked files using
5735  * git-ls-files(1). */
5736 static bool
5737 status_open(struct view *view)
5739         reset_view(view);
5741         add_line_data(view, NULL, LINE_STAT_HEAD);
5742         status_update_onbranch();
5744         io_run_bg(update_index_argv);
5746         if (is_initial_commit()) {
5747                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5748                         return FALSE;
5749         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5750                 return FALSE;
5751         }
5753         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5754             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5755                 return FALSE;
5757         /* Restore the exact position or use the specialized restore
5758          * mode? */
5759         if (!view->p_restore)
5760                 status_restore(view);
5761         return TRUE;
5764 static bool
5765 status_draw(struct view *view, struct line *line, unsigned int lineno)
5767         struct status *status = line->data;
5768         enum line_type type;
5769         const char *text;
5771         if (!status) {
5772                 switch (line->type) {
5773                 case LINE_STAT_STAGED:
5774                         type = LINE_STAT_SECTION;
5775                         text = "Changes to be committed:";
5776                         break;
5778                 case LINE_STAT_UNSTAGED:
5779                         type = LINE_STAT_SECTION;
5780                         text = "Changed but not updated:";
5781                         break;
5783                 case LINE_STAT_UNTRACKED:
5784                         type = LINE_STAT_SECTION;
5785                         text = "Untracked files:";
5786                         break;
5788                 case LINE_STAT_NONE:
5789                         type = LINE_DEFAULT;
5790                         text = "  (no files)";
5791                         break;
5793                 case LINE_STAT_HEAD:
5794                         type = LINE_STAT_HEAD;
5795                         text = status_onbranch;
5796                         break;
5798                 default:
5799                         return FALSE;
5800                 }
5801         } else {
5802                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5804                 buf[0] = status->status;
5805                 if (draw_text(view, line->type, buf, TRUE))
5806                         return TRUE;
5807                 type = LINE_DEFAULT;
5808                 text = status->new.name;
5809         }
5811         draw_text(view, type, text, TRUE);
5812         return TRUE;
5815 static enum request
5816 status_load_error(struct view *view, struct view *stage, const char *path)
5818         if (displayed_views() == 2 || display[current_view] != view)
5819                 maximize_view(view);
5820         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5821         return REQ_NONE;
5824 static enum request
5825 status_enter(struct view *view, struct line *line)
5827         struct status *status = line->data;
5828         const char *oldpath = status ? status->old.name : NULL;
5829         /* Diffs for unmerged entries are empty when passing the new
5830          * path, so leave it empty. */
5831         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5832         const char *info;
5833         enum open_flags split;
5834         struct view *stage = VIEW(REQ_VIEW_STAGE);
5836         if (line->type == LINE_STAT_NONE ||
5837             (!status && line[1].type == LINE_STAT_NONE)) {
5838                 report("No file to diff");
5839                 return REQ_NONE;
5840         }
5842         switch (line->type) {
5843         case LINE_STAT_STAGED:
5844                 if (is_initial_commit()) {
5845                         const char *no_head_diff_argv[] = {
5846                                 "git", "diff", "--no-color", "--patch-with-stat",
5847                                         "--", "/dev/null", newpath, NULL
5848                         };
5850                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5851                                 return status_load_error(view, stage, newpath);
5852                 } else {
5853                         const char *index_show_argv[] = {
5854                                 "git", "diff-index", "--root", "--patch-with-stat",
5855                                         "-C", "-M", "--cached", "HEAD", "--",
5856                                         oldpath, newpath, NULL
5857                         };
5859                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5860                                 return status_load_error(view, stage, newpath);
5861                 }
5863                 if (status)
5864                         info = "Staged changes to %s";
5865                 else
5866                         info = "Staged changes";
5867                 break;
5869         case LINE_STAT_UNSTAGED:
5870         {
5871                 const char *files_show_argv[] = {
5872                         "git", "diff-files", "--root", "--patch-with-stat",
5873                                 "-C", "-M", "--", oldpath, newpath, NULL
5874                 };
5876                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5877                         return status_load_error(view, stage, newpath);
5878                 if (status)
5879                         info = "Unstaged changes to %s";
5880                 else
5881                         info = "Unstaged changes";
5882                 break;
5883         }
5884         case LINE_STAT_UNTRACKED:
5885                 if (!newpath) {
5886                         report("No file to show");
5887                         return REQ_NONE;
5888                 }
5890                 if (!suffixcmp(status->new.name, -1, "/")) {
5891                         report("Cannot display a directory");
5892                         return REQ_NONE;
5893                 }
5895                 if (!prepare_update_file(stage, newpath))
5896                         return status_load_error(view, stage, newpath);
5897                 info = "Untracked file %s";
5898                 break;
5900         case LINE_STAT_HEAD:
5901                 return REQ_NONE;
5903         default:
5904                 die("line type %d not handled in switch", line->type);
5905         }
5907         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5908         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5909         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5910                 if (status) {
5911                         stage_status = *status;
5912                 } else {
5913                         memset(&stage_status, 0, sizeof(stage_status));
5914                 }
5916                 stage_line_type = line->type;
5917                 stage_chunks = 0;
5918                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5919         }
5921         return REQ_NONE;
5924 static bool
5925 status_exists(struct status *status, enum line_type type)
5927         struct view *view = VIEW(REQ_VIEW_STATUS);
5928         unsigned long lineno;
5930         for (lineno = 0; lineno < view->lines; lineno++) {
5931                 struct line *line = &view->line[lineno];
5932                 struct status *pos = line->data;
5934                 if (line->type != type)
5935                         continue;
5936                 if (!pos && (!status || !status->status) && line[1].data) {
5937                         select_view_line(view, lineno);
5938                         return TRUE;
5939                 }
5940                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5941                         select_view_line(view, lineno);
5942                         return TRUE;
5943                 }
5944         }
5946         return FALSE;
5950 static bool
5951 status_update_prepare(struct io *io, enum line_type type)
5953         const char *staged_argv[] = {
5954                 "git", "update-index", "-z", "--index-info", NULL
5955         };
5956         const char *others_argv[] = {
5957                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5958         };
5960         switch (type) {
5961         case LINE_STAT_STAGED:
5962                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5964         case LINE_STAT_UNSTAGED:
5965         case LINE_STAT_UNTRACKED:
5966                 return io_run(io, others_argv, opt_cdup, IO_WR);
5968         default:
5969                 die("line type %d not handled in switch", type);
5970                 return FALSE;
5971         }
5974 static bool
5975 status_update_write(struct io *io, struct status *status, enum line_type type)
5977         char buf[SIZEOF_STR];
5978         size_t bufsize = 0;
5980         switch (type) {
5981         case LINE_STAT_STAGED:
5982                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5983                                         status->old.mode,
5984                                         status->old.rev,
5985                                         status->old.name, 0))
5986                         return FALSE;
5987                 break;
5989         case LINE_STAT_UNSTAGED:
5990         case LINE_STAT_UNTRACKED:
5991                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5992                         return FALSE;
5993                 break;
5995         default:
5996                 die("line type %d not handled in switch", type);
5997         }
5999         return io_write(io, buf, bufsize);
6002 static bool
6003 status_update_file(struct status *status, enum line_type type)
6005         struct io io = {};
6006         bool result;
6008         if (!status_update_prepare(&io, type))
6009                 return FALSE;
6011         result = status_update_write(&io, status, type);
6012         return io_done(&io) && result;
6015 static bool
6016 status_update_files(struct view *view, struct line *line)
6018         char buf[sizeof(view->ref)];
6019         struct io io = {};
6020         bool result = TRUE;
6021         struct line *pos = view->line + view->lines;
6022         int files = 0;
6023         int file, done;
6024         int cursor_y = -1, cursor_x = -1;
6026         if (!status_update_prepare(&io, line->type))
6027                 return FALSE;
6029         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6030                 files++;
6032         string_copy(buf, view->ref);
6033         getsyx(cursor_y, cursor_x);
6034         for (file = 0, done = 5; result && file < files; line++, file++) {
6035                 int almost_done = file * 100 / files;
6037                 if (almost_done > done) {
6038                         done = almost_done;
6039                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6040                                       file, files, done);
6041                         update_view_title(view);
6042                         setsyx(cursor_y, cursor_x);
6043                         doupdate();
6044                 }
6045                 result = status_update_write(&io, line->data, line->type);
6046         }
6047         string_copy(view->ref, buf);
6049         return io_done(&io) && result;
6052 static bool
6053 status_update(struct view *view)
6055         struct line *line = &view->line[view->lineno];
6057         assert(view->lines);
6059         if (!line->data) {
6060                 /* This should work even for the "On branch" line. */
6061                 if (line < view->line + view->lines && !line[1].data) {
6062                         report("Nothing to update");
6063                         return FALSE;
6064                 }
6066                 if (!status_update_files(view, line + 1)) {
6067                         report("Failed to update file status");
6068                         return FALSE;
6069                 }
6071         } else if (!status_update_file(line->data, line->type)) {
6072                 report("Failed to update file status");
6073                 return FALSE;
6074         }
6076         return TRUE;
6079 static bool
6080 status_revert(struct status *status, enum line_type type, bool has_none)
6082         if (!status || type != LINE_STAT_UNSTAGED) {
6083                 if (type == LINE_STAT_STAGED) {
6084                         report("Cannot revert changes to staged files");
6085                 } else if (type == LINE_STAT_UNTRACKED) {
6086                         report("Cannot revert changes to untracked files");
6087                 } else if (has_none) {
6088                         report("Nothing to revert");
6089                 } else {
6090                         report("Cannot revert changes to multiple files");
6091                 }
6093         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6094                 char mode[10] = "100644";
6095                 const char *reset_argv[] = {
6096                         "git", "update-index", "--cacheinfo", mode,
6097                                 status->old.rev, status->old.name, NULL
6098                 };
6099                 const char *checkout_argv[] = {
6100                         "git", "checkout", "--", status->old.name, NULL
6101                 };
6103                 if (status->status == 'U') {
6104                         string_format(mode, "%5o", status->old.mode);
6106                         if (status->old.mode == 0 && status->new.mode == 0) {
6107                                 reset_argv[2] = "--force-remove";
6108                                 reset_argv[3] = status->old.name;
6109                                 reset_argv[4] = NULL;
6110                         }
6112                         if (!io_run_fg(reset_argv, opt_cdup))
6113                                 return FALSE;
6114                         if (status->old.mode == 0 && status->new.mode == 0)
6115                                 return TRUE;
6116                 }
6118                 return io_run_fg(checkout_argv, opt_cdup);
6119         }
6121         return FALSE;
6124 static enum request
6125 status_request(struct view *view, enum request request, struct line *line)
6127         struct status *status = line->data;
6129         switch (request) {
6130         case REQ_STATUS_UPDATE:
6131                 if (!status_update(view))
6132                         return REQ_NONE;
6133                 break;
6135         case REQ_STATUS_REVERT:
6136                 if (!status_revert(status, line->type, status_has_none(view, line)))
6137                         return REQ_NONE;
6138                 break;
6140         case REQ_STATUS_MERGE:
6141                 if (!status || status->status != 'U') {
6142                         report("Merging only possible for files with unmerged status ('U').");
6143                         return REQ_NONE;
6144                 }
6145                 open_mergetool(status->new.name);
6146                 break;
6148         case REQ_EDIT:
6149                 if (!status)
6150                         return request;
6151                 if (status->status == 'D') {
6152                         report("File has been deleted.");
6153                         return REQ_NONE;
6154                 }
6156                 open_editor(status->new.name);
6157                 break;
6159         case REQ_VIEW_BLAME:
6160                 if (status)
6161                         opt_ref[0] = 0;
6162                 return request;
6164         case REQ_ENTER:
6165                 /* After returning the status view has been split to
6166                  * show the stage view. No further reloading is
6167                  * necessary. */
6168                 return status_enter(view, line);
6170         case REQ_REFRESH:
6171                 /* Simply reload the view. */
6172                 break;
6174         default:
6175                 return request;
6176         }
6178         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6180         return REQ_NONE;
6183 static void
6184 status_select(struct view *view, struct line *line)
6186         struct status *status = line->data;
6187         char file[SIZEOF_STR] = "all files";
6188         const char *text;
6189         const char *key;
6191         if (status && !string_format(file, "'%s'", status->new.name))
6192                 return;
6194         if (!status && line[1].type == LINE_STAT_NONE)
6195                 line++;
6197         switch (line->type) {
6198         case LINE_STAT_STAGED:
6199                 text = "Press %s to unstage %s for commit";
6200                 break;
6202         case LINE_STAT_UNSTAGED:
6203                 text = "Press %s to stage %s for commit";
6204                 break;
6206         case LINE_STAT_UNTRACKED:
6207                 text = "Press %s to stage %s for addition";
6208                 break;
6210         case LINE_STAT_HEAD:
6211         case LINE_STAT_NONE:
6212                 text = "Nothing to update";
6213                 break;
6215         default:
6216                 die("line type %d not handled in switch", line->type);
6217         }
6219         if (status && status->status == 'U') {
6220                 text = "Press %s to resolve conflict in %s";
6221                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6223         } else {
6224                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6225         }
6227         string_format(view->ref, text, key, file);
6228         if (status)
6229                 string_copy(opt_file, status->new.name);
6232 static bool
6233 status_grep(struct view *view, struct line *line)
6235         struct status *status = line->data;
6237         if (status) {
6238                 const char buf[2] = { status->status, 0 };
6239                 const char *text[] = { status->new.name, buf, NULL };
6241                 return grep_text(view, text);
6242         }
6244         return FALSE;
6247 static struct view_ops status_ops = {
6248         "file",
6249         NULL,
6250         status_open,
6251         NULL,
6252         status_draw,
6253         status_request,
6254         status_grep,
6255         status_select,
6256 };
6259 static bool
6260 stage_diff_write(struct io *io, struct line *line, struct line *end)
6262         while (line < end) {
6263                 if (!io_write(io, line->data, strlen(line->data)) ||
6264                     !io_write(io, "\n", 1))
6265                         return FALSE;
6266                 line++;
6267                 if (line->type == LINE_DIFF_CHUNK ||
6268                     line->type == LINE_DIFF_HEADER)
6269                         break;
6270         }
6272         return TRUE;
6275 static struct line *
6276 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6278         for (; view->line < line; line--)
6279                 if (line->type == type)
6280                         return line;
6282         return NULL;
6285 static bool
6286 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6288         const char *apply_argv[SIZEOF_ARG] = {
6289                 "git", "apply", "--whitespace=nowarn", NULL
6290         };
6291         struct line *diff_hdr;
6292         struct io io = {};
6293         int argc = 3;
6295         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6296         if (!diff_hdr)
6297                 return FALSE;
6299         if (!revert)
6300                 apply_argv[argc++] = "--cached";
6301         if (revert || stage_line_type == LINE_STAT_STAGED)
6302                 apply_argv[argc++] = "-R";
6303         apply_argv[argc++] = "-";
6304         apply_argv[argc++] = NULL;
6305         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6306                 return FALSE;
6308         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6309             !stage_diff_write(&io, chunk, view->line + view->lines))
6310                 chunk = NULL;
6312         io_done(&io);
6313         io_run_bg(update_index_argv);
6315         return chunk ? TRUE : FALSE;
6318 static bool
6319 stage_update(struct view *view, struct line *line)
6321         struct line *chunk = NULL;
6323         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6324                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6326         if (chunk) {
6327                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6328                         report("Failed to apply chunk");
6329                         return FALSE;
6330                 }
6332         } else if (!stage_status.status) {
6333                 view = VIEW(REQ_VIEW_STATUS);
6335                 for (line = view->line; line < view->line + view->lines; line++)
6336                         if (line->type == stage_line_type)
6337                                 break;
6339                 if (!status_update_files(view, line + 1)) {
6340                         report("Failed to update files");
6341                         return FALSE;
6342                 }
6344         } else if (!status_update_file(&stage_status, stage_line_type)) {
6345                 report("Failed to update file");
6346                 return FALSE;
6347         }
6349         return TRUE;
6352 static bool
6353 stage_revert(struct view *view, struct line *line)
6355         struct line *chunk = NULL;
6357         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6358                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6360         if (chunk) {
6361                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6362                         return FALSE;
6364                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6365                         report("Failed to revert chunk");
6366                         return FALSE;
6367                 }
6368                 return TRUE;
6370         } else {
6371                 return status_revert(stage_status.status ? &stage_status : NULL,
6372                                      stage_line_type, FALSE);
6373         }
6377 static void
6378 stage_next(struct view *view, struct line *line)
6380         int i;
6382         if (!stage_chunks) {
6383                 for (line = view->line; line < view->line + view->lines; line++) {
6384                         if (line->type != LINE_DIFF_CHUNK)
6385                                 continue;
6387                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6388                                 report("Allocation failure");
6389                                 return;
6390                         }
6392                         stage_chunk[stage_chunks++] = line - view->line;
6393                 }
6394         }
6396         for (i = 0; i < stage_chunks; i++) {
6397                 if (stage_chunk[i] > view->lineno) {
6398                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6399                         report("Chunk %d of %d", i + 1, stage_chunks);
6400                         return;
6401                 }
6402         }
6404         report("No next chunk found");
6407 static enum request
6408 stage_request(struct view *view, enum request request, struct line *line)
6410         switch (request) {
6411         case REQ_STATUS_UPDATE:
6412                 if (!stage_update(view, line))
6413                         return REQ_NONE;
6414                 break;
6416         case REQ_STATUS_REVERT:
6417                 if (!stage_revert(view, line))
6418                         return REQ_NONE;
6419                 break;
6421         case REQ_STAGE_NEXT:
6422                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6423                         report("File is untracked; press %s to add",
6424                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6425                         return REQ_NONE;
6426                 }
6427                 stage_next(view, line);
6428                 return REQ_NONE;
6430         case REQ_EDIT:
6431                 if (!stage_status.new.name[0])
6432                         return request;
6433                 if (stage_status.status == 'D') {
6434                         report("File has been deleted.");
6435                         return REQ_NONE;
6436                 }
6438                 open_editor(stage_status.new.name);
6439                 break;
6441         case REQ_REFRESH:
6442                 /* Reload everything ... */
6443                 break;
6445         case REQ_VIEW_BLAME:
6446                 if (stage_status.new.name[0]) {
6447                         string_copy(opt_file, stage_status.new.name);
6448                         opt_ref[0] = 0;
6449                 }
6450                 return request;
6452         case REQ_ENTER:
6453                 return pager_request(view, request, line);
6455         default:
6456                 return request;
6457         }
6459         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6460         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6462         /* Check whether the staged entry still exists, and close the
6463          * stage view if it doesn't. */
6464         if (!status_exists(&stage_status, stage_line_type)) {
6465                 status_restore(VIEW(REQ_VIEW_STATUS));
6466                 return REQ_VIEW_CLOSE;
6467         }
6469         if (stage_line_type == LINE_STAT_UNTRACKED) {
6470                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6471                         report("Cannot display a directory");
6472                         return REQ_NONE;
6473                 }
6475                 if (!prepare_update_file(view, stage_status.new.name)) {
6476                         report("Failed to open file: %s", strerror(errno));
6477                         return REQ_NONE;
6478                 }
6479         }
6480         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6482         return REQ_NONE;
6485 static struct view_ops stage_ops = {
6486         "line",
6487         NULL,
6488         NULL,
6489         pager_read,
6490         pager_draw,
6491         stage_request,
6492         pager_grep,
6493         pager_select,
6494 };
6497 /*
6498  * Revision graph
6499  */
6501 struct commit {
6502         char id[SIZEOF_REV];            /* SHA1 ID. */
6503         char title[128];                /* First line of the commit message. */
6504         const char *author;             /* Author of the commit. */
6505         struct time time;               /* Date from the author ident. */
6506         struct ref_list *refs;          /* Repository references. */
6507         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6508         size_t graph_size;              /* The width of the graph array. */
6509         bool has_parents;               /* Rewritten --parents seen. */
6510 };
6512 /* Size of rev graph with no  "padding" columns */
6513 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6515 struct rev_graph {
6516         struct rev_graph *prev, *next, *parents;
6517         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6518         size_t size;
6519         struct commit *commit;
6520         size_t pos;
6521         unsigned int boundary:1;
6522 };
6524 /* Parents of the commit being visualized. */
6525 static struct rev_graph graph_parents[4];
6527 /* The current stack of revisions on the graph. */
6528 static struct rev_graph graph_stacks[4] = {
6529         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6530         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6531         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6532         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6533 };
6535 static inline bool
6536 graph_parent_is_merge(struct rev_graph *graph)
6538         return graph->parents->size > 1;
6541 static inline void
6542 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6544         struct commit *commit = graph->commit;
6546         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6547                 commit->graph[commit->graph_size++] = symbol;
6550 static void
6551 clear_rev_graph(struct rev_graph *graph)
6553         graph->boundary = 0;
6554         graph->size = graph->pos = 0;
6555         graph->commit = NULL;
6556         memset(graph->parents, 0, sizeof(*graph->parents));
6559 static void
6560 done_rev_graph(struct rev_graph *graph)
6562         if (graph_parent_is_merge(graph) &&
6563             graph->pos < graph->size - 1 &&
6564             graph->next->size == graph->size + graph->parents->size - 1) {
6565                 size_t i = graph->pos + graph->parents->size - 1;
6567                 graph->commit->graph_size = i * 2;
6568                 while (i < graph->next->size - 1) {
6569                         append_to_rev_graph(graph, ' ');
6570                         append_to_rev_graph(graph, '\\');
6571                         i++;
6572                 }
6573         }
6575         clear_rev_graph(graph);
6578 static void
6579 push_rev_graph(struct rev_graph *graph, const char *parent)
6581         int i;
6583         /* "Collapse" duplicate parents lines.
6584          *
6585          * FIXME: This needs to also update update the drawn graph but
6586          * for now it just serves as a method for pruning graph lines. */
6587         for (i = 0; i < graph->size; i++)
6588                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6589                         return;
6591         if (graph->size < SIZEOF_REVITEMS) {
6592                 string_copy_rev(graph->rev[graph->size++], parent);
6593         }
6596 static chtype
6597 get_rev_graph_symbol(struct rev_graph *graph)
6599         chtype symbol;
6601         if (graph->boundary)
6602                 symbol = REVGRAPH_BOUND;
6603         else if (graph->parents->size == 0)
6604                 symbol = REVGRAPH_INIT;
6605         else if (graph_parent_is_merge(graph))
6606                 symbol = REVGRAPH_MERGE;
6607         else if (graph->pos >= graph->size)
6608                 symbol = REVGRAPH_BRANCH;
6609         else
6610                 symbol = REVGRAPH_COMMIT;
6612         return symbol;
6615 static void
6616 draw_rev_graph(struct rev_graph *graph)
6618         struct rev_filler {
6619                 chtype separator, line;
6620         };
6621         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6622         static struct rev_filler fillers[] = {
6623                 { ' ',  '|' },
6624                 { '`',  '.' },
6625                 { '\'', ' ' },
6626                 { '/',  ' ' },
6627         };
6628         chtype symbol = get_rev_graph_symbol(graph);
6629         struct rev_filler *filler;
6630         size_t i;
6632         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6633         filler = &fillers[DEFAULT];
6635         for (i = 0; i < graph->pos; i++) {
6636                 append_to_rev_graph(graph, filler->line);
6637                 if (graph_parent_is_merge(graph->prev) &&
6638                     graph->prev->pos == i)
6639                         filler = &fillers[RSHARP];
6641                 append_to_rev_graph(graph, filler->separator);
6642         }
6644         /* Place the symbol for this revision. */
6645         append_to_rev_graph(graph, symbol);
6647         if (graph->prev->size > graph->size)
6648                 filler = &fillers[RDIAG];
6649         else
6650                 filler = &fillers[DEFAULT];
6652         i++;
6654         for (; i < graph->size; i++) {
6655                 append_to_rev_graph(graph, filler->separator);
6656                 append_to_rev_graph(graph, filler->line);
6657                 if (graph_parent_is_merge(graph->prev) &&
6658                     i < graph->prev->pos + graph->parents->size)
6659                         filler = &fillers[RSHARP];
6660                 if (graph->prev->size > graph->size)
6661                         filler = &fillers[LDIAG];
6662         }
6664         if (graph->prev->size > graph->size) {
6665                 append_to_rev_graph(graph, filler->separator);
6666                 if (filler->line != ' ')
6667                         append_to_rev_graph(graph, filler->line);
6668         }
6671 /* Prepare the next rev graph */
6672 static void
6673 prepare_rev_graph(struct rev_graph *graph)
6675         size_t i;
6677         /* First, traverse all lines of revisions up to the active one. */
6678         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6679                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6680                         break;
6682                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6683         }
6685         /* Interleave the new revision parent(s). */
6686         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6687                 push_rev_graph(graph->next, graph->parents->rev[i]);
6689         /* Lastly, put any remaining revisions. */
6690         for (i = graph->pos + 1; i < graph->size; i++)
6691                 push_rev_graph(graph->next, graph->rev[i]);
6694 static void
6695 update_rev_graph(struct view *view, struct rev_graph *graph)
6697         /* If this is the finalizing update ... */
6698         if (graph->commit)
6699                 prepare_rev_graph(graph);
6701         /* Graph visualization needs a one rev look-ahead,
6702          * so the first update doesn't visualize anything. */
6703         if (!graph->prev->commit)
6704                 return;
6706         if (view->lines > 2)
6707                 view->line[view->lines - 3].dirty = 1;
6708         if (view->lines > 1)
6709                 view->line[view->lines - 2].dirty = 1;
6710         draw_rev_graph(graph->prev);
6711         done_rev_graph(graph->prev->prev);
6715 /*
6716  * Main view backend
6717  */
6719 static const char *main_argv[SIZEOF_ARG] = {
6720         "git", "log", "--no-color", "--pretty=raw", "--parents",
6721                       "--topo-order", "%(head)", NULL
6722 };
6724 static bool
6725 main_draw(struct view *view, struct line *line, unsigned int lineno)
6727         struct commit *commit = line->data;
6729         if (!commit->author)
6730                 return FALSE;
6732         if (opt_date && draw_date(view, &commit->time))
6733                 return TRUE;
6735         if (opt_author && draw_author(view, commit->author))
6736                 return TRUE;
6738         if (opt_rev_graph && commit->graph_size &&
6739             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6740                 return TRUE;
6742         if (opt_show_refs && commit->refs) {
6743                 size_t i;
6745                 for (i = 0; i < commit->refs->size; i++) {
6746                         struct ref *ref = commit->refs->refs[i];
6747                         enum line_type type;
6749                         if (ref->head)
6750                                 type = LINE_MAIN_HEAD;
6751                         else if (ref->ltag)
6752                                 type = LINE_MAIN_LOCAL_TAG;
6753                         else if (ref->tag)
6754                                 type = LINE_MAIN_TAG;
6755                         else if (ref->tracked)
6756                                 type = LINE_MAIN_TRACKED;
6757                         else if (ref->remote)
6758                                 type = LINE_MAIN_REMOTE;
6759                         else
6760                                 type = LINE_MAIN_REF;
6762                         if (draw_text(view, type, "[", TRUE) ||
6763                             draw_text(view, type, ref->name, TRUE) ||
6764                             draw_text(view, type, "]", TRUE))
6765                                 return TRUE;
6767                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6768                                 return TRUE;
6769                 }
6770         }
6772         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6773         return TRUE;
6776 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6777 static bool
6778 main_read(struct view *view, char *line)
6780         static struct rev_graph *graph = graph_stacks;
6781         enum line_type type;
6782         struct commit *commit;
6784         if (!line) {
6785                 int i;
6787                 if (!view->lines && !view->prev)
6788                         die("No revisions match the given arguments.");
6789                 if (view->lines > 0) {
6790                         commit = view->line[view->lines - 1].data;
6791                         view->line[view->lines - 1].dirty = 1;
6792                         if (!commit->author) {
6793                                 view->lines--;
6794                                 free(commit);
6795                                 graph->commit = NULL;
6796                         }
6797                 }
6798                 update_rev_graph(view, graph);
6800                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6801                         clear_rev_graph(&graph_stacks[i]);
6802                 return TRUE;
6803         }
6805         type = get_line_type(line);
6806         if (type == LINE_COMMIT) {
6807                 commit = calloc(1, sizeof(struct commit));
6808                 if (!commit)
6809                         return FALSE;
6811                 line += STRING_SIZE("commit ");
6812                 if (*line == '-') {
6813                         graph->boundary = 1;
6814                         line++;
6815                 }
6817                 string_copy_rev(commit->id, line);
6818                 commit->refs = get_ref_list(commit->id);
6819                 graph->commit = commit;
6820                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6822                 while ((line = strchr(line, ' '))) {
6823                         line++;
6824                         push_rev_graph(graph->parents, line);
6825                         commit->has_parents = TRUE;
6826                 }
6827                 return TRUE;
6828         }
6830         if (!view->lines)
6831                 return TRUE;
6832         commit = view->line[view->lines - 1].data;
6834         switch (type) {
6835         case LINE_PARENT:
6836                 if (commit->has_parents)
6837                         break;
6838                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6839                 break;
6841         case LINE_AUTHOR:
6842                 parse_author_line(line + STRING_SIZE("author "),
6843                                   &commit->author, &commit->time);
6844                 update_rev_graph(view, graph);
6845                 graph = graph->next;
6846                 break;
6848         default:
6849                 /* Fill in the commit title if it has not already been set. */
6850                 if (commit->title[0])
6851                         break;
6853                 /* Require titles to start with a non-space character at the
6854                  * offset used by git log. */
6855                 if (strncmp(line, "    ", 4))
6856                         break;
6857                 line += 4;
6858                 /* Well, if the title starts with a whitespace character,
6859                  * try to be forgiving.  Otherwise we end up with no title. */
6860                 while (isspace(*line))
6861                         line++;
6862                 if (*line == '\0')
6863                         break;
6864                 /* FIXME: More graceful handling of titles; append "..." to
6865                  * shortened titles, etc. */
6867                 string_expand(commit->title, sizeof(commit->title), line, 1);
6868                 view->line[view->lines - 1].dirty = 1;
6869         }
6871         return TRUE;
6874 static enum request
6875 main_request(struct view *view, enum request request, struct line *line)
6877         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6879         switch (request) {
6880         case REQ_ENTER:
6881                 open_view(view, REQ_VIEW_DIFF, flags);
6882                 break;
6883         case REQ_REFRESH:
6884                 load_refs();
6885                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6886                 break;
6887         default:
6888                 return request;
6889         }
6891         return REQ_NONE;
6894 static bool
6895 grep_refs(struct ref_list *list, regex_t *regex)
6897         regmatch_t pmatch;
6898         size_t i;
6900         if (!opt_show_refs || !list)
6901                 return FALSE;
6903         for (i = 0; i < list->size; i++) {
6904                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6905                         return TRUE;
6906         }
6908         return FALSE;
6911 static bool
6912 main_grep(struct view *view, struct line *line)
6914         struct commit *commit = line->data;
6915         const char *text[] = {
6916                 commit->title,
6917                 opt_author ? commit->author : "",
6918                 mkdate(&commit->time, opt_date),
6919                 NULL
6920         };
6922         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6925 static void
6926 main_select(struct view *view, struct line *line)
6928         struct commit *commit = line->data;
6930         string_copy_rev(view->ref, commit->id);
6931         string_copy_rev(ref_commit, view->ref);
6934 static struct view_ops main_ops = {
6935         "commit",
6936         main_argv,
6937         NULL,
6938         main_read,
6939         main_draw,
6940         main_request,
6941         main_grep,
6942         main_select,
6943 };
6946 /*
6947  * Status management
6948  */
6950 /* Whether or not the curses interface has been initialized. */
6951 static bool cursed = FALSE;
6953 /* Terminal hacks and workarounds. */
6954 static bool use_scroll_redrawwin;
6955 static bool use_scroll_status_wclear;
6957 /* The status window is used for polling keystrokes. */
6958 static WINDOW *status_win;
6960 /* Reading from the prompt? */
6961 static bool input_mode = FALSE;
6963 static bool status_empty = FALSE;
6965 /* Update status and title window. */
6966 static void
6967 report(const char *msg, ...)
6969         struct view *view = display[current_view];
6971         if (input_mode)
6972                 return;
6974         if (!view) {
6975                 char buf[SIZEOF_STR];
6976                 va_list args;
6978                 va_start(args, msg);
6979                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6980                         buf[sizeof(buf) - 1] = 0;
6981                         buf[sizeof(buf) - 2] = '.';
6982                         buf[sizeof(buf) - 3] = '.';
6983                         buf[sizeof(buf) - 4] = '.';
6984                 }
6985                 va_end(args);
6986                 die("%s", buf);
6987         }
6989         if (!status_empty || *msg) {
6990                 va_list args;
6992                 va_start(args, msg);
6994                 wmove(status_win, 0, 0);
6995                 if (view->has_scrolled && use_scroll_status_wclear)
6996                         wclear(status_win);
6997                 if (*msg) {
6998                         vwprintw(status_win, msg, args);
6999                         status_empty = FALSE;
7000                 } else {
7001                         status_empty = TRUE;
7002                 }
7003                 wclrtoeol(status_win);
7004                 wnoutrefresh(status_win);
7006                 va_end(args);
7007         }
7009         update_view_title(view);
7012 static void
7013 init_display(void)
7015         const char *term;
7016         int x, y;
7018         /* Initialize the curses library */
7019         if (isatty(STDIN_FILENO)) {
7020                 cursed = !!initscr();
7021                 opt_tty = stdin;
7022         } else {
7023                 /* Leave stdin and stdout alone when acting as a pager. */
7024                 opt_tty = fopen("/dev/tty", "r+");
7025                 if (!opt_tty)
7026                         die("Failed to open /dev/tty");
7027                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7028         }
7030         if (!cursed)
7031                 die("Failed to initialize curses");
7033         nonl();         /* Disable conversion and detect newlines from input. */
7034         cbreak();       /* Take input chars one at a time, no wait for \n */
7035         noecho();       /* Don't echo input */
7036         leaveok(stdscr, FALSE);
7038         if (has_colors())
7039                 init_colors();
7041         getmaxyx(stdscr, y, x);
7042         status_win = newwin(1, 0, y - 1, 0);
7043         if (!status_win)
7044                 die("Failed to create status window");
7046         /* Enable keyboard mapping */
7047         keypad(status_win, TRUE);
7048         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7050         TABSIZE = opt_tab_size;
7052         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7053         if (term && !strcmp(term, "gnome-terminal")) {
7054                 /* In the gnome-terminal-emulator, the message from
7055                  * scrolling up one line when impossible followed by
7056                  * scrolling down one line causes corruption of the
7057                  * status line. This is fixed by calling wclear. */
7058                 use_scroll_status_wclear = TRUE;
7059                 use_scroll_redrawwin = FALSE;
7061         } else if (term && !strcmp(term, "xrvt-xpm")) {
7062                 /* No problems with full optimizations in xrvt-(unicode)
7063                  * and aterm. */
7064                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7066         } else {
7067                 /* When scrolling in (u)xterm the last line in the
7068                  * scrolling direction will update slowly. */
7069                 use_scroll_redrawwin = TRUE;
7070                 use_scroll_status_wclear = FALSE;
7071         }
7074 static int
7075 get_input(int prompt_position)
7077         struct view *view;
7078         int i, key, cursor_y, cursor_x;
7079         bool loading = FALSE;
7081         if (prompt_position)
7082                 input_mode = TRUE;
7084         while (TRUE) {
7085                 foreach_view (view, i) {
7086                         update_view(view);
7087                         if (view_is_displayed(view) && view->has_scrolled &&
7088                             use_scroll_redrawwin)
7089                                 redrawwin(view->win);
7090                         view->has_scrolled = FALSE;
7091                         if (view->pipe)
7092                                 loading = TRUE;
7093                 }
7095                 /* Update the cursor position. */
7096                 if (prompt_position) {
7097                         getbegyx(status_win, cursor_y, cursor_x);
7098                         cursor_x = prompt_position;
7099                 } else {
7100                         view = display[current_view];
7101                         getbegyx(view->win, cursor_y, cursor_x);
7102                         cursor_x = view->width - 1;
7103                         cursor_y += view->lineno - view->offset;
7104                 }
7105                 setsyx(cursor_y, cursor_x);
7107                 /* Refresh, accept single keystroke of input */
7108                 doupdate();
7109                 nodelay(status_win, loading);
7110                 key = wgetch(status_win);
7112                 /* wgetch() with nodelay() enabled returns ERR when
7113                  * there's no input. */
7114                 if (key == ERR) {
7116                 } else if (key == KEY_RESIZE) {
7117                         int height, width;
7119                         getmaxyx(stdscr, height, width);
7121                         wresize(status_win, 1, width);
7122                         mvwin(status_win, height - 1, 0);
7123                         wnoutrefresh(status_win);
7124                         resize_display();
7125                         redraw_display(TRUE);
7127                 } else {
7128                         input_mode = FALSE;
7129                         return key;
7130                 }
7131         }
7134 static char *
7135 prompt_input(const char *prompt, input_handler handler, void *data)
7137         enum input_status status = INPUT_OK;
7138         static char buf[SIZEOF_STR];
7139         size_t pos = 0;
7141         buf[pos] = 0;
7143         while (status == INPUT_OK || status == INPUT_SKIP) {
7144                 int key;
7146                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7147                 wclrtoeol(status_win);
7149                 key = get_input(pos + 1);
7150                 switch (key) {
7151                 case KEY_RETURN:
7152                 case KEY_ENTER:
7153                 case '\n':
7154                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7155                         break;
7157                 case KEY_BACKSPACE:
7158                         if (pos > 0)
7159                                 buf[--pos] = 0;
7160                         else
7161                                 status = INPUT_CANCEL;
7162                         break;
7164                 case KEY_ESC:
7165                         status = INPUT_CANCEL;
7166                         break;
7168                 default:
7169                         if (pos >= sizeof(buf)) {
7170                                 report("Input string too long");
7171                                 return NULL;
7172                         }
7174                         status = handler(data, buf, key);
7175                         if (status == INPUT_OK)
7176                                 buf[pos++] = (char) key;
7177                 }
7178         }
7180         /* Clear the status window */
7181         status_empty = FALSE;
7182         report("");
7184         if (status == INPUT_CANCEL)
7185                 return NULL;
7187         buf[pos++] = 0;
7189         return buf;
7192 static enum input_status
7193 prompt_yesno_handler(void *data, char *buf, int c)
7195         if (c == 'y' || c == 'Y')
7196                 return INPUT_STOP;
7197         if (c == 'n' || c == 'N')
7198                 return INPUT_CANCEL;
7199         return INPUT_SKIP;
7202 static bool
7203 prompt_yesno(const char *prompt)
7205         char prompt2[SIZEOF_STR];
7207         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7208                 return FALSE;
7210         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7213 static enum input_status
7214 read_prompt_handler(void *data, char *buf, int c)
7216         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7219 static char *
7220 read_prompt(const char *prompt)
7222         return prompt_input(prompt, read_prompt_handler, NULL);
7225 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7227         enum input_status status = INPUT_OK;
7228         int size = 0;
7230         while (items[size].text)
7231                 size++;
7233         while (status == INPUT_OK) {
7234                 const struct menu_item *item = &items[*selected];
7235                 int key;
7236                 int i;
7238                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7239                           prompt, *selected + 1, size);
7240                 if (item->hotkey)
7241                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7242                 wprintw(status_win, "%s", item->text);
7243                 wclrtoeol(status_win);
7245                 key = get_input(COLS - 1);
7246                 switch (key) {
7247                 case KEY_RETURN:
7248                 case KEY_ENTER:
7249                 case '\n':
7250                         status = INPUT_STOP;
7251                         break;
7253                 case KEY_LEFT:
7254                 case KEY_UP:
7255                         *selected = *selected - 1;
7256                         if (*selected < 0)
7257                                 *selected = size - 1;
7258                         break;
7260                 case KEY_RIGHT:
7261                 case KEY_DOWN:
7262                         *selected = (*selected + 1) % size;
7263                         break;
7265                 case KEY_ESC:
7266                         status = INPUT_CANCEL;
7267                         break;
7269                 default:
7270                         for (i = 0; items[i].text; i++)
7271                                 if (items[i].hotkey == key) {
7272                                         *selected = i;
7273                                         status = INPUT_STOP;
7274                                         break;
7275                                 }
7276                 }
7277         }
7279         /* Clear the status window */
7280         status_empty = FALSE;
7281         report("");
7283         return status != INPUT_CANCEL;
7286 /*
7287  * Repository properties
7288  */
7290 static struct ref **refs = NULL;
7291 static size_t refs_size = 0;
7292 static struct ref *refs_head = NULL;
7294 static struct ref_list **ref_lists = NULL;
7295 static size_t ref_lists_size = 0;
7297 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7298 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7299 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7301 static int
7302 compare_refs(const void *ref1_, const void *ref2_)
7304         const struct ref *ref1 = *(const struct ref **)ref1_;
7305         const struct ref *ref2 = *(const struct ref **)ref2_;
7307         if (ref1->tag != ref2->tag)
7308                 return ref2->tag - ref1->tag;
7309         if (ref1->ltag != ref2->ltag)
7310                 return ref2->ltag - ref2->ltag;
7311         if (ref1->head != ref2->head)
7312                 return ref2->head - ref1->head;
7313         if (ref1->tracked != ref2->tracked)
7314                 return ref2->tracked - ref1->tracked;
7315         if (ref1->remote != ref2->remote)
7316                 return ref2->remote - ref1->remote;
7317         return strcmp(ref1->name, ref2->name);
7320 static void
7321 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7323         size_t i;
7325         for (i = 0; i < refs_size; i++)
7326                 if (!visitor(data, refs[i]))
7327                         break;
7330 static struct ref *
7331 get_ref_head()
7333         return refs_head;
7336 static struct ref_list *
7337 get_ref_list(const char *id)
7339         struct ref_list *list;
7340         size_t i;
7342         for (i = 0; i < ref_lists_size; i++)
7343                 if (!strcmp(id, ref_lists[i]->id))
7344                         return ref_lists[i];
7346         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7347                 return NULL;
7348         list = calloc(1, sizeof(*list));
7349         if (!list)
7350                 return NULL;
7352         for (i = 0; i < refs_size; i++) {
7353                 if (!strcmp(id, refs[i]->id) &&
7354                     realloc_refs_list(&list->refs, list->size, 1))
7355                         list->refs[list->size++] = refs[i];
7356         }
7358         if (!list->refs) {
7359                 free(list);
7360                 return NULL;
7361         }
7363         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7364         ref_lists[ref_lists_size++] = list;
7365         return list;
7368 static int
7369 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7371         struct ref *ref = NULL;
7372         bool tag = FALSE;
7373         bool ltag = FALSE;
7374         bool remote = FALSE;
7375         bool tracked = FALSE;
7376         bool head = FALSE;
7377         int from = 0, to = refs_size - 1;
7379         if (!prefixcmp(name, "refs/tags/")) {
7380                 if (!suffixcmp(name, namelen, "^{}")) {
7381                         namelen -= 3;
7382                         name[namelen] = 0;
7383                 } else {
7384                         ltag = TRUE;
7385                 }
7387                 tag = TRUE;
7388                 namelen -= STRING_SIZE("refs/tags/");
7389                 name    += STRING_SIZE("refs/tags/");
7391         } else if (!prefixcmp(name, "refs/remotes/")) {
7392                 remote = TRUE;
7393                 namelen -= STRING_SIZE("refs/remotes/");
7394                 name    += STRING_SIZE("refs/remotes/");
7395                 tracked  = !strcmp(opt_remote, name);
7397         } else if (!prefixcmp(name, "refs/heads/")) {
7398                 namelen -= STRING_SIZE("refs/heads/");
7399                 name    += STRING_SIZE("refs/heads/");
7400                 if (!strncmp(opt_head, name, namelen))
7401                         return OK;
7403         } else if (!strcmp(name, "HEAD")) {
7404                 head     = TRUE;
7405                 if (*opt_head) {
7406                         namelen  = strlen(opt_head);
7407                         name     = opt_head;
7408                 }
7409         }
7411         /* If we are reloading or it's an annotated tag, replace the
7412          * previous SHA1 with the resolved commit id; relies on the fact
7413          * git-ls-remote lists the commit id of an annotated tag right
7414          * before the commit id it points to. */
7415         while (from <= to) {
7416                 size_t pos = (to + from) / 2;
7417                 int cmp = strcmp(name, refs[pos]->name);
7419                 if (!cmp) {
7420                         ref = refs[pos];
7421                         break;
7422                 }
7424                 if (cmp < 0)
7425                         to = pos - 1;
7426                 else
7427                         from = pos + 1;
7428         }
7430         if (!ref) {
7431                 if (!realloc_refs(&refs, refs_size, 1))
7432                         return ERR;
7433                 ref = calloc(1, sizeof(*ref) + namelen);
7434                 if (!ref)
7435                         return ERR;
7436                 memmove(refs + from + 1, refs + from,
7437                         (refs_size - from) * sizeof(*refs));
7438                 refs[from] = ref;
7439                 strncpy(ref->name, name, namelen);
7440                 refs_size++;
7441         }
7443         ref->head = head;
7444         ref->tag = tag;
7445         ref->ltag = ltag;
7446         ref->remote = remote;
7447         ref->tracked = tracked;
7448         string_copy_rev(ref->id, id);
7450         if (head)
7451                 refs_head = ref;
7452         return OK;
7455 static int
7456 load_refs(void)
7458         const char *head_argv[] = {
7459                 "git", "symbolic-ref", "HEAD", NULL
7460         };
7461         static const char *ls_remote_argv[SIZEOF_ARG] = {
7462                 "git", "ls-remote", opt_git_dir, NULL
7463         };
7464         static bool init = FALSE;
7465         size_t i;
7467         if (!init) {
7468                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7469                         die("TIG_LS_REMOTE contains too many arguments");
7470                 init = TRUE;
7471         }
7473         if (!*opt_git_dir)
7474                 return OK;
7476         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7477             !prefixcmp(opt_head, "refs/heads/")) {
7478                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7480                 memmove(opt_head, offset, strlen(offset) + 1);
7481         }
7483         refs_head = NULL;
7484         for (i = 0; i < refs_size; i++)
7485                 refs[i]->id[0] = 0;
7487         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7488                 return ERR;
7490         /* Update the ref lists to reflect changes. */
7491         for (i = 0; i < ref_lists_size; i++) {
7492                 struct ref_list *list = ref_lists[i];
7493                 size_t old, new;
7495                 for (old = new = 0; old < list->size; old++)
7496                         if (!strcmp(list->id, list->refs[old]->id))
7497                                 list->refs[new++] = list->refs[old];
7498                 list->size = new;
7499         }
7501         return OK;
7504 static void
7505 set_remote_branch(const char *name, const char *value, size_t valuelen)
7507         if (!strcmp(name, ".remote")) {
7508                 string_ncopy(opt_remote, value, valuelen);
7510         } else if (*opt_remote && !strcmp(name, ".merge")) {
7511                 size_t from = strlen(opt_remote);
7513                 if (!prefixcmp(value, "refs/heads/"))
7514                         value += STRING_SIZE("refs/heads/");
7516                 if (!string_format_from(opt_remote, &from, "/%s", value))
7517                         opt_remote[0] = 0;
7518         }
7521 static void
7522 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7524         const char *argv[SIZEOF_ARG] = { name, "=" };
7525         int argc = 1 + (cmd == option_set_command);
7526         int error = ERR;
7528         if (!argv_from_string(argv, &argc, value))
7529                 config_msg = "Too many option arguments";
7530         else
7531                 error = cmd(argc, argv);
7533         if (error == ERR)
7534                 warn("Option 'tig.%s': %s", name, config_msg);
7537 static bool
7538 set_environment_variable(const char *name, const char *value)
7540         size_t len = strlen(name) + 1 + strlen(value) + 1;
7541         char *env = malloc(len);
7543         if (env &&
7544             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7545             putenv(env) == 0)
7546                 return TRUE;
7547         free(env);
7548         return FALSE;
7551 static void
7552 set_work_tree(const char *value)
7554         char cwd[SIZEOF_STR];
7556         if (!getcwd(cwd, sizeof(cwd)))
7557                 die("Failed to get cwd path: %s", strerror(errno));
7558         if (chdir(opt_git_dir) < 0)
7559                 die("Failed to chdir(%s): %s", strerror(errno));
7560         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7561                 die("Failed to get git path: %s", strerror(errno));
7562         if (chdir(cwd) < 0)
7563                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7564         if (chdir(value) < 0)
7565                 die("Failed to chdir(%s): %s", value, strerror(errno));
7566         if (!getcwd(cwd, sizeof(cwd)))
7567                 die("Failed to get cwd path: %s", strerror(errno));
7568         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7569                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7570         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7571                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7572         opt_is_inside_work_tree = TRUE;
7575 static int
7576 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7578         if (!strcmp(name, "i18n.commitencoding"))
7579                 string_ncopy(opt_encoding, value, valuelen);
7581         else if (!strcmp(name, "core.editor"))
7582                 string_ncopy(opt_editor, value, valuelen);
7584         else if (!strcmp(name, "core.worktree"))
7585                 set_work_tree(value);
7587         else if (!prefixcmp(name, "tig.color."))
7588                 set_repo_config_option(name + 10, value, option_color_command);
7590         else if (!prefixcmp(name, "tig.bind."))
7591                 set_repo_config_option(name + 9, value, option_bind_command);
7593         else if (!prefixcmp(name, "tig."))
7594                 set_repo_config_option(name + 4, value, option_set_command);
7596         else if (*opt_head && !prefixcmp(name, "branch.") &&
7597                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7598                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7600         return OK;
7603 static int
7604 load_git_config(void)
7606         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7608         return io_run_load(config_list_argv, "=", read_repo_config_option);
7611 static int
7612 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7614         if (!opt_git_dir[0]) {
7615                 string_ncopy(opt_git_dir, name, namelen);
7617         } else if (opt_is_inside_work_tree == -1) {
7618                 /* This can be 3 different values depending on the
7619                  * version of git being used. If git-rev-parse does not
7620                  * understand --is-inside-work-tree it will simply echo
7621                  * the option else either "true" or "false" is printed.
7622                  * Default to true for the unknown case. */
7623                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7625         } else if (*name == '.') {
7626                 string_ncopy(opt_cdup, name, namelen);
7628         } else {
7629                 string_ncopy(opt_prefix, name, namelen);
7630         }
7632         return OK;
7635 static int
7636 load_repo_info(void)
7638         const char *rev_parse_argv[] = {
7639                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7640                         "--show-cdup", "--show-prefix", NULL
7641         };
7643         return io_run_load(rev_parse_argv, "=", read_repo_info);
7647 /*
7648  * Main
7649  */
7651 static const char usage[] =
7652 "tig " TIG_VERSION " (" __DATE__ ")\n"
7653 "\n"
7654 "Usage: tig        [options] [revs] [--] [paths]\n"
7655 "   or: tig show   [options] [revs] [--] [paths]\n"
7656 "   or: tig blame  [rev] path\n"
7657 "   or: tig status\n"
7658 "   or: tig <      [git command output]\n"
7659 "\n"
7660 "Options:\n"
7661 "  -v, --version   Show version and exit\n"
7662 "  -h, --help      Show help message and exit";
7664 static void __NORETURN
7665 quit(int sig)
7667         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7668         if (cursed)
7669                 endwin();
7670         exit(0);
7673 static void __NORETURN
7674 die(const char *err, ...)
7676         va_list args;
7678         endwin();
7680         va_start(args, err);
7681         fputs("tig: ", stderr);
7682         vfprintf(stderr, err, args);
7683         fputs("\n", stderr);
7684         va_end(args);
7686         exit(1);
7689 static void
7690 warn(const char *msg, ...)
7692         va_list args;
7694         va_start(args, msg);
7695         fputs("tig warning: ", stderr);
7696         vfprintf(stderr, msg, args);
7697         fputs("\n", stderr);
7698         va_end(args);
7701 static enum request
7702 parse_options(int argc, const char *argv[])
7704         enum request request = REQ_VIEW_MAIN;
7705         const char *subcommand;
7706         bool seen_dashdash = FALSE;
7707         /* XXX: This is vulnerable to the user overriding options
7708          * required for the main view parser. */
7709         const char *custom_argv[SIZEOF_ARG] = {
7710                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7711                         "--topo-order", NULL
7712         };
7713         int i, j = 6;
7715         if (!isatty(STDIN_FILENO)) {
7716                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7717                 return REQ_VIEW_PAGER;
7718         }
7720         if (argc <= 1)
7721                 return REQ_NONE;
7723         subcommand = argv[1];
7724         if (!strcmp(subcommand, "status")) {
7725                 if (argc > 2)
7726                         warn("ignoring arguments after `%s'", subcommand);
7727                 return REQ_VIEW_STATUS;
7729         } else if (!strcmp(subcommand, "blame")) {
7730                 if (argc <= 2 || argc > 4)
7731                         die("invalid number of options to blame\n\n%s", usage);
7733                 i = 2;
7734                 if (argc == 4) {
7735                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7736                         i++;
7737                 }
7739                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7740                 return REQ_VIEW_BLAME;
7742         } else if (!strcmp(subcommand, "show")) {
7743                 request = REQ_VIEW_DIFF;
7745         } else {
7746                 subcommand = NULL;
7747         }
7749         if (subcommand) {
7750                 custom_argv[1] = subcommand;
7751                 j = 2;
7752         }
7754         for (i = 1 + !!subcommand; i < argc; i++) {
7755                 const char *opt = argv[i];
7757                 if (seen_dashdash || !strcmp(opt, "--")) {
7758                         seen_dashdash = TRUE;
7760                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7761                         printf("tig version %s\n", TIG_VERSION);
7762                         quit(0);
7764                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7765                         printf("%s\n", usage);
7766                         quit(0);
7767                 }
7769                 custom_argv[j++] = opt;
7770                 if (j >= ARRAY_SIZE(custom_argv))
7771                         die("command too long");
7772         }
7774         if (!prepare_update(VIEW(request), custom_argv, NULL))
7775                 die("Failed to format arguments");
7777         return request;
7780 int
7781 main(int argc, const char *argv[])
7783         const char *codeset = "UTF-8";
7784         enum request request = parse_options(argc, argv);
7785         struct view *view;
7786         size_t i;
7788         signal(SIGINT, quit);
7789         signal(SIGPIPE, SIG_IGN);
7791         if (setlocale(LC_ALL, "")) {
7792                 codeset = nl_langinfo(CODESET);
7793         }
7795         if (load_repo_info() == ERR)
7796                 die("Failed to load repo info.");
7798         if (load_options() == ERR)
7799                 die("Failed to load user config.");
7801         if (load_git_config() == ERR)
7802                 die("Failed to load repo config.");
7804         /* Require a git repository unless when running in pager mode. */
7805         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7806                 die("Not a git repository");
7808         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7809                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7810                 if (opt_iconv_in == ICONV_NONE)
7811                         die("Failed to initialize character set conversion");
7812         }
7814         if (codeset && strcmp(codeset, "UTF-8")) {
7815                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7816                 if (opt_iconv_out == ICONV_NONE)
7817                         die("Failed to initialize character set conversion");
7818         }
7820         if (load_refs() == ERR)
7821                 die("Failed to load refs.");
7823         foreach_view (view, i)
7824                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7825                         die("Too many arguments in the `%s` environment variable",
7826                             view->cmd_env);
7828         init_display();
7830         if (request != REQ_NONE)
7831                 open_view(NULL, request, OPEN_PREPARED);
7832         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7834         while (view_driver(display[current_view], request)) {
7835                 int key = get_input(0);
7837                 view = display[current_view];
7838                 request = get_keybinding(view->keymap, key);
7840                 /* Some low-level request handling. This keeps access to
7841                  * status_win restricted. */
7842                 switch (request) {
7843                 case REQ_NONE:
7844                         report("Unknown key, press %s for help",
7845                                get_key(view->keymap, REQ_VIEW_HELP));
7846                         break;
7847                 case REQ_PROMPT:
7848                 {
7849                         char *cmd = read_prompt(":");
7851                         if (cmd && isdigit(*cmd)) {
7852                                 int lineno = view->lineno + 1;
7854                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7855                                         select_view_line(view, lineno - 1);
7856                                         report("");
7857                                 } else {
7858                                         report("Unable to parse '%s' as a line number", cmd);
7859                                 }
7861                         } else if (cmd) {
7862                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7863                                 const char *argv[SIZEOF_ARG] = { "git" };
7864                                 int argc = 1;
7866                                 /* When running random commands, initially show the
7867                                  * command in the title. However, it maybe later be
7868                                  * overwritten if a commit line is selected. */
7869                                 string_ncopy(next->ref, cmd, strlen(cmd));
7871                                 if (!argv_from_string(argv, &argc, cmd)) {
7872                                         report("Too many arguments");
7873                                 } else if (!prepare_update(next, argv, NULL)) {
7874                                         report("Failed to format command");
7875                                 } else {
7876                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7877                                 }
7878                         }
7880                         request = REQ_NONE;
7881                         break;
7882                 }
7883                 case REQ_SEARCH:
7884                 case REQ_SEARCH_BACK:
7885                 {
7886                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7887                         char *search = read_prompt(prompt);
7889                         if (search)
7890                                 string_ncopy(opt_search, search, strlen(search));
7891                         else if (*opt_search)
7892                                 request = request == REQ_SEARCH ?
7893                                         REQ_FIND_NEXT :
7894                                         REQ_FIND_PREV;
7895                         else
7896                                 request = REQ_NONE;
7897                         break;
7898                 }
7899                 default:
7900                         break;
7901                 }
7902         }
7904         quit(0);
7906         return 0;