Code

Color 'Reviewed-by' and 'Tested-by' lines
[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;
2243         /* Buffering */
2244         size_t lines;           /* Total number of lines */
2245         struct line *line;      /* Line index */
2246         unsigned int digits;    /* Number of digits in the lines member. */
2248         /* Drawing */
2249         struct line *curline;   /* Line currently being drawn. */
2250         enum line_type curtype; /* Attribute currently used for drawing. */
2251         unsigned long col;      /* Column when drawing. */
2252         bool has_scrolled;      /* View was scrolled. */
2254         /* Loading */
2255         struct io io;
2256         struct io *pipe;
2257         time_t start_time;
2258         time_t update_secs;
2259 };
2261 struct view_ops {
2262         /* What type of content being displayed. Used in the title bar. */
2263         const char *type;
2264         /* Default command arguments. */
2265         const char **argv;
2266         /* Open and reads in all view content. */
2267         bool (*open)(struct view *view);
2268         /* Read one line; updates view->line. */
2269         bool (*read)(struct view *view, char *data);
2270         /* Draw one line; @lineno must be < view->height. */
2271         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2272         /* Depending on view handle a special requests. */
2273         enum request (*request)(struct view *view, enum request request, struct line *line);
2274         /* Search for regexp in a line. */
2275         bool (*grep)(struct view *view, struct line *line);
2276         /* Select line */
2277         void (*select)(struct view *view, struct line *line);
2278         /* Prepare view for loading */
2279         bool (*prepare)(struct view *view);
2280 };
2282 static struct view_ops blame_ops;
2283 static struct view_ops blob_ops;
2284 static struct view_ops diff_ops;
2285 static struct view_ops help_ops;
2286 static struct view_ops log_ops;
2287 static struct view_ops main_ops;
2288 static struct view_ops pager_ops;
2289 static struct view_ops stage_ops;
2290 static struct view_ops status_ops;
2291 static struct view_ops tree_ops;
2292 static struct view_ops branch_ops;
2294 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2295         { type, name, #env, ref, ops, map, git, refresh }
2297 #define VIEW_(id, name, ops, git, refresh, ref) \
2298         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2300 static struct view views[] = {
2301         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  TRUE,  ref_head),
2302         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  FALSE, ref_commit),
2303         VIEW_(LOG,    "log",    &log_ops,    TRUE,  TRUE,  ref_head),
2304         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  FALSE, ref_commit),
2305         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  FALSE, ref_blob),
2306         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  FALSE, ref_commit),
2307         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  TRUE,  ref_head),
2308         VIEW_(HELP,   "help",   &help_ops,   FALSE, FALSE, ""),
2309         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, FALSE, "stdin"),
2310         VIEW_(STATUS, "status", &status_ops, TRUE,  TRUE,  ""),
2311         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  TRUE,  ""),
2312 };
2314 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2316 #define foreach_view(view, i) \
2317         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2319 #define view_is_displayed(view) \
2320         (view == display[0] || view == display[1])
2322 #define view_has_parent(view, child_type, parent_type) \
2323         (view->type == child_type && view->parent && view->parent->type == parent_type)
2325 static inline void
2326 set_view_attr(struct view *view, enum line_type type)
2328         if (!view->curline->selected && view->curtype != type) {
2329                 (void) wattrset(view->win, get_line_attr(type));
2330                 wchgat(view->win, -1, 0, type, NULL);
2331                 view->curtype = type;
2332         }
2335 static int
2336 draw_chars(struct view *view, enum line_type type, const char *string,
2337            int max_len, bool use_tilde)
2339         static char out_buffer[BUFSIZ * 2];
2340         int len = 0;
2341         int col = 0;
2342         int trimmed = FALSE;
2343         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2345         if (max_len <= 0)
2346                 return 0;
2348         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2350         set_view_attr(view, type);
2351         if (len > 0) {
2352                 if (opt_iconv_out != ICONV_NONE) {
2353                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2354                         size_t inlen = len + 1;
2356                         char *outbuf = out_buffer;
2357                         size_t outlen = sizeof(out_buffer);
2359                         size_t ret;
2361                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2362                         if (ret != (size_t) -1) {
2363                                 string = out_buffer;
2364                                 len = sizeof(out_buffer) - outlen;
2365                         }
2366                 }
2368                 waddnstr(view->win, string, len);
2369         }
2370         if (trimmed && use_tilde) {
2371                 set_view_attr(view, LINE_DELIMITER);
2372                 waddch(view->win, '~');
2373                 col++;
2374         }
2376         return col;
2379 static int
2380 draw_space(struct view *view, enum line_type type, int max, int spaces)
2382         static char space[] = "                    ";
2383         int col = 0;
2385         spaces = MIN(max, spaces);
2387         while (spaces > 0) {
2388                 int len = MIN(spaces, sizeof(space) - 1);
2390                 col += draw_chars(view, type, space, len, FALSE);
2391                 spaces -= len;
2392         }
2394         return col;
2397 static bool
2398 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2400         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2401         return view->width + view->yoffset <= view->col;
2404 static bool
2405 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2407         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2408         int max = view->width + view->yoffset - view->col;
2409         int i;
2411         if (max < size)
2412                 size = max;
2414         set_view_attr(view, type);
2415         /* Using waddch() instead of waddnstr() ensures that
2416          * they'll be rendered correctly for the cursor line. */
2417         for (i = skip; i < size; i++)
2418                 waddch(view->win, graphic[i]);
2420         view->col += size;
2421         if (size < max && skip <= size)
2422                 waddch(view->win, ' ');
2423         view->col++;
2425         return view->width + view->yoffset <= view->col;
2428 static bool
2429 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2431         int max = MIN(view->width + view->yoffset - view->col, len);
2432         int col;
2434         if (text)
2435                 col = draw_chars(view, type, text, max - 1, trim);
2436         else
2437                 col = draw_space(view, type, max - 1, max - 1);
2439         view->col += col;
2440         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2441         return view->width + view->yoffset <= view->col;
2444 static bool
2445 draw_date(struct view *view, struct time *time)
2447         const char *date = mkdate(time, opt_date);
2448         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2450         return draw_field(view, LINE_DATE, date, cols, FALSE);
2453 static bool
2454 draw_author(struct view *view, const char *author)
2456         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2457         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2459         if (abbreviate && author)
2460                 author = get_author_initials(author);
2462         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2465 static bool
2466 draw_mode(struct view *view, mode_t mode)
2468         const char *str;
2470         if (S_ISDIR(mode))
2471                 str = "drwxr-xr-x";
2472         else if (S_ISLNK(mode))
2473                 str = "lrwxrwxrwx";
2474         else if (S_ISGITLINK(mode))
2475                 str = "m---------";
2476         else if (S_ISREG(mode) && mode & S_IXUSR)
2477                 str = "-rwxr-xr-x";
2478         else if (S_ISREG(mode))
2479                 str = "-rw-r--r--";
2480         else
2481                 str = "----------";
2483         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2486 static bool
2487 draw_lineno(struct view *view, unsigned int lineno)
2489         char number[10];
2490         int digits3 = view->digits < 3 ? 3 : view->digits;
2491         int max = MIN(view->width + view->yoffset - view->col, digits3);
2492         char *text = NULL;
2493         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2495         lineno += view->offset + 1;
2496         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2497                 static char fmt[] = "%1ld";
2499                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2500                 if (string_format(number, fmt, lineno))
2501                         text = number;
2502         }
2503         if (text)
2504                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2505         else
2506                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2507         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2510 static bool
2511 draw_view_line(struct view *view, unsigned int lineno)
2513         struct line *line;
2514         bool selected = (view->offset + lineno == view->lineno);
2516         assert(view_is_displayed(view));
2518         if (view->offset + lineno >= view->lines)
2519                 return FALSE;
2521         line = &view->line[view->offset + lineno];
2523         wmove(view->win, lineno, 0);
2524         if (line->cleareol)
2525                 wclrtoeol(view->win);
2526         view->col = 0;
2527         view->curline = line;
2528         view->curtype = LINE_NONE;
2529         line->selected = FALSE;
2530         line->dirty = line->cleareol = 0;
2532         if (selected) {
2533                 set_view_attr(view, LINE_CURSOR);
2534                 line->selected = TRUE;
2535                 view->ops->select(view, line);
2536         }
2538         return view->ops->draw(view, line, lineno);
2541 static void
2542 redraw_view_dirty(struct view *view)
2544         bool dirty = FALSE;
2545         int lineno;
2547         for (lineno = 0; lineno < view->height; lineno++) {
2548                 if (view->offset + lineno >= view->lines)
2549                         break;
2550                 if (!view->line[view->offset + lineno].dirty)
2551                         continue;
2552                 dirty = TRUE;
2553                 if (!draw_view_line(view, lineno))
2554                         break;
2555         }
2557         if (!dirty)
2558                 return;
2559         wnoutrefresh(view->win);
2562 static void
2563 redraw_view_from(struct view *view, int lineno)
2565         assert(0 <= lineno && lineno < view->height);
2567         for (; lineno < view->height; lineno++) {
2568                 if (!draw_view_line(view, lineno))
2569                         break;
2570         }
2572         wnoutrefresh(view->win);
2575 static void
2576 redraw_view(struct view *view)
2578         werase(view->win);
2579         redraw_view_from(view, 0);
2583 static void
2584 update_view_title(struct view *view)
2586         char buf[SIZEOF_STR];
2587         char state[SIZEOF_STR];
2588         size_t bufpos = 0, statelen = 0;
2590         assert(view_is_displayed(view));
2592         if (view->type != VIEW_STATUS && view->lines) {
2593                 unsigned int view_lines = view->offset + view->height;
2594                 unsigned int lines = view->lines
2595                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2596                                    : 0;
2598                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2599                                    view->ops->type,
2600                                    view->lineno + 1,
2601                                    view->lines,
2602                                    lines);
2604         }
2606         if (view->pipe) {
2607                 time_t secs = time(NULL) - view->start_time;
2609                 /* Three git seconds are a long time ... */
2610                 if (secs > 2)
2611                         string_format_from(state, &statelen, " loading %lds", secs);
2612         }
2614         string_format_from(buf, &bufpos, "[%s]", view->name);
2615         if (*view->ref && bufpos < view->width) {
2616                 size_t refsize = strlen(view->ref);
2617                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2619                 if (minsize < view->width)
2620                         refsize = view->width - minsize + 7;
2621                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2622         }
2624         if (statelen && bufpos < view->width) {
2625                 string_format_from(buf, &bufpos, "%s", state);
2626         }
2628         if (view == display[current_view])
2629                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2630         else
2631                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2633         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2634         wclrtoeol(view->title);
2635         wnoutrefresh(view->title);
2638 static int
2639 apply_step(double step, int value)
2641         if (step >= 1)
2642                 return (int) step;
2643         value *= step + 0.01;
2644         return value ? value : 1;
2647 static void
2648 resize_display(void)
2650         int offset, i;
2651         struct view *base = display[0];
2652         struct view *view = display[1] ? display[1] : display[0];
2654         /* Setup window dimensions */
2656         getmaxyx(stdscr, base->height, base->width);
2658         /* Make room for the status window. */
2659         base->height -= 1;
2661         if (view != base) {
2662                 /* Horizontal split. */
2663                 view->width   = base->width;
2664                 view->height  = apply_step(opt_scale_split_view, base->height);
2665                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2666                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2667                 base->height -= view->height;
2669                 /* Make room for the title bar. */
2670                 view->height -= 1;
2671         }
2673         /* Make room for the title bar. */
2674         base->height -= 1;
2676         offset = 0;
2678         foreach_displayed_view (view, i) {
2679                 if (!view->win) {
2680                         view->win = newwin(view->height, 0, offset, 0);
2681                         if (!view->win)
2682                                 die("Failed to create %s view", view->name);
2684                         scrollok(view->win, FALSE);
2686                         view->title = newwin(1, 0, offset + view->height, 0);
2687                         if (!view->title)
2688                                 die("Failed to create title window");
2690                 } else {
2691                         wresize(view->win, view->height, view->width);
2692                         mvwin(view->win,   offset, 0);
2693                         mvwin(view->title, offset + view->height, 0);
2694                 }
2696                 offset += view->height + 1;
2697         }
2700 static void
2701 redraw_display(bool clear)
2703         struct view *view;
2704         int i;
2706         foreach_displayed_view (view, i) {
2707                 if (clear)
2708                         wclear(view->win);
2709                 redraw_view(view);
2710                 update_view_title(view);
2711         }
2714 static void
2715 toggle_enum_option_do(unsigned int *opt, const char *help,
2716                       const struct enum_map *map, size_t size)
2718         *opt = (*opt + 1) % size;
2719         redraw_display(FALSE);
2720         report("Displaying %s %s", enum_name(map[*opt]), help);
2723 #define toggle_enum_option(opt, help, map) \
2724         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2726 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2727 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2729 static void
2730 toggle_view_option(bool *option, const char *help)
2732         *option = !*option;
2733         redraw_display(FALSE);
2734         report("%sabling %s", *option ? "En" : "Dis", help);
2737 static void
2738 open_option_menu(void)
2740         const struct menu_item menu[] = {
2741                 { '.', "line numbers", &opt_line_number },
2742                 { 'D', "date display", &opt_date },
2743                 { 'A', "author display", &opt_author },
2744                 { 'g', "revision graph display", &opt_rev_graph },
2745                 { 'F', "reference display", &opt_show_refs },
2746                 { 0 }
2747         };
2748         int selected = 0;
2750         if (prompt_menu("Toggle option", menu, &selected)) {
2751                 if (menu[selected].data == &opt_date)
2752                         toggle_date();
2753                 else if (menu[selected].data == &opt_author)
2754                         toggle_author();
2755                 else
2756                         toggle_view_option(menu[selected].data, menu[selected].text);
2757         }
2760 static void
2761 maximize_view(struct view *view)
2763         memset(display, 0, sizeof(display));
2764         current_view = 0;
2765         display[current_view] = view;
2766         resize_display();
2767         redraw_display(FALSE);
2768         report("");
2772 /*
2773  * Navigation
2774  */
2776 static bool
2777 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2779         if (lineno >= view->lines)
2780                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2782         if (offset > lineno || offset + view->height <= lineno) {
2783                 unsigned long half = view->height / 2;
2785                 if (lineno > half)
2786                         offset = lineno - half;
2787                 else
2788                         offset = 0;
2789         }
2791         if (offset != view->offset || lineno != view->lineno) {
2792                 view->offset = offset;
2793                 view->lineno = lineno;
2794                 return TRUE;
2795         }
2797         return FALSE;
2800 /* Scrolling backend */
2801 static void
2802 do_scroll_view(struct view *view, int lines)
2804         bool redraw_current_line = FALSE;
2806         /* The rendering expects the new offset. */
2807         view->offset += lines;
2809         assert(0 <= view->offset && view->offset < view->lines);
2810         assert(lines);
2812         /* Move current line into the view. */
2813         if (view->lineno < view->offset) {
2814                 view->lineno = view->offset;
2815                 redraw_current_line = TRUE;
2816         } else if (view->lineno >= view->offset + view->height) {
2817                 view->lineno = view->offset + view->height - 1;
2818                 redraw_current_line = TRUE;
2819         }
2821         assert(view->offset <= view->lineno && view->lineno < view->lines);
2823         /* Redraw the whole screen if scrolling is pointless. */
2824         if (view->height < ABS(lines)) {
2825                 redraw_view(view);
2827         } else {
2828                 int line = lines > 0 ? view->height - lines : 0;
2829                 int end = line + ABS(lines);
2831                 scrollok(view->win, TRUE);
2832                 wscrl(view->win, lines);
2833                 scrollok(view->win, FALSE);
2835                 while (line < end && draw_view_line(view, line))
2836                         line++;
2838                 if (redraw_current_line)
2839                         draw_view_line(view, view->lineno - view->offset);
2840                 wnoutrefresh(view->win);
2841         }
2843         view->has_scrolled = TRUE;
2844         report("");
2847 /* Scroll frontend */
2848 static void
2849 scroll_view(struct view *view, enum request request)
2851         int lines = 1;
2853         assert(view_is_displayed(view));
2855         switch (request) {
2856         case REQ_SCROLL_LEFT:
2857                 if (view->yoffset == 0) {
2858                         report("Cannot scroll beyond the first column");
2859                         return;
2860                 }
2861                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2862                         view->yoffset = 0;
2863                 else
2864                         view->yoffset -= apply_step(opt_hscroll, view->width);
2865                 redraw_view_from(view, 0);
2866                 report("");
2867                 return;
2868         case REQ_SCROLL_RIGHT:
2869                 view->yoffset += apply_step(opt_hscroll, view->width);
2870                 redraw_view(view);
2871                 report("");
2872                 return;
2873         case REQ_SCROLL_PAGE_DOWN:
2874                 lines = view->height;
2875         case REQ_SCROLL_LINE_DOWN:
2876                 if (view->offset + lines > view->lines)
2877                         lines = view->lines - view->offset;
2879                 if (lines == 0 || view->offset + view->height >= view->lines) {
2880                         report("Cannot scroll beyond the last line");
2881                         return;
2882                 }
2883                 break;
2885         case REQ_SCROLL_PAGE_UP:
2886                 lines = view->height;
2887         case REQ_SCROLL_LINE_UP:
2888                 if (lines > view->offset)
2889                         lines = view->offset;
2891                 if (lines == 0) {
2892                         report("Cannot scroll beyond the first line");
2893                         return;
2894                 }
2896                 lines = -lines;
2897                 break;
2899         default:
2900                 die("request %d not handled in switch", request);
2901         }
2903         do_scroll_view(view, lines);
2906 /* Cursor moving */
2907 static void
2908 move_view(struct view *view, enum request request)
2910         int scroll_steps = 0;
2911         int steps;
2913         switch (request) {
2914         case REQ_MOVE_FIRST_LINE:
2915                 steps = -view->lineno;
2916                 break;
2918         case REQ_MOVE_LAST_LINE:
2919                 steps = view->lines - view->lineno - 1;
2920                 break;
2922         case REQ_MOVE_PAGE_UP:
2923                 steps = view->height > view->lineno
2924                       ? -view->lineno : -view->height;
2925                 break;
2927         case REQ_MOVE_PAGE_DOWN:
2928                 steps = view->lineno + view->height >= view->lines
2929                       ? view->lines - view->lineno - 1 : view->height;
2930                 break;
2932         case REQ_MOVE_UP:
2933                 steps = -1;
2934                 break;
2936         case REQ_MOVE_DOWN:
2937                 steps = 1;
2938                 break;
2940         default:
2941                 die("request %d not handled in switch", request);
2942         }
2944         if (steps <= 0 && view->lineno == 0) {
2945                 report("Cannot move beyond the first line");
2946                 return;
2948         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2949                 report("Cannot move beyond the last line");
2950                 return;
2951         }
2953         /* Move the current line */
2954         view->lineno += steps;
2955         assert(0 <= view->lineno && view->lineno < view->lines);
2957         /* Check whether the view needs to be scrolled */
2958         if (view->lineno < view->offset ||
2959             view->lineno >= view->offset + view->height) {
2960                 scroll_steps = steps;
2961                 if (steps < 0 && -steps > view->offset) {
2962                         scroll_steps = -view->offset;
2964                 } else if (steps > 0) {
2965                         if (view->lineno == view->lines - 1 &&
2966                             view->lines > view->height) {
2967                                 scroll_steps = view->lines - view->offset - 1;
2968                                 if (scroll_steps >= view->height)
2969                                         scroll_steps -= view->height - 1;
2970                         }
2971                 }
2972         }
2974         if (!view_is_displayed(view)) {
2975                 view->offset += scroll_steps;
2976                 assert(0 <= view->offset && view->offset < view->lines);
2977                 view->ops->select(view, &view->line[view->lineno]);
2978                 return;
2979         }
2981         /* Repaint the old "current" line if we be scrolling */
2982         if (ABS(steps) < view->height)
2983                 draw_view_line(view, view->lineno - steps - view->offset);
2985         if (scroll_steps) {
2986                 do_scroll_view(view, scroll_steps);
2987                 return;
2988         }
2990         /* Draw the current line */
2991         draw_view_line(view, view->lineno - view->offset);
2993         wnoutrefresh(view->win);
2994         report("");
2998 /*
2999  * Searching
3000  */
3002 static void search_view(struct view *view, enum request request);
3004 static bool
3005 grep_text(struct view *view, const char *text[])
3007         regmatch_t pmatch;
3008         size_t i;
3010         for (i = 0; text[i]; i++)
3011                 if (*text[i] &&
3012                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3013                         return TRUE;
3014         return FALSE;
3017 static void
3018 select_view_line(struct view *view, unsigned long lineno)
3020         unsigned long old_lineno = view->lineno;
3021         unsigned long old_offset = view->offset;
3023         if (goto_view_line(view, view->offset, lineno)) {
3024                 if (view_is_displayed(view)) {
3025                         if (old_offset != view->offset) {
3026                                 redraw_view(view);
3027                         } else {
3028                                 draw_view_line(view, old_lineno - view->offset);
3029                                 draw_view_line(view, view->lineno - view->offset);
3030                                 wnoutrefresh(view->win);
3031                         }
3032                 } else {
3033                         view->ops->select(view, &view->line[view->lineno]);
3034                 }
3035         }
3038 static void
3039 find_next(struct view *view, enum request request)
3041         unsigned long lineno = view->lineno;
3042         int direction;
3044         if (!*view->grep) {
3045                 if (!*opt_search)
3046                         report("No previous search");
3047                 else
3048                         search_view(view, request);
3049                 return;
3050         }
3052         switch (request) {
3053         case REQ_SEARCH:
3054         case REQ_FIND_NEXT:
3055                 direction = 1;
3056                 break;
3058         case REQ_SEARCH_BACK:
3059         case REQ_FIND_PREV:
3060                 direction = -1;
3061                 break;
3063         default:
3064                 return;
3065         }
3067         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3068                 lineno += direction;
3070         /* Note, lineno is unsigned long so will wrap around in which case it
3071          * will become bigger than view->lines. */
3072         for (; lineno < view->lines; lineno += direction) {
3073                 if (view->ops->grep(view, &view->line[lineno])) {
3074                         select_view_line(view, lineno);
3075                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3076                         return;
3077                 }
3078         }
3080         report("No match found for '%s'", view->grep);
3083 static void
3084 search_view(struct view *view, enum request request)
3086         int regex_err;
3088         if (view->regex) {
3089                 regfree(view->regex);
3090                 *view->grep = 0;
3091         } else {
3092                 view->regex = calloc(1, sizeof(*view->regex));
3093                 if (!view->regex)
3094                         return;
3095         }
3097         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3098         if (regex_err != 0) {
3099                 char buf[SIZEOF_STR] = "unknown error";
3101                 regerror(regex_err, view->regex, buf, sizeof(buf));
3102                 report("Search failed: %s", buf);
3103                 return;
3104         }
3106         string_copy(view->grep, opt_search);
3108         find_next(view, request);
3111 /*
3112  * Incremental updating
3113  */
3115 static void
3116 reset_view(struct view *view)
3118         int i;
3120         for (i = 0; i < view->lines; i++)
3121                 free(view->line[i].data);
3122         free(view->line);
3124         view->p_offset = view->offset;
3125         view->p_yoffset = view->yoffset;
3126         view->p_lineno = view->lineno;
3128         view->line = NULL;
3129         view->offset = 0;
3130         view->yoffset = 0;
3131         view->lines  = 0;
3132         view->lineno = 0;
3133         view->vid[0] = 0;
3134         view->update_secs = 0;
3137 static void
3138 free_argv(const char *argv[])
3140         int argc;
3142         for (argc = 0; argv[argc]; argc++)
3143                 free((void *) argv[argc]);
3146 static const char *
3147 format_arg(const char *name)
3149         static struct {
3150                 const char *name;
3151                 size_t namelen;
3152                 const char *value;
3153                 const char *value_if_empty;
3154         } vars[] = {
3155 #define FORMAT_VAR(name, value, value_if_empty) \
3156         { name, STRING_SIZE(name), value, value_if_empty }
3157                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3158                 FORMAT_VAR("%(file)",           opt_file,       ""),
3159                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3160                 FORMAT_VAR("%(head)",           ref_head,       ""),
3161                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3162                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3163                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3164         };
3165         int i;
3167         for (i = 0; i < ARRAY_SIZE(vars); i++)
3168                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3169                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3171         report("Unknown replacement: `%s`", name);
3172         return NULL;
3175 static bool
3176 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3178         char buf[SIZEOF_STR];
3179         int argc;
3180         bool noreplace = flags == FORMAT_NONE;
3182         free_argv(dst_argv);
3184         for (argc = 0; src_argv[argc]; argc++) {
3185                 const char *arg = src_argv[argc];
3186                 size_t bufpos = 0;
3188                 while (arg) {
3189                         char *next = strstr(arg, "%(");
3190                         int len = next - arg;
3191                         const char *value;
3193                         if (!next || noreplace) {
3194                                 len = strlen(arg);
3195                                 value = "";
3197                         } else {
3198                                 value = format_arg(next);
3200                                 if (!value) {
3201                                         return FALSE;
3202                                 }
3203                         }
3205                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3206                                 return FALSE;
3208                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3209                 }
3211                 dst_argv[argc] = strdup(buf);
3212                 if (!dst_argv[argc])
3213                         break;
3214         }
3216         dst_argv[argc] = NULL;
3218         return src_argv[argc] == NULL;
3221 static bool
3222 restore_view_position(struct view *view)
3224         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3225                 return FALSE;
3227         /* Changing the view position cancels the restoring. */
3228         /* FIXME: Changing back to the first line is not detected. */
3229         if (view->offset != 0 || view->lineno != 0) {
3230                 view->p_restore = FALSE;
3231                 return FALSE;
3232         }
3234         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3235             view_is_displayed(view))
3236                 werase(view->win);
3238         view->yoffset = view->p_yoffset;
3239         view->p_restore = FALSE;
3241         return TRUE;
3244 static void
3245 end_update(struct view *view, bool force)
3247         if (!view->pipe)
3248                 return;
3249         while (!view->ops->read(view, NULL))
3250                 if (!force)
3251                         return;
3252         if (force)
3253                 io_kill(view->pipe);
3254         io_done(view->pipe);
3255         view->pipe = NULL;
3258 static void
3259 setup_update(struct view *view, const char *vid)
3261         reset_view(view);
3262         string_copy_rev(view->vid, vid);
3263         view->pipe = &view->io;
3264         view->start_time = time(NULL);
3267 static bool
3268 prepare_update(struct view *view, const char *argv[], const char *dir)
3270         if (view->pipe)
3271                 end_update(view, TRUE);
3272         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3275 static bool
3276 prepare_update_file(struct view *view, const char *name)
3278         if (view->pipe)
3279                 end_update(view, TRUE);
3280         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3283 static bool
3284 begin_update(struct view *view, bool refresh)
3286         if (view->pipe)
3287                 end_update(view, TRUE);
3289         if (!refresh) {
3290                 if (view->ops->prepare) {
3291                         if (!view->ops->prepare(view))
3292                                 return FALSE;
3293                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3294                         return FALSE;
3295                 }
3297                 /* Put the current ref_* value to the view title ref
3298                  * member. This is needed by the blob view. Most other
3299                  * views sets it automatically after loading because the
3300                  * first line is a commit line. */
3301                 string_copy_rev(view->ref, view->id);
3302         }
3304         if (!io_start(&view->io))
3305                 return FALSE;
3307         setup_update(view, view->id);
3309         return TRUE;
3312 static bool
3313 update_view(struct view *view)
3315         char out_buffer[BUFSIZ * 2];
3316         char *line;
3317         /* Clear the view and redraw everything since the tree sorting
3318          * might have rearranged things. */
3319         bool redraw = view->lines == 0;
3320         bool can_read = TRUE;
3322         if (!view->pipe)
3323                 return TRUE;
3325         if (!io_can_read(view->pipe)) {
3326                 if (view->lines == 0 && view_is_displayed(view)) {
3327                         time_t secs = time(NULL) - view->start_time;
3329                         if (secs > 1 && secs > view->update_secs) {
3330                                 if (view->update_secs == 0)
3331                                         redraw_view(view);
3332                                 update_view_title(view);
3333                                 view->update_secs = secs;
3334                         }
3335                 }
3336                 return TRUE;
3337         }
3339         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3340                 if (opt_iconv_in != ICONV_NONE) {
3341                         ICONV_CONST char *inbuf = line;
3342                         size_t inlen = strlen(line) + 1;
3344                         char *outbuf = out_buffer;
3345                         size_t outlen = sizeof(out_buffer);
3347                         size_t ret;
3349                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3350                         if (ret != (size_t) -1)
3351                                 line = out_buffer;
3352                 }
3354                 if (!view->ops->read(view, line)) {
3355                         report("Allocation failure");
3356                         end_update(view, TRUE);
3357                         return FALSE;
3358                 }
3359         }
3361         {
3362                 unsigned long lines = view->lines;
3363                 int digits;
3365                 for (digits = 0; lines; digits++)
3366                         lines /= 10;
3368                 /* Keep the displayed view in sync with line number scaling. */
3369                 if (digits != view->digits) {
3370                         view->digits = digits;
3371                         if (opt_line_number || view->type == VIEW_BLAME)
3372                                 redraw = TRUE;
3373                 }
3374         }
3376         if (io_error(view->pipe)) {
3377                 report("Failed to read: %s", io_strerror(view->pipe));
3378                 end_update(view, TRUE);
3380         } else if (io_eof(view->pipe)) {
3381                 report("");
3382                 end_update(view, FALSE);
3383         }
3385         if (restore_view_position(view))
3386                 redraw = TRUE;
3388         if (!view_is_displayed(view))
3389                 return TRUE;
3391         if (redraw)
3392                 redraw_view_from(view, 0);
3393         else
3394                 redraw_view_dirty(view);
3396         /* Update the title _after_ the redraw so that if the redraw picks up a
3397          * commit reference in view->ref it'll be available here. */
3398         update_view_title(view);
3399         return TRUE;
3402 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3404 static struct line *
3405 add_line_data(struct view *view, void *data, enum line_type type)
3407         struct line *line;
3409         if (!realloc_lines(&view->line, view->lines, 1))
3410                 return NULL;
3412         line = &view->line[view->lines++];
3413         memset(line, 0, sizeof(*line));
3414         line->type = type;
3415         line->data = data;
3416         line->dirty = 1;
3418         return line;
3421 static struct line *
3422 add_line_text(struct view *view, const char *text, enum line_type type)
3424         char *data = text ? strdup(text) : NULL;
3426         return data ? add_line_data(view, data, type) : NULL;
3429 static struct line *
3430 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3432         char buf[SIZEOF_STR];
3433         va_list args;
3435         va_start(args, fmt);
3436         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3437                 buf[0] = 0;
3438         va_end(args);
3440         return buf[0] ? add_line_text(view, buf, type) : NULL;
3443 /*
3444  * View opening
3445  */
3447 enum open_flags {
3448         OPEN_DEFAULT = 0,       /* Use default view switching. */
3449         OPEN_SPLIT = 1,         /* Split current view. */
3450         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3451         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3452         OPEN_PREPARED = 32,     /* Open already prepared command. */
3453 };
3455 static void
3456 open_view(struct view *prev, enum request request, enum open_flags flags)
3458         bool split = !!(flags & OPEN_SPLIT);
3459         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3460         bool nomaximize = !!(flags & OPEN_REFRESH);
3461         struct view *view = VIEW(request);
3462         int nviews = displayed_views();
3463         struct view *base_view = display[0];
3465         if (view == prev && nviews == 1 && !reload) {
3466                 report("Already in %s view", view->name);
3467                 return;
3468         }
3470         if (view->git_dir && !opt_git_dir[0]) {
3471                 report("The %s view is disabled in pager view", view->name);
3472                 return;
3473         }
3475         if (split) {
3476                 display[1] = view;
3477                 current_view = 1;
3478         } else if (!nomaximize) {
3479                 /* Maximize the current view. */
3480                 memset(display, 0, sizeof(display));
3481                 current_view = 0;
3482                 display[current_view] = view;
3483         }
3485         /* No parent signals that this is the first loaded view. */
3486         if (prev && view != prev) {
3487                 view->parent = prev;
3488         }
3490         /* Resize the view when switching between split- and full-screen,
3491          * or when switching between two different full-screen views. */
3492         if (nviews != displayed_views() ||
3493             (nviews == 1 && base_view != display[0]))
3494                 resize_display();
3496         if (view->ops->open) {
3497                 if (view->pipe)
3498                         end_update(view, TRUE);
3499                 if (!view->ops->open(view)) {
3500                         report("Failed to load %s view", view->name);
3501                         return;
3502                 }
3503                 restore_view_position(view);
3505         } else if ((reload || strcmp(view->vid, view->id)) &&
3506                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3507                 report("Failed to load %s view", view->name);
3508                 return;
3509         }
3511         if (split && prev->lineno - prev->offset >= prev->height) {
3512                 /* Take the title line into account. */
3513                 int lines = prev->lineno - prev->offset - prev->height + 1;
3515                 /* Scroll the view that was split if the current line is
3516                  * outside the new limited view. */
3517                 do_scroll_view(prev, lines);
3518         }
3520         if (prev && view != prev && split && view_is_displayed(prev)) {
3521                 /* "Blur" the previous view. */
3522                 update_view_title(prev);
3523         }
3525         if (view->pipe && view->lines == 0) {
3526                 /* Clear the old view and let the incremental updating refill
3527                  * the screen. */
3528                 werase(view->win);
3529                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3530                 report("");
3531         } else if (view_is_displayed(view)) {
3532                 redraw_view(view);
3533                 report("");
3534         }
3537 static void
3538 open_external_viewer(const char *argv[], const char *dir)
3540         def_prog_mode();           /* save current tty modes */
3541         endwin();                  /* restore original tty modes */
3542         io_run_fg(argv, dir);
3543         fprintf(stderr, "Press Enter to continue");
3544         getc(opt_tty);
3545         reset_prog_mode();
3546         redraw_display(TRUE);
3549 static void
3550 open_mergetool(const char *file)
3552         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3554         open_external_viewer(mergetool_argv, opt_cdup);
3557 static void
3558 open_editor(const char *file)
3560         const char *editor_argv[] = { "vi", file, NULL };
3561         const char *editor;
3563         editor = getenv("GIT_EDITOR");
3564         if (!editor && *opt_editor)
3565                 editor = opt_editor;
3566         if (!editor)
3567                 editor = getenv("VISUAL");
3568         if (!editor)
3569                 editor = getenv("EDITOR");
3570         if (!editor)
3571                 editor = "vi";
3573         editor_argv[0] = editor;
3574         open_external_viewer(editor_argv, opt_cdup);
3577 static void
3578 open_run_request(enum request request)
3580         struct run_request *req = get_run_request(request);
3581         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3583         if (!req) {
3584                 report("Unknown run request");
3585                 return;
3586         }
3588         if (format_argv(argv, req->argv, FORMAT_ALL))
3589                 open_external_viewer(argv, NULL);
3590         free_argv(argv);
3593 /*
3594  * User request switch noodle
3595  */
3597 static int
3598 view_driver(struct view *view, enum request request)
3600         int i;
3602         if (request == REQ_NONE)
3603                 return TRUE;
3605         if (request > REQ_NONE) {
3606                 open_run_request(request);
3607                 /* FIXME: When all views can refresh always do this. */
3608                 if (view->refresh)
3609                         request = REQ_REFRESH;
3610                 else
3611                         return TRUE;
3612         }
3614         if (view && view->lines) {
3615                 request = view->ops->request(view, request, &view->line[view->lineno]);
3616                 if (request == REQ_NONE)
3617                         return TRUE;
3618         }
3620         switch (request) {
3621         case REQ_MOVE_UP:
3622         case REQ_MOVE_DOWN:
3623         case REQ_MOVE_PAGE_UP:
3624         case REQ_MOVE_PAGE_DOWN:
3625         case REQ_MOVE_FIRST_LINE:
3626         case REQ_MOVE_LAST_LINE:
3627                 move_view(view, request);
3628                 break;
3630         case REQ_SCROLL_LEFT:
3631         case REQ_SCROLL_RIGHT:
3632         case REQ_SCROLL_LINE_DOWN:
3633         case REQ_SCROLL_LINE_UP:
3634         case REQ_SCROLL_PAGE_DOWN:
3635         case REQ_SCROLL_PAGE_UP:
3636                 scroll_view(view, request);
3637                 break;
3639         case REQ_VIEW_BLAME:
3640                 if (!opt_file[0]) {
3641                         report("No file chosen, press %s to open tree view",
3642                                get_key(view->keymap, REQ_VIEW_TREE));
3643                         break;
3644                 }
3645                 open_view(view, request, OPEN_DEFAULT);
3646                 break;
3648         case REQ_VIEW_BLOB:
3649                 if (!ref_blob[0]) {
3650                         report("No file chosen, press %s to open tree view",
3651                                get_key(view->keymap, REQ_VIEW_TREE));
3652                         break;
3653                 }
3654                 open_view(view, request, OPEN_DEFAULT);
3655                 break;
3657         case REQ_VIEW_PAGER:
3658                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3659                         report("No pager content, press %s to run command from prompt",
3660                                get_key(view->keymap, REQ_PROMPT));
3661                         break;
3662                 }
3663                 open_view(view, request, OPEN_DEFAULT);
3664                 break;
3666         case REQ_VIEW_STAGE:
3667                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3668                         report("No stage content, press %s to open the status view and choose file",
3669                                get_key(view->keymap, REQ_VIEW_STATUS));
3670                         break;
3671                 }
3672                 open_view(view, request, OPEN_DEFAULT);
3673                 break;
3675         case REQ_VIEW_STATUS:
3676                 if (opt_is_inside_work_tree == FALSE) {
3677                         report("The status view requires a working tree");
3678                         break;
3679                 }
3680                 open_view(view, request, OPEN_DEFAULT);
3681                 break;
3683         case REQ_VIEW_MAIN:
3684         case REQ_VIEW_DIFF:
3685         case REQ_VIEW_LOG:
3686         case REQ_VIEW_TREE:
3687         case REQ_VIEW_HELP:
3688         case REQ_VIEW_BRANCH:
3689                 open_view(view, request, OPEN_DEFAULT);
3690                 break;
3692         case REQ_NEXT:
3693         case REQ_PREVIOUS:
3694                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3696                 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3697                     view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3698                     view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3699                     view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3700                     view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3701                         int line;
3703                         view = view->parent;
3704                         line = view->lineno;
3705                         move_view(view, request);
3706                         if (view_is_displayed(view))
3707                                 update_view_title(view);
3708                         if (line != view->lineno)
3709                                 view->ops->request(view, REQ_ENTER,
3710                                                    &view->line[view->lineno]);
3712                 } else {
3713                         move_view(view, request);
3714                 }
3715                 break;
3717         case REQ_VIEW_NEXT:
3718         {
3719                 int nviews = displayed_views();
3720                 int next_view = (current_view + 1) % nviews;
3722                 if (next_view == current_view) {
3723                         report("Only one view is displayed");
3724                         break;
3725                 }
3727                 current_view = next_view;
3728                 /* Blur out the title of the previous view. */
3729                 update_view_title(view);
3730                 report("");
3731                 break;
3732         }
3733         case REQ_REFRESH:
3734                 report("Refreshing is not yet supported for the %s view", view->name);
3735                 break;
3737         case REQ_MAXIMIZE:
3738                 if (displayed_views() == 2)
3739                         maximize_view(view);
3740                 break;
3742         case REQ_OPTIONS:
3743                 open_option_menu();
3744                 break;
3746         case REQ_TOGGLE_LINENO:
3747                 toggle_view_option(&opt_line_number, "line numbers");
3748                 break;
3750         case REQ_TOGGLE_DATE:
3751                 toggle_date();
3752                 break;
3754         case REQ_TOGGLE_AUTHOR:
3755                 toggle_author();
3756                 break;
3758         case REQ_TOGGLE_REV_GRAPH:
3759                 toggle_view_option(&opt_rev_graph, "revision graph display");
3760                 break;
3762         case REQ_TOGGLE_REFS:
3763                 toggle_view_option(&opt_show_refs, "reference display");
3764                 break;
3766         case REQ_TOGGLE_SORT_FIELD:
3767         case REQ_TOGGLE_SORT_ORDER:
3768                 report("Sorting is not yet supported for the %s view", view->name);
3769                 break;
3771         case REQ_SEARCH:
3772         case REQ_SEARCH_BACK:
3773                 search_view(view, request);
3774                 break;
3776         case REQ_FIND_NEXT:
3777         case REQ_FIND_PREV:
3778                 find_next(view, request);
3779                 break;
3781         case REQ_STOP_LOADING:
3782                 foreach_view(view, i) {
3783                         if (view->pipe)
3784                                 report("Stopped loading the %s view", view->name),
3785                         end_update(view, TRUE);
3786                 }
3787                 break;
3789         case REQ_SHOW_VERSION:
3790                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3791                 return TRUE;
3793         case REQ_SCREEN_REDRAW:
3794                 redraw_display(TRUE);
3795                 break;
3797         case REQ_EDIT:
3798                 report("Nothing to edit");
3799                 break;
3801         case REQ_ENTER:
3802                 report("Nothing to enter");
3803                 break;
3805         case REQ_VIEW_CLOSE:
3806                 /* XXX: Mark closed views by letting view->parent point to the
3807                  * view itself. Parents to closed view should never be
3808                  * followed. */
3809                 if (view->parent &&
3810                     view->parent->parent != view->parent) {
3811                         maximize_view(view->parent);
3812                         view->parent = view;
3813                         break;
3814                 }
3815                 /* Fall-through */
3816         case REQ_QUIT:
3817                 return FALSE;
3819         default:
3820                 report("Unknown key, press %s for help",
3821                        get_key(view->keymap, REQ_VIEW_HELP));
3822                 return TRUE;
3823         }
3825         return TRUE;
3829 /*
3830  * View backend utilities
3831  */
3833 enum sort_field {
3834         ORDERBY_NAME,
3835         ORDERBY_DATE,
3836         ORDERBY_AUTHOR,
3837 };
3839 struct sort_state {
3840         const enum sort_field *fields;
3841         size_t size, current;
3842         bool reverse;
3843 };
3845 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3846 #define get_sort_field(state) ((state).fields[(state).current])
3847 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3849 static void
3850 sort_view(struct view *view, enum request request, struct sort_state *state,
3851           int (*compare)(const void *, const void *))
3853         switch (request) {
3854         case REQ_TOGGLE_SORT_FIELD:
3855                 state->current = (state->current + 1) % state->size;
3856                 break;
3858         case REQ_TOGGLE_SORT_ORDER:
3859                 state->reverse = !state->reverse;
3860                 break;
3861         default:
3862                 die("Not a sort request");
3863         }
3865         qsort(view->line, view->lines, sizeof(*view->line), compare);
3866         redraw_view(view);
3869 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3871 /* Small author cache to reduce memory consumption. It uses binary
3872  * search to lookup or find place to position new entries. No entries
3873  * are ever freed. */
3874 static const char *
3875 get_author(const char *name)
3877         static const char **authors;
3878         static size_t authors_size;
3879         int from = 0, to = authors_size - 1;
3881         while (from <= to) {
3882                 size_t pos = (to + from) / 2;
3883                 int cmp = strcmp(name, authors[pos]);
3885                 if (!cmp)
3886                         return authors[pos];
3888                 if (cmp < 0)
3889                         to = pos - 1;
3890                 else
3891                         from = pos + 1;
3892         }
3894         if (!realloc_authors(&authors, authors_size, 1))
3895                 return NULL;
3896         name = strdup(name);
3897         if (!name)
3898                 return NULL;
3900         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3901         authors[from] = name;
3902         authors_size++;
3904         return name;
3907 static void
3908 parse_timesec(struct time *time, const char *sec)
3910         time->sec = (time_t) atol(sec);
3913 static void
3914 parse_timezone(struct time *time, const char *zone)
3916         long tz;
3918         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3919         tz += ('0' - zone[2]) * 60 * 60;
3920         tz += ('0' - zone[3]) * 60 * 10;
3921         tz += ('0' - zone[4]) * 60;
3923         if (zone[0] == '-')
3924                 tz = -tz;
3926         time->tz = tz;
3927         time->sec -= tz;
3930 /* Parse author lines where the name may be empty:
3931  *      author  <email@address.tld> 1138474660 +0100
3932  */
3933 static void
3934 parse_author_line(char *ident, const char **author, struct time *time)
3936         char *nameend = strchr(ident, '<');
3937         char *emailend = strchr(ident, '>');
3939         if (nameend && emailend)
3940                 *nameend = *emailend = 0;
3941         ident = chomp_string(ident);
3942         if (!*ident) {
3943                 if (nameend)
3944                         ident = chomp_string(nameend + 1);
3945                 if (!*ident)
3946                         ident = "Unknown";
3947         }
3949         *author = get_author(ident);
3951         /* Parse epoch and timezone */
3952         if (emailend && emailend[1] == ' ') {
3953                 char *secs = emailend + 2;
3954                 char *zone = strchr(secs, ' ');
3956                 parse_timesec(time, secs);
3958                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3959                         parse_timezone(time, zone + 1);
3960         }
3963 static bool
3964 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3966         char rev[SIZEOF_REV];
3967         const char *revlist_argv[] = {
3968                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3969         };
3970         struct menu_item *items;
3971         char text[SIZEOF_STR];
3972         bool ok = TRUE;
3973         int i;
3975         items = calloc(*parents + 1, sizeof(*items));
3976         if (!items)
3977                 return FALSE;
3979         for (i = 0; i < *parents; i++) {
3980                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3981                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3982                     !(items[i].text = strdup(text))) {
3983                         ok = FALSE;
3984                         break;
3985                 }
3986         }
3988         if (ok) {
3989                 *parents = 0;
3990                 ok = prompt_menu("Select parent", items, parents);
3991         }
3992         for (i = 0; items[i].text; i++)
3993                 free((char *) items[i].text);
3994         free(items);
3995         return ok;
3998 static bool
3999 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4001         char buf[SIZEOF_STR * 4];
4002         const char *revlist_argv[] = {
4003                 "git", "log", "--no-color", "-1",
4004                         "--pretty=format:%P", id, "--", path, NULL
4005         };
4006         int parents;
4008         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4009             (parents = strlen(buf) / 40) < 0) {
4010                 report("Failed to get parent information");
4011                 return FALSE;
4013         } else if (parents == 0) {
4014                 if (path)
4015                         report("Path '%s' does not exist in the parent", path);
4016                 else
4017                         report("The selected commit has no parents");
4018                 return FALSE;
4019         }
4021         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
4022                 return FALSE;
4024         string_copy_rev(rev, &buf[41 * parents]);
4025         return TRUE;
4028 /*
4029  * Pager backend
4030  */
4032 static bool
4033 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4035         char text[SIZEOF_STR];
4037         if (opt_line_number && draw_lineno(view, lineno))
4038                 return TRUE;
4040         string_expand(text, sizeof(text), line->data, opt_tab_size);
4041         draw_text(view, line->type, text, TRUE);
4042         return TRUE;
4045 static bool
4046 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4048         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4049         char ref[SIZEOF_STR];
4051         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4052                 return TRUE;
4054         /* This is the only fatal call, since it can "corrupt" the buffer. */
4055         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4056                 return FALSE;
4058         return TRUE;
4061 static void
4062 add_pager_refs(struct view *view, struct line *line)
4064         char buf[SIZEOF_STR];
4065         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4066         struct ref_list *list;
4067         size_t bufpos = 0, i;
4068         const char *sep = "Refs: ";
4069         bool is_tag = FALSE;
4071         assert(line->type == LINE_COMMIT);
4073         list = get_ref_list(commit_id);
4074         if (!list) {
4075                 if (view->type == VIEW_DIFF)
4076                         goto try_add_describe_ref;
4077                 return;
4078         }
4080         for (i = 0; i < list->size; i++) {
4081                 struct ref *ref = list->refs[i];
4082                 const char *fmt = ref->tag    ? "%s[%s]" :
4083                                   ref->remote ? "%s<%s>" : "%s%s";
4085                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4086                         return;
4087                 sep = ", ";
4088                 if (ref->tag)
4089                         is_tag = TRUE;
4090         }
4092         if (!is_tag && view->type == VIEW_DIFF) {
4093 try_add_describe_ref:
4094                 /* Add <tag>-g<commit_id> "fake" reference. */
4095                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4096                         return;
4097         }
4099         if (bufpos == 0)
4100                 return;
4102         add_line_text(view, buf, LINE_PP_REFS);
4105 static bool
4106 pager_read(struct view *view, char *data)
4108         struct line *line;
4110         if (!data)
4111                 return TRUE;
4113         line = add_line_text(view, data, get_line_type(data));
4114         if (!line)
4115                 return FALSE;
4117         if (line->type == LINE_COMMIT &&
4118             (view->type == VIEW_DIFF ||
4119              view->type == VIEW_LOG))
4120                 add_pager_refs(view, line);
4122         return TRUE;
4125 static enum request
4126 pager_request(struct view *view, enum request request, struct line *line)
4128         int split = 0;
4130         if (request != REQ_ENTER)
4131                 return request;
4133         if (line->type == LINE_COMMIT &&
4134            (view->type == VIEW_LOG ||
4135             view->type == VIEW_PAGER)) {
4136                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4137                 split = 1;
4138         }
4140         /* Always scroll the view even if it was split. That way
4141          * you can use Enter to scroll through the log view and
4142          * split open each commit diff. */
4143         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4145         /* FIXME: A minor workaround. Scrolling the view will call report("")
4146          * but if we are scrolling a non-current view this won't properly
4147          * update the view title. */
4148         if (split)
4149                 update_view_title(view);
4151         return REQ_NONE;
4154 static bool
4155 pager_grep(struct view *view, struct line *line)
4157         const char *text[] = { line->data, NULL };
4159         return grep_text(view, text);
4162 static void
4163 pager_select(struct view *view, struct line *line)
4165         if (line->type == LINE_COMMIT) {
4166                 char *text = (char *)line->data + STRING_SIZE("commit ");
4168                 if (view->type != VIEW_PAGER)
4169                         string_copy_rev(view->ref, text);
4170                 string_copy_rev(ref_commit, text);
4171         }
4174 static struct view_ops pager_ops = {
4175         "line",
4176         NULL,
4177         NULL,
4178         pager_read,
4179         pager_draw,
4180         pager_request,
4181         pager_grep,
4182         pager_select,
4183 };
4185 static const char *log_argv[SIZEOF_ARG] = {
4186         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4187 };
4189 static enum request
4190 log_request(struct view *view, enum request request, struct line *line)
4192         switch (request) {
4193         case REQ_REFRESH:
4194                 load_refs();
4195                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4196                 return REQ_NONE;
4197         default:
4198                 return pager_request(view, request, line);
4199         }
4202 static struct view_ops log_ops = {
4203         "line",
4204         log_argv,
4205         NULL,
4206         pager_read,
4207         pager_draw,
4208         log_request,
4209         pager_grep,
4210         pager_select,
4211 };
4213 static const char *diff_argv[SIZEOF_ARG] = {
4214         "git", "show", "--pretty=fuller", "--no-color", "--root",
4215                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4216 };
4218 static struct view_ops diff_ops = {
4219         "line",
4220         diff_argv,
4221         NULL,
4222         pager_read,
4223         pager_draw,
4224         pager_request,
4225         pager_grep,
4226         pager_select,
4227 };
4229 /*
4230  * Help backend
4231  */
4233 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4235 static bool
4236 help_open_keymap_title(struct view *view, enum keymap keymap)
4238         struct line *line;
4240         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4241                                help_keymap_hidden[keymap] ? '+' : '-',
4242                                enum_name(keymap_table[keymap]));
4243         if (line)
4244                 line->other = keymap;
4246         return help_keymap_hidden[keymap];
4249 static void
4250 help_open_keymap(struct view *view, enum keymap keymap)
4252         const char *group = NULL;
4253         char buf[SIZEOF_STR];
4254         size_t bufpos;
4255         bool add_title = TRUE;
4256         int i;
4258         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4259                 const char *key = NULL;
4261                 if (req_info[i].request == REQ_NONE)
4262                         continue;
4264                 if (!req_info[i].request) {
4265                         group = req_info[i].help;
4266                         continue;
4267                 }
4269                 key = get_keys(keymap, req_info[i].request, TRUE);
4270                 if (!key || !*key)
4271                         continue;
4273                 if (add_title && help_open_keymap_title(view, keymap))
4274                         return;
4275                 add_title = FALSE;
4277                 if (group) {
4278                         add_line_text(view, group, LINE_HELP_GROUP);
4279                         group = NULL;
4280                 }
4282                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4283                                 enum_name(req_info[i]), req_info[i].help);
4284         }
4286         group = "External commands:";
4288         for (i = 0; i < run_requests; i++) {
4289                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4290                 const char *key;
4291                 int argc;
4293                 if (!req || req->keymap != keymap)
4294                         continue;
4296                 key = get_key_name(req->key);
4297                 if (!*key)
4298                         key = "(no key defined)";
4300                 if (add_title && help_open_keymap_title(view, keymap))
4301                         return;
4302                 if (group) {
4303                         add_line_text(view, group, LINE_HELP_GROUP);
4304                         group = NULL;
4305                 }
4307                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4308                         if (!string_format_from(buf, &bufpos, "%s%s",
4309                                                 argc ? " " : "", req->argv[argc]))
4310                                 return;
4312                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4313         }
4316 static bool
4317 help_open(struct view *view)
4319         enum keymap keymap;
4321         reset_view(view);
4322         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4323         add_line_text(view, "", LINE_DEFAULT);
4325         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4326                 help_open_keymap(view, keymap);
4328         return TRUE;
4331 static enum request
4332 help_request(struct view *view, enum request request, struct line *line)
4334         switch (request) {
4335         case REQ_ENTER:
4336                 if (line->type == LINE_HELP_KEYMAP) {
4337                         help_keymap_hidden[line->other] =
4338                                 !help_keymap_hidden[line->other];
4339                         view->p_restore = TRUE;
4340                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4341                 }
4343                 return REQ_NONE;
4344         default:
4345                 return pager_request(view, request, line);
4346         }
4349 static struct view_ops help_ops = {
4350         "line",
4351         NULL,
4352         help_open,
4353         NULL,
4354         pager_draw,
4355         help_request,
4356         pager_grep,
4357         pager_select,
4358 };
4361 /*
4362  * Tree backend
4363  */
4365 struct tree_stack_entry {
4366         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4367         unsigned long lineno;           /* Line number to restore */
4368         char *name;                     /* Position of name in opt_path */
4369 };
4371 /* The top of the path stack. */
4372 static struct tree_stack_entry *tree_stack = NULL;
4373 unsigned long tree_lineno = 0;
4375 static void
4376 pop_tree_stack_entry(void)
4378         struct tree_stack_entry *entry = tree_stack;
4380         tree_lineno = entry->lineno;
4381         entry->name[0] = 0;
4382         tree_stack = entry->prev;
4383         free(entry);
4386 static void
4387 push_tree_stack_entry(const char *name, unsigned long lineno)
4389         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4390         size_t pathlen = strlen(opt_path);
4392         if (!entry)
4393                 return;
4395         entry->prev = tree_stack;
4396         entry->name = opt_path + pathlen;
4397         tree_stack = entry;
4399         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4400                 pop_tree_stack_entry();
4401                 return;
4402         }
4404         /* Move the current line to the first tree entry. */
4405         tree_lineno = 1;
4406         entry->lineno = lineno;
4409 /* Parse output from git-ls-tree(1):
4410  *
4411  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4412  */
4414 #define SIZEOF_TREE_ATTR \
4415         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4417 #define SIZEOF_TREE_MODE \
4418         STRING_SIZE("100644 ")
4420 #define TREE_ID_OFFSET \
4421         STRING_SIZE("100644 blob ")
4423 struct tree_entry {
4424         char id[SIZEOF_REV];
4425         mode_t mode;
4426         struct time time;               /* Date from the author ident. */
4427         const char *author;             /* Author of the commit. */
4428         char name[1];
4429 };
4431 static const char *
4432 tree_path(const struct line *line)
4434         return ((struct tree_entry *) line->data)->name;
4437 static int
4438 tree_compare_entry(const struct line *line1, const struct line *line2)
4440         if (line1->type != line2->type)
4441                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4442         return strcmp(tree_path(line1), tree_path(line2));
4445 static const enum sort_field tree_sort_fields[] = {
4446         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4447 };
4448 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4450 static int
4451 tree_compare(const void *l1, const void *l2)
4453         const struct line *line1 = (const struct line *) l1;
4454         const struct line *line2 = (const struct line *) l2;
4455         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4456         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4458         if (line1->type == LINE_TREE_HEAD)
4459                 return -1;
4460         if (line2->type == LINE_TREE_HEAD)
4461                 return 1;
4463         switch (get_sort_field(tree_sort_state)) {
4464         case ORDERBY_DATE:
4465                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4467         case ORDERBY_AUTHOR:
4468                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4470         case ORDERBY_NAME:
4471         default:
4472                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4473         }
4477 static struct line *
4478 tree_entry(struct view *view, enum line_type type, const char *path,
4479            const char *mode, const char *id)
4481         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4482         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4484         if (!entry || !line) {
4485                 free(entry);
4486                 return NULL;
4487         }
4489         strncpy(entry->name, path, strlen(path));
4490         if (mode)
4491                 entry->mode = strtoul(mode, NULL, 8);
4492         if (id)
4493                 string_copy_rev(entry->id, id);
4495         return line;
4498 static bool
4499 tree_read_date(struct view *view, char *text, bool *read_date)
4501         static const char *author_name;
4502         static struct time author_time;
4504         if (!text && *read_date) {
4505                 *read_date = FALSE;
4506                 return TRUE;
4508         } else if (!text) {
4509                 char *path = *opt_path ? opt_path : ".";
4510                 /* Find next entry to process */
4511                 const char *log_file[] = {
4512                         "git", "log", "--no-color", "--pretty=raw",
4513                                 "--cc", "--raw", view->id, "--", path, NULL
4514                 };
4515                 struct io io = {};
4517                 if (!view->lines) {
4518                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4519                         report("Tree is empty");
4520                         return TRUE;
4521                 }
4523                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4524                         report("Failed to load tree data");
4525                         return TRUE;
4526                 }
4528                 io_done(view->pipe);
4529                 view->io = io;
4530                 *read_date = TRUE;
4531                 return FALSE;
4533         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4534                 parse_author_line(text + STRING_SIZE("author "),
4535                                   &author_name, &author_time);
4537         } else if (*text == ':') {
4538                 char *pos;
4539                 size_t annotated = 1;
4540                 size_t i;
4542                 pos = strchr(text, '\t');
4543                 if (!pos)
4544                         return TRUE;
4545                 text = pos + 1;
4546                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4547                         text += strlen(opt_path);
4548                 pos = strchr(text, '/');
4549                 if (pos)
4550                         *pos = 0;
4552                 for (i = 1; i < view->lines; i++) {
4553                         struct line *line = &view->line[i];
4554                         struct tree_entry *entry = line->data;
4556                         annotated += !!entry->author;
4557                         if (entry->author || strcmp(entry->name, text))
4558                                 continue;
4560                         entry->author = author_name;
4561                         entry->time = author_time;
4562                         line->dirty = 1;
4563                         break;
4564                 }
4566                 if (annotated == view->lines)
4567                         io_kill(view->pipe);
4568         }
4569         return TRUE;
4572 static bool
4573 tree_read(struct view *view, char *text)
4575         static bool read_date = FALSE;
4576         struct tree_entry *data;
4577         struct line *entry, *line;
4578         enum line_type type;
4579         size_t textlen = text ? strlen(text) : 0;
4580         char *path = text + SIZEOF_TREE_ATTR;
4582         if (read_date || !text)
4583                 return tree_read_date(view, text, &read_date);
4585         if (textlen <= SIZEOF_TREE_ATTR)
4586                 return FALSE;
4587         if (view->lines == 0 &&
4588             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4589                 return FALSE;
4591         /* Strip the path part ... */
4592         if (*opt_path) {
4593                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4594                 size_t striplen = strlen(opt_path);
4596                 if (pathlen > striplen)
4597                         memmove(path, path + striplen,
4598                                 pathlen - striplen + 1);
4600                 /* Insert "link" to parent directory. */
4601                 if (view->lines == 1 &&
4602                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4603                         return FALSE;
4604         }
4606         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4607         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4608         if (!entry)
4609                 return FALSE;
4610         data = entry->data;
4612         /* Skip "Directory ..." and ".." line. */
4613         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4614                 if (tree_compare_entry(line, entry) <= 0)
4615                         continue;
4617                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4619                 line->data = data;
4620                 line->type = type;
4621                 for (; line <= entry; line++)
4622                         line->dirty = line->cleareol = 1;
4623                 return TRUE;
4624         }
4626         if (tree_lineno > view->lineno) {
4627                 view->lineno = tree_lineno;
4628                 tree_lineno = 0;
4629         }
4631         return TRUE;
4634 static bool
4635 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4637         struct tree_entry *entry = line->data;
4639         if (line->type == LINE_TREE_HEAD) {
4640                 if (draw_text(view, line->type, "Directory path /", TRUE))
4641                         return TRUE;
4642         } else {
4643                 if (draw_mode(view, entry->mode))
4644                         return TRUE;
4646                 if (opt_author && draw_author(view, entry->author))
4647                         return TRUE;
4649                 if (opt_date && draw_date(view, &entry->time))
4650                         return TRUE;
4651         }
4652         if (draw_text(view, line->type, entry->name, TRUE))
4653                 return TRUE;
4654         return TRUE;
4657 static void
4658 open_blob_editor()
4660         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4661         int fd = mkstemp(file);
4663         if (fd == -1)
4664                 report("Failed to create temporary file");
4665         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4666                 report("Failed to save blob data to file");
4667         else
4668                 open_editor(file);
4669         if (fd != -1)
4670                 unlink(file);
4673 static enum request
4674 tree_request(struct view *view, enum request request, struct line *line)
4676         enum open_flags flags;
4678         switch (request) {
4679         case REQ_VIEW_BLAME:
4680                 if (line->type != LINE_TREE_FILE) {
4681                         report("Blame only supported for files");
4682                         return REQ_NONE;
4683                 }
4685                 string_copy(opt_ref, view->vid);
4686                 return request;
4688         case REQ_EDIT:
4689                 if (line->type != LINE_TREE_FILE) {
4690                         report("Edit only supported for files");
4691                 } else if (!is_head_commit(view->vid)) {
4692                         open_blob_editor();
4693                 } else {
4694                         open_editor(opt_file);
4695                 }
4696                 return REQ_NONE;
4698         case REQ_TOGGLE_SORT_FIELD:
4699         case REQ_TOGGLE_SORT_ORDER:
4700                 sort_view(view, request, &tree_sort_state, tree_compare);
4701                 return REQ_NONE;
4703         case REQ_PARENT:
4704                 if (!*opt_path) {
4705                         /* quit view if at top of tree */
4706                         return REQ_VIEW_CLOSE;
4707                 }
4708                 /* fake 'cd  ..' */
4709                 line = &view->line[1];
4710                 break;
4712         case REQ_ENTER:
4713                 break;
4715         default:
4716                 return request;
4717         }
4719         /* Cleanup the stack if the tree view is at a different tree. */
4720         while (!*opt_path && tree_stack)
4721                 pop_tree_stack_entry();
4723         switch (line->type) {
4724         case LINE_TREE_DIR:
4725                 /* Depending on whether it is a subdirectory or parent link
4726                  * mangle the path buffer. */
4727                 if (line == &view->line[1] && *opt_path) {
4728                         pop_tree_stack_entry();
4730                 } else {
4731                         const char *basename = tree_path(line);
4733                         push_tree_stack_entry(basename, view->lineno);
4734                 }
4736                 /* Trees and subtrees share the same ID, so they are not not
4737                  * unique like blobs. */
4738                 flags = OPEN_RELOAD;
4739                 request = REQ_VIEW_TREE;
4740                 break;
4742         case LINE_TREE_FILE:
4743                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4744                 request = REQ_VIEW_BLOB;
4745                 break;
4747         default:
4748                 return REQ_NONE;
4749         }
4751         open_view(view, request, flags);
4752         if (request == REQ_VIEW_TREE)
4753                 view->lineno = tree_lineno;
4755         return REQ_NONE;
4758 static bool
4759 tree_grep(struct view *view, struct line *line)
4761         struct tree_entry *entry = line->data;
4762         const char *text[] = {
4763                 entry->name,
4764                 opt_author ? entry->author : "",
4765                 mkdate(&entry->time, opt_date),
4766                 NULL
4767         };
4769         return grep_text(view, text);
4772 static void
4773 tree_select(struct view *view, struct line *line)
4775         struct tree_entry *entry = line->data;
4777         if (line->type == LINE_TREE_FILE) {
4778                 string_copy_rev(ref_blob, entry->id);
4779                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4781         } else if (line->type != LINE_TREE_DIR) {
4782                 return;
4783         }
4785         string_copy_rev(view->ref, entry->id);
4788 static bool
4789 tree_prepare(struct view *view)
4791         if (view->lines == 0 && opt_prefix[0]) {
4792                 char *pos = opt_prefix;
4794                 while (pos && *pos) {
4795                         char *end = strchr(pos, '/');
4797                         if (end)
4798                                 *end = 0;
4799                         push_tree_stack_entry(pos, 0);
4800                         pos = end;
4801                         if (end) {
4802                                 *end = '/';
4803                                 pos++;
4804                         }
4805                 }
4807         } else if (strcmp(view->vid, view->id)) {
4808                 opt_path[0] = 0;
4809         }
4811         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4814 static const char *tree_argv[SIZEOF_ARG] = {
4815         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4816 };
4818 static struct view_ops tree_ops = {
4819         "file",
4820         tree_argv,
4821         NULL,
4822         tree_read,
4823         tree_draw,
4824         tree_request,
4825         tree_grep,
4826         tree_select,
4827         tree_prepare,
4828 };
4830 static bool
4831 blob_read(struct view *view, char *line)
4833         if (!line)
4834                 return TRUE;
4835         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4838 static enum request
4839 blob_request(struct view *view, enum request request, struct line *line)
4841         switch (request) {
4842         case REQ_EDIT:
4843                 open_blob_editor();
4844                 return REQ_NONE;
4845         default:
4846                 return pager_request(view, request, line);
4847         }
4850 static const char *blob_argv[SIZEOF_ARG] = {
4851         "git", "cat-file", "blob", "%(blob)", NULL
4852 };
4854 static struct view_ops blob_ops = {
4855         "line",
4856         blob_argv,
4857         NULL,
4858         blob_read,
4859         pager_draw,
4860         blob_request,
4861         pager_grep,
4862         pager_select,
4863 };
4865 /*
4866  * Blame backend
4867  *
4868  * Loading the blame view is a two phase job:
4869  *
4870  *  1. File content is read either using opt_file from the
4871  *     filesystem or using git-cat-file.
4872  *  2. Then blame information is incrementally added by
4873  *     reading output from git-blame.
4874  */
4876 static const char *blame_head_argv[] = {
4877         "git", "blame", "--incremental", "--", "%(file)", NULL
4878 };
4880 static const char *blame_ref_argv[] = {
4881         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4882 };
4884 static const char *blame_cat_file_argv[] = {
4885         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4886 };
4888 struct blame_commit {
4889         char id[SIZEOF_REV];            /* SHA1 ID. */
4890         char title[128];                /* First line of the commit message. */
4891         const char *author;             /* Author of the commit. */
4892         struct time time;               /* Date from the author ident. */
4893         char filename[128];             /* Name of file. */
4894         bool has_previous;              /* Was a "previous" line detected. */
4895 };
4897 struct blame {
4898         struct blame_commit *commit;
4899         unsigned long lineno;
4900         char text[1];
4901 };
4903 static bool
4904 blame_open(struct view *view)
4906         char path[SIZEOF_STR];
4908         if (!view->parent && *opt_prefix) {
4909                 string_copy(path, opt_file);
4910                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4911                         return FALSE;
4912         }
4914         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4915                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4916                         return FALSE;
4917         }
4919         setup_update(view, opt_file);
4920         string_format(view->ref, "%s ...", opt_file);
4922         return TRUE;
4925 static struct blame_commit *
4926 get_blame_commit(struct view *view, const char *id)
4928         size_t i;
4930         for (i = 0; i < view->lines; i++) {
4931                 struct blame *blame = view->line[i].data;
4933                 if (!blame->commit)
4934                         continue;
4936                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4937                         return blame->commit;
4938         }
4940         {
4941                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4943                 if (commit)
4944                         string_ncopy(commit->id, id, SIZEOF_REV);
4945                 return commit;
4946         }
4949 static bool
4950 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4952         const char *pos = *posref;
4954         *posref = NULL;
4955         pos = strchr(pos + 1, ' ');
4956         if (!pos || !isdigit(pos[1]))
4957                 return FALSE;
4958         *number = atoi(pos + 1);
4959         if (*number < min || *number > max)
4960                 return FALSE;
4962         *posref = pos;
4963         return TRUE;
4966 static struct blame_commit *
4967 parse_blame_commit(struct view *view, const char *text, int *blamed)
4969         struct blame_commit *commit;
4970         struct blame *blame;
4971         const char *pos = text + SIZEOF_REV - 2;
4972         size_t orig_lineno = 0;
4973         size_t lineno;
4974         size_t group;
4976         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4977                 return NULL;
4979         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4980             !parse_number(&pos, &lineno, 1, view->lines) ||
4981             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4982                 return NULL;
4984         commit = get_blame_commit(view, text);
4985         if (!commit)
4986                 return NULL;
4988         *blamed += group;
4989         while (group--) {
4990                 struct line *line = &view->line[lineno + group - 1];
4992                 blame = line->data;
4993                 blame->commit = commit;
4994                 blame->lineno = orig_lineno + group - 1;
4995                 line->dirty = 1;
4996         }
4998         return commit;
5001 static bool
5002 blame_read_file(struct view *view, const char *line, bool *read_file)
5004         if (!line) {
5005                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
5006                 struct io io = {};
5008                 if (view->lines == 0 && !view->parent)
5009                         die("No blame exist for %s", view->vid);
5011                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
5012                         report("Failed to load blame data");
5013                         return TRUE;
5014                 }
5016                 io_done(view->pipe);
5017                 view->io = io;
5018                 *read_file = FALSE;
5019                 return FALSE;
5021         } else {
5022                 size_t linelen = strlen(line);
5023                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5025                 if (!blame)
5026                         return FALSE;
5028                 blame->commit = NULL;
5029                 strncpy(blame->text, line, linelen);
5030                 blame->text[linelen] = 0;
5031                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5032         }
5035 static bool
5036 match_blame_header(const char *name, char **line)
5038         size_t namelen = strlen(name);
5039         bool matched = !strncmp(name, *line, namelen);
5041         if (matched)
5042                 *line += namelen;
5044         return matched;
5047 static bool
5048 blame_read(struct view *view, char *line)
5050         static struct blame_commit *commit = NULL;
5051         static int blamed = 0;
5052         static bool read_file = TRUE;
5054         if (read_file)
5055                 return blame_read_file(view, line, &read_file);
5057         if (!line) {
5058                 /* Reset all! */
5059                 commit = NULL;
5060                 blamed = 0;
5061                 read_file = TRUE;
5062                 string_format(view->ref, "%s", view->vid);
5063                 if (view_is_displayed(view)) {
5064                         update_view_title(view);
5065                         redraw_view_from(view, 0);
5066                 }
5067                 return TRUE;
5068         }
5070         if (!commit) {
5071                 commit = parse_blame_commit(view, line, &blamed);
5072                 string_format(view->ref, "%s %2d%%", view->vid,
5073                               view->lines ? blamed * 100 / view->lines : 0);
5075         } else if (match_blame_header("author ", &line)) {
5076                 commit->author = get_author(line);
5078         } else if (match_blame_header("author-time ", &line)) {
5079                 parse_timesec(&commit->time, line);
5081         } else if (match_blame_header("author-tz ", &line)) {
5082                 parse_timezone(&commit->time, line);
5084         } else if (match_blame_header("summary ", &line)) {
5085                 string_ncopy(commit->title, line, strlen(line));
5087         } else if (match_blame_header("previous ", &line)) {
5088                 commit->has_previous = TRUE;
5090         } else if (match_blame_header("filename ", &line)) {
5091                 string_ncopy(commit->filename, line, strlen(line));
5092                 commit = NULL;
5093         }
5095         return TRUE;
5098 static bool
5099 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5101         struct blame *blame = line->data;
5102         struct time *time = NULL;
5103         const char *id = NULL, *author = NULL;
5104         char text[SIZEOF_STR];
5106         if (blame->commit && *blame->commit->filename) {
5107                 id = blame->commit->id;
5108                 author = blame->commit->author;
5109                 time = &blame->commit->time;
5110         }
5112         if (opt_date && draw_date(view, time))
5113                 return TRUE;
5115         if (opt_author && draw_author(view, author))
5116                 return TRUE;
5118         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5119                 return TRUE;
5121         if (draw_lineno(view, lineno))
5122                 return TRUE;
5124         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5125         draw_text(view, LINE_DEFAULT, text, TRUE);
5126         return TRUE;
5129 static bool
5130 check_blame_commit(struct blame *blame, bool check_null_id)
5132         if (!blame->commit)
5133                 report("Commit data not loaded yet");
5134         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5135                 report("No commit exist for the selected line");
5136         else
5137                 return TRUE;
5138         return FALSE;
5141 static void
5142 setup_blame_parent_line(struct view *view, struct blame *blame)
5144         const char *diff_tree_argv[] = {
5145                 "git", "diff-tree", "-U0", blame->commit->id,
5146                         "--", blame->commit->filename, NULL
5147         };
5148         struct io io = {};
5149         int parent_lineno = -1;
5150         int blamed_lineno = -1;
5151         char *line;
5153         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5154                 return;
5156         while ((line = io_get(&io, '\n', TRUE))) {
5157                 if (*line == '@') {
5158                         char *pos = strchr(line, '+');
5160                         parent_lineno = atoi(line + 4);
5161                         if (pos)
5162                                 blamed_lineno = atoi(pos + 1);
5164                 } else if (*line == '+' && parent_lineno != -1) {
5165                         if (blame->lineno == blamed_lineno - 1 &&
5166                             !strcmp(blame->text, line + 1)) {
5167                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5168                                 break;
5169                         }
5170                         blamed_lineno++;
5171                 }
5172         }
5174         io_done(&io);
5177 static enum request
5178 blame_request(struct view *view, enum request request, struct line *line)
5180         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5181         struct blame *blame = line->data;
5183         switch (request) {
5184         case REQ_VIEW_BLAME:
5185                 if (check_blame_commit(blame, TRUE)) {
5186                         string_copy(opt_ref, blame->commit->id);
5187                         string_copy(opt_file, blame->commit->filename);
5188                         if (blame->lineno)
5189                                 view->lineno = blame->lineno;
5190                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5191                 }
5192                 break;
5194         case REQ_PARENT:
5195                 if (check_blame_commit(blame, TRUE) &&
5196                     select_commit_parent(blame->commit->id, opt_ref,
5197                                          blame->commit->filename)) {
5198                         string_copy(opt_file, blame->commit->filename);
5199                         setup_blame_parent_line(view, blame);
5200                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5201                 }
5202                 break;
5204         case REQ_ENTER:
5205                 if (!check_blame_commit(blame, FALSE))
5206                         break;
5208                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5209                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5210                         break;
5212                 if (!strcmp(blame->commit->id, NULL_ID)) {
5213                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5214                         const char *diff_index_argv[] = {
5215                                 "git", "diff-index", "--root", "--patch-with-stat",
5216                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5217                         };
5219                         if (!blame->commit->has_previous) {
5220                                 diff_index_argv[1] = "diff";
5221                                 diff_index_argv[2] = "--no-color";
5222                                 diff_index_argv[6] = "--";
5223                                 diff_index_argv[7] = "/dev/null";
5224                         }
5226                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5227                                 report("Failed to allocate diff command");
5228                                 break;
5229                         }
5230                         flags |= OPEN_PREPARED;
5231                 }
5233                 open_view(view, REQ_VIEW_DIFF, flags);
5234                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5235                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5236                 break;
5238         default:
5239                 return request;
5240         }
5242         return REQ_NONE;
5245 static bool
5246 blame_grep(struct view *view, struct line *line)
5248         struct blame *blame = line->data;
5249         struct blame_commit *commit = blame->commit;
5250         const char *text[] = {
5251                 blame->text,
5252                 commit ? commit->title : "",
5253                 commit ? commit->id : "",
5254                 commit && opt_author ? commit->author : "",
5255                 commit ? mkdate(&commit->time, opt_date) : "",
5256                 NULL
5257         };
5259         return grep_text(view, text);
5262 static void
5263 blame_select(struct view *view, struct line *line)
5265         struct blame *blame = line->data;
5266         struct blame_commit *commit = blame->commit;
5268         if (!commit)
5269                 return;
5271         if (!strcmp(commit->id, NULL_ID))
5272                 string_ncopy(ref_commit, "HEAD", 4);
5273         else
5274                 string_copy_rev(ref_commit, commit->id);
5277 static struct view_ops blame_ops = {
5278         "line",
5279         NULL,
5280         blame_open,
5281         blame_read,
5282         blame_draw,
5283         blame_request,
5284         blame_grep,
5285         blame_select,
5286 };
5288 /*
5289  * Branch backend
5290  */
5292 struct branch {
5293         const char *author;             /* Author of the last commit. */
5294         struct time time;               /* Date of the last activity. */
5295         const struct ref *ref;          /* Name and commit ID information. */
5296 };
5298 static const struct ref branch_all;
5300 static const enum sort_field branch_sort_fields[] = {
5301         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5302 };
5303 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5305 static int
5306 branch_compare(const void *l1, const void *l2)
5308         const struct branch *branch1 = ((const struct line *) l1)->data;
5309         const struct branch *branch2 = ((const struct line *) l2)->data;
5311         switch (get_sort_field(branch_sort_state)) {
5312         case ORDERBY_DATE:
5313                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5315         case ORDERBY_AUTHOR:
5316                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5318         case ORDERBY_NAME:
5319         default:
5320                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5321         }
5324 static bool
5325 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5327         struct branch *branch = line->data;
5328         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5330         if (opt_date && draw_date(view, &branch->time))
5331                 return TRUE;
5333         if (opt_author && draw_author(view, branch->author))
5334                 return TRUE;
5336         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5337         return TRUE;
5340 static enum request
5341 branch_request(struct view *view, enum request request, struct line *line)
5343         struct branch *branch = line->data;
5345         switch (request) {
5346         case REQ_REFRESH:
5347                 load_refs();
5348                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5349                 return REQ_NONE;
5351         case REQ_TOGGLE_SORT_FIELD:
5352         case REQ_TOGGLE_SORT_ORDER:
5353                 sort_view(view, request, &branch_sort_state, branch_compare);
5354                 return REQ_NONE;
5356         case REQ_ENTER:
5357                 if (branch->ref == &branch_all) {
5358                         const char *all_branches_argv[] = {
5359                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5360                                       "--topo-order", "--all", NULL
5361                         };
5362                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5364                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5365                                 report("Failed to load view of all branches");
5366                                 return REQ_NONE;
5367                         }
5368                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5369                 } else {
5370                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5371                 }
5372                 return REQ_NONE;
5374         default:
5375                 return request;
5376         }
5379 static bool
5380 branch_read(struct view *view, char *line)
5382         static char id[SIZEOF_REV];
5383         struct branch *reference;
5384         size_t i;
5386         if (!line)
5387                 return TRUE;
5389         switch (get_line_type(line)) {
5390         case LINE_COMMIT:
5391                 string_copy_rev(id, line + STRING_SIZE("commit "));
5392                 return TRUE;
5394         case LINE_AUTHOR:
5395                 for (i = 0, reference = NULL; i < view->lines; i++) {
5396                         struct branch *branch = view->line[i].data;
5398                         if (strcmp(branch->ref->id, id))
5399                                 continue;
5401                         view->line[i].dirty = TRUE;
5402                         if (reference) {
5403                                 branch->author = reference->author;
5404                                 branch->time = reference->time;
5405                                 continue;
5406                         }
5408                         parse_author_line(line + STRING_SIZE("author "),
5409                                           &branch->author, &branch->time);
5410                         reference = branch;
5411                 }
5412                 return TRUE;
5414         default:
5415                 return TRUE;
5416         }
5420 static bool
5421 branch_open_visitor(void *data, const struct ref *ref)
5423         struct view *view = data;
5424         struct branch *branch;
5426         if (ref->tag || ref->ltag || ref->remote)
5427                 return TRUE;
5429         branch = calloc(1, sizeof(*branch));
5430         if (!branch)
5431                 return FALSE;
5433         branch->ref = ref;
5434         return !!add_line_data(view, branch, LINE_DEFAULT);
5437 static bool
5438 branch_open(struct view *view)
5440         const char *branch_log[] = {
5441                 "git", "log", "--no-color", "--pretty=raw",
5442                         "--simplify-by-decoration", "--all", NULL
5443         };
5445         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5446                 report("Failed to load branch data");
5447                 return TRUE;
5448         }
5450         setup_update(view, view->id);
5451         branch_open_visitor(view, &branch_all);
5452         foreach_ref(branch_open_visitor, view);
5453         view->p_restore = TRUE;
5455         return TRUE;
5458 static bool
5459 branch_grep(struct view *view, struct line *line)
5461         struct branch *branch = line->data;
5462         const char *text[] = {
5463                 branch->ref->name,
5464                 branch->author,
5465                 NULL
5466         };
5468         return grep_text(view, text);
5471 static void
5472 branch_select(struct view *view, struct line *line)
5474         struct branch *branch = line->data;
5476         string_copy_rev(view->ref, branch->ref->id);
5477         string_copy_rev(ref_commit, branch->ref->id);
5478         string_copy_rev(ref_head, branch->ref->id);
5479         string_copy_rev(ref_branch, branch->ref->name);
5482 static struct view_ops branch_ops = {
5483         "branch",
5484         NULL,
5485         branch_open,
5486         branch_read,
5487         branch_draw,
5488         branch_request,
5489         branch_grep,
5490         branch_select,
5491 };
5493 /*
5494  * Status backend
5495  */
5497 struct status {
5498         char status;
5499         struct {
5500                 mode_t mode;
5501                 char rev[SIZEOF_REV];
5502                 char name[SIZEOF_STR];
5503         } old;
5504         struct {
5505                 mode_t mode;
5506                 char rev[SIZEOF_REV];
5507                 char name[SIZEOF_STR];
5508         } new;
5509 };
5511 static char status_onbranch[SIZEOF_STR];
5512 static struct status stage_status;
5513 static enum line_type stage_line_type;
5514 static size_t stage_chunks;
5515 static int *stage_chunk;
5517 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5519 /* This should work even for the "On branch" line. */
5520 static inline bool
5521 status_has_none(struct view *view, struct line *line)
5523         return line < view->line + view->lines && !line[1].data;
5526 /* Get fields from the diff line:
5527  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5528  */
5529 static inline bool
5530 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5532         const char *old_mode = buf +  1;
5533         const char *new_mode = buf +  8;
5534         const char *old_rev  = buf + 15;
5535         const char *new_rev  = buf + 56;
5536         const char *status   = buf + 97;
5538         if (bufsize < 98 ||
5539             old_mode[-1] != ':' ||
5540             new_mode[-1] != ' ' ||
5541             old_rev[-1]  != ' ' ||
5542             new_rev[-1]  != ' ' ||
5543             status[-1]   != ' ')
5544                 return FALSE;
5546         file->status = *status;
5548         string_copy_rev(file->old.rev, old_rev);
5549         string_copy_rev(file->new.rev, new_rev);
5551         file->old.mode = strtoul(old_mode, NULL, 8);
5552         file->new.mode = strtoul(new_mode, NULL, 8);
5554         file->old.name[0] = file->new.name[0] = 0;
5556         return TRUE;
5559 static bool
5560 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5562         struct status *unmerged = NULL;
5563         char *buf;
5564         struct io io = {};
5566         if (!io_run(&io, argv, opt_cdup, IO_RD))
5567                 return FALSE;
5569         add_line_data(view, NULL, type);
5571         while ((buf = io_get(&io, 0, TRUE))) {
5572                 struct status *file = unmerged;
5574                 if (!file) {
5575                         file = calloc(1, sizeof(*file));
5576                         if (!file || !add_line_data(view, file, type))
5577                                 goto error_out;
5578                 }
5580                 /* Parse diff info part. */
5581                 if (status) {
5582                         file->status = status;
5583                         if (status == 'A')
5584                                 string_copy(file->old.rev, NULL_ID);
5586                 } else if (!file->status || file == unmerged) {
5587                         if (!status_get_diff(file, buf, strlen(buf)))
5588                                 goto error_out;
5590                         buf = io_get(&io, 0, TRUE);
5591                         if (!buf)
5592                                 break;
5594                         /* Collapse all modified entries that follow an
5595                          * associated unmerged entry. */
5596                         if (unmerged == file) {
5597                                 unmerged->status = 'U';
5598                                 unmerged = NULL;
5599                         } else if (file->status == 'U') {
5600                                 unmerged = file;
5601                         }
5602                 }
5604                 /* Grab the old name for rename/copy. */
5605                 if (!*file->old.name &&
5606                     (file->status == 'R' || file->status == 'C')) {
5607                         string_ncopy(file->old.name, buf, strlen(buf));
5609                         buf = io_get(&io, 0, TRUE);
5610                         if (!buf)
5611                                 break;
5612                 }
5614                 /* git-ls-files just delivers a NUL separated list of
5615                  * file names similar to the second half of the
5616                  * git-diff-* output. */
5617                 string_ncopy(file->new.name, buf, strlen(buf));
5618                 if (!*file->old.name)
5619                         string_copy(file->old.name, file->new.name);
5620                 file = NULL;
5621         }
5623         if (io_error(&io)) {
5624 error_out:
5625                 io_done(&io);
5626                 return FALSE;
5627         }
5629         if (!view->line[view->lines - 1].data)
5630                 add_line_data(view, NULL, LINE_STAT_NONE);
5632         io_done(&io);
5633         return TRUE;
5636 /* Don't show unmerged entries in the staged section. */
5637 static const char *status_diff_index_argv[] = {
5638         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5639                              "--cached", "-M", "HEAD", NULL
5640 };
5642 static const char *status_diff_files_argv[] = {
5643         "git", "diff-files", "-z", NULL
5644 };
5646 static const char *status_list_other_argv[] = {
5647         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5648 };
5650 static const char *status_list_no_head_argv[] = {
5651         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5652 };
5654 static const char *update_index_argv[] = {
5655         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5656 };
5658 /* Restore the previous line number to stay in the context or select a
5659  * line with something that can be updated. */
5660 static void
5661 status_restore(struct view *view)
5663         if (view->p_lineno >= view->lines)
5664                 view->p_lineno = view->lines - 1;
5665         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5666                 view->p_lineno++;
5667         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5668                 view->p_lineno--;
5670         /* If the above fails, always skip the "On branch" line. */
5671         if (view->p_lineno < view->lines)
5672                 view->lineno = view->p_lineno;
5673         else
5674                 view->lineno = 1;
5676         if (view->lineno < view->offset)
5677                 view->offset = view->lineno;
5678         else if (view->offset + view->height <= view->lineno)
5679                 view->offset = view->lineno - view->height + 1;
5681         view->p_restore = FALSE;
5684 static void
5685 status_update_onbranch(void)
5687         static const char *paths[][2] = {
5688                 { "rebase-apply/rebasing",      "Rebasing" },
5689                 { "rebase-apply/applying",      "Applying mailbox" },
5690                 { "rebase-apply/",              "Rebasing mailbox" },
5691                 { "rebase-merge/interactive",   "Interactive rebase" },
5692                 { "rebase-merge/",              "Rebase merge" },
5693                 { "MERGE_HEAD",                 "Merging" },
5694                 { "BISECT_LOG",                 "Bisecting" },
5695                 { "HEAD",                       "On branch" },
5696         };
5697         char buf[SIZEOF_STR];
5698         struct stat stat;
5699         int i;
5701         if (is_initial_commit()) {
5702                 string_copy(status_onbranch, "Initial commit");
5703                 return;
5704         }
5706         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5707                 char *head = opt_head;
5709                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5710                     lstat(buf, &stat) < 0)
5711                         continue;
5713                 if (!*opt_head) {
5714                         struct io io = {};
5716                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5717                             io_read_buf(&io, buf, sizeof(buf))) {
5718                                 head = buf;
5719                                 if (!prefixcmp(head, "refs/heads/"))
5720                                         head += STRING_SIZE("refs/heads/");
5721                         }
5722                 }
5724                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5725                         string_copy(status_onbranch, opt_head);
5726                 return;
5727         }
5729         string_copy(status_onbranch, "Not currently on any branch");
5732 /* First parse staged info using git-diff-index(1), then parse unstaged
5733  * info using git-diff-files(1), and finally untracked files using
5734  * git-ls-files(1). */
5735 static bool
5736 status_open(struct view *view)
5738         reset_view(view);
5740         add_line_data(view, NULL, LINE_STAT_HEAD);
5741         status_update_onbranch();
5743         io_run_bg(update_index_argv);
5745         if (is_initial_commit()) {
5746                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5747                         return FALSE;
5748         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5749                 return FALSE;
5750         }
5752         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5753             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5754                 return FALSE;
5756         /* Restore the exact position or use the specialized restore
5757          * mode? */
5758         if (!view->p_restore)
5759                 status_restore(view);
5760         return TRUE;
5763 static bool
5764 status_draw(struct view *view, struct line *line, unsigned int lineno)
5766         struct status *status = line->data;
5767         enum line_type type;
5768         const char *text;
5770         if (!status) {
5771                 switch (line->type) {
5772                 case LINE_STAT_STAGED:
5773                         type = LINE_STAT_SECTION;
5774                         text = "Changes to be committed:";
5775                         break;
5777                 case LINE_STAT_UNSTAGED:
5778                         type = LINE_STAT_SECTION;
5779                         text = "Changed but not updated:";
5780                         break;
5782                 case LINE_STAT_UNTRACKED:
5783                         type = LINE_STAT_SECTION;
5784                         text = "Untracked files:";
5785                         break;
5787                 case LINE_STAT_NONE:
5788                         type = LINE_DEFAULT;
5789                         text = "  (no files)";
5790                         break;
5792                 case LINE_STAT_HEAD:
5793                         type = LINE_STAT_HEAD;
5794                         text = status_onbranch;
5795                         break;
5797                 default:
5798                         return FALSE;
5799                 }
5800         } else {
5801                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5803                 buf[0] = status->status;
5804                 if (draw_text(view, line->type, buf, TRUE))
5805                         return TRUE;
5806                 type = LINE_DEFAULT;
5807                 text = status->new.name;
5808         }
5810         draw_text(view, type, text, TRUE);
5811         return TRUE;
5814 static enum request
5815 status_load_error(struct view *view, struct view *stage, const char *path)
5817         if (displayed_views() == 2 || display[current_view] != view)
5818                 maximize_view(view);
5819         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5820         return REQ_NONE;
5823 static enum request
5824 status_enter(struct view *view, struct line *line)
5826         struct status *status = line->data;
5827         const char *oldpath = status ? status->old.name : NULL;
5828         /* Diffs for unmerged entries are empty when passing the new
5829          * path, so leave it empty. */
5830         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5831         const char *info;
5832         enum open_flags split;
5833         struct view *stage = VIEW(REQ_VIEW_STAGE);
5835         if (line->type == LINE_STAT_NONE ||
5836             (!status && line[1].type == LINE_STAT_NONE)) {
5837                 report("No file to diff");
5838                 return REQ_NONE;
5839         }
5841         switch (line->type) {
5842         case LINE_STAT_STAGED:
5843                 if (is_initial_commit()) {
5844                         const char *no_head_diff_argv[] = {
5845                                 "git", "diff", "--no-color", "--patch-with-stat",
5846                                         "--", "/dev/null", newpath, NULL
5847                         };
5849                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5850                                 return status_load_error(view, stage, newpath);
5851                 } else {
5852                         const char *index_show_argv[] = {
5853                                 "git", "diff-index", "--root", "--patch-with-stat",
5854                                         "-C", "-M", "--cached", "HEAD", "--",
5855                                         oldpath, newpath, NULL
5856                         };
5858                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5859                                 return status_load_error(view, stage, newpath);
5860                 }
5862                 if (status)
5863                         info = "Staged changes to %s";
5864                 else
5865                         info = "Staged changes";
5866                 break;
5868         case LINE_STAT_UNSTAGED:
5869         {
5870                 const char *files_show_argv[] = {
5871                         "git", "diff-files", "--root", "--patch-with-stat",
5872                                 "-C", "-M", "--", oldpath, newpath, NULL
5873                 };
5875                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5876                         return status_load_error(view, stage, newpath);
5877                 if (status)
5878                         info = "Unstaged changes to %s";
5879                 else
5880                         info = "Unstaged changes";
5881                 break;
5882         }
5883         case LINE_STAT_UNTRACKED:
5884                 if (!newpath) {
5885                         report("No file to show");
5886                         return REQ_NONE;
5887                 }
5889                 if (!suffixcmp(status->new.name, -1, "/")) {
5890                         report("Cannot display a directory");
5891                         return REQ_NONE;
5892                 }
5894                 if (!prepare_update_file(stage, newpath))
5895                         return status_load_error(view, stage, newpath);
5896                 info = "Untracked file %s";
5897                 break;
5899         case LINE_STAT_HEAD:
5900                 return REQ_NONE;
5902         default:
5903                 die("line type %d not handled in switch", line->type);
5904         }
5906         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5907         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5908         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5909                 if (status) {
5910                         stage_status = *status;
5911                 } else {
5912                         memset(&stage_status, 0, sizeof(stage_status));
5913                 }
5915                 stage_line_type = line->type;
5916                 stage_chunks = 0;
5917                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5918         }
5920         return REQ_NONE;
5923 static bool
5924 status_exists(struct status *status, enum line_type type)
5926         struct view *view = VIEW(REQ_VIEW_STATUS);
5927         unsigned long lineno;
5929         for (lineno = 0; lineno < view->lines; lineno++) {
5930                 struct line *line = &view->line[lineno];
5931                 struct status *pos = line->data;
5933                 if (line->type != type)
5934                         continue;
5935                 if (!pos && (!status || !status->status) && line[1].data) {
5936                         select_view_line(view, lineno);
5937                         return TRUE;
5938                 }
5939                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5940                         select_view_line(view, lineno);
5941                         return TRUE;
5942                 }
5943         }
5945         return FALSE;
5949 static bool
5950 status_update_prepare(struct io *io, enum line_type type)
5952         const char *staged_argv[] = {
5953                 "git", "update-index", "-z", "--index-info", NULL
5954         };
5955         const char *others_argv[] = {
5956                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5957         };
5959         switch (type) {
5960         case LINE_STAT_STAGED:
5961                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5963         case LINE_STAT_UNSTAGED:
5964         case LINE_STAT_UNTRACKED:
5965                 return io_run(io, others_argv, opt_cdup, IO_WR);
5967         default:
5968                 die("line type %d not handled in switch", type);
5969                 return FALSE;
5970         }
5973 static bool
5974 status_update_write(struct io *io, struct status *status, enum line_type type)
5976         char buf[SIZEOF_STR];
5977         size_t bufsize = 0;
5979         switch (type) {
5980         case LINE_STAT_STAGED:
5981                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5982                                         status->old.mode,
5983                                         status->old.rev,
5984                                         status->old.name, 0))
5985                         return FALSE;
5986                 break;
5988         case LINE_STAT_UNSTAGED:
5989         case LINE_STAT_UNTRACKED:
5990                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5991                         return FALSE;
5992                 break;
5994         default:
5995                 die("line type %d not handled in switch", type);
5996         }
5998         return io_write(io, buf, bufsize);
6001 static bool
6002 status_update_file(struct status *status, enum line_type type)
6004         struct io io = {};
6005         bool result;
6007         if (!status_update_prepare(&io, type))
6008                 return FALSE;
6010         result = status_update_write(&io, status, type);
6011         return io_done(&io) && result;
6014 static bool
6015 status_update_files(struct view *view, struct line *line)
6017         char buf[sizeof(view->ref)];
6018         struct io io = {};
6019         bool result = TRUE;
6020         struct line *pos = view->line + view->lines;
6021         int files = 0;
6022         int file, done;
6023         int cursor_y = -1, cursor_x = -1;
6025         if (!status_update_prepare(&io, line->type))
6026                 return FALSE;
6028         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6029                 files++;
6031         string_copy(buf, view->ref);
6032         getsyx(cursor_y, cursor_x);
6033         for (file = 0, done = 5; result && file < files; line++, file++) {
6034                 int almost_done = file * 100 / files;
6036                 if (almost_done > done) {
6037                         done = almost_done;
6038                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6039                                       file, files, done);
6040                         update_view_title(view);
6041                         setsyx(cursor_y, cursor_x);
6042                         doupdate();
6043                 }
6044                 result = status_update_write(&io, line->data, line->type);
6045         }
6046         string_copy(view->ref, buf);
6048         return io_done(&io) && result;
6051 static bool
6052 status_update(struct view *view)
6054         struct line *line = &view->line[view->lineno];
6056         assert(view->lines);
6058         if (!line->data) {
6059                 /* This should work even for the "On branch" line. */
6060                 if (line < view->line + view->lines && !line[1].data) {
6061                         report("Nothing to update");
6062                         return FALSE;
6063                 }
6065                 if (!status_update_files(view, line + 1)) {
6066                         report("Failed to update file status");
6067                         return FALSE;
6068                 }
6070         } else if (!status_update_file(line->data, line->type)) {
6071                 report("Failed to update file status");
6072                 return FALSE;
6073         }
6075         return TRUE;
6078 static bool
6079 status_revert(struct status *status, enum line_type type, bool has_none)
6081         if (!status || type != LINE_STAT_UNSTAGED) {
6082                 if (type == LINE_STAT_STAGED) {
6083                         report("Cannot revert changes to staged files");
6084                 } else if (type == LINE_STAT_UNTRACKED) {
6085                         report("Cannot revert changes to untracked files");
6086                 } else if (has_none) {
6087                         report("Nothing to revert");
6088                 } else {
6089                         report("Cannot revert changes to multiple files");
6090                 }
6092         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6093                 char mode[10] = "100644";
6094                 const char *reset_argv[] = {
6095                         "git", "update-index", "--cacheinfo", mode,
6096                                 status->old.rev, status->old.name, NULL
6097                 };
6098                 const char *checkout_argv[] = {
6099                         "git", "checkout", "--", status->old.name, NULL
6100                 };
6102                 if (status->status == 'U') {
6103                         string_format(mode, "%5o", status->old.mode);
6105                         if (status->old.mode == 0 && status->new.mode == 0) {
6106                                 reset_argv[2] = "--force-remove";
6107                                 reset_argv[3] = status->old.name;
6108                                 reset_argv[4] = NULL;
6109                         }
6111                         if (!io_run_fg(reset_argv, opt_cdup))
6112                                 return FALSE;
6113                         if (status->old.mode == 0 && status->new.mode == 0)
6114                                 return TRUE;
6115                 }
6117                 return io_run_fg(checkout_argv, opt_cdup);
6118         }
6120         return FALSE;
6123 static enum request
6124 status_request(struct view *view, enum request request, struct line *line)
6126         struct status *status = line->data;
6128         switch (request) {
6129         case REQ_STATUS_UPDATE:
6130                 if (!status_update(view))
6131                         return REQ_NONE;
6132                 break;
6134         case REQ_STATUS_REVERT:
6135                 if (!status_revert(status, line->type, status_has_none(view, line)))
6136                         return REQ_NONE;
6137                 break;
6139         case REQ_STATUS_MERGE:
6140                 if (!status || status->status != 'U') {
6141                         report("Merging only possible for files with unmerged status ('U').");
6142                         return REQ_NONE;
6143                 }
6144                 open_mergetool(status->new.name);
6145                 break;
6147         case REQ_EDIT:
6148                 if (!status)
6149                         return request;
6150                 if (status->status == 'D') {
6151                         report("File has been deleted.");
6152                         return REQ_NONE;
6153                 }
6155                 open_editor(status->new.name);
6156                 break;
6158         case REQ_VIEW_BLAME:
6159                 if (status)
6160                         opt_ref[0] = 0;
6161                 return request;
6163         case REQ_ENTER:
6164                 /* After returning the status view has been split to
6165                  * show the stage view. No further reloading is
6166                  * necessary. */
6167                 return status_enter(view, line);
6169         case REQ_REFRESH:
6170                 /* Simply reload the view. */
6171                 break;
6173         default:
6174                 return request;
6175         }
6177         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6179         return REQ_NONE;
6182 static void
6183 status_select(struct view *view, struct line *line)
6185         struct status *status = line->data;
6186         char file[SIZEOF_STR] = "all files";
6187         const char *text;
6188         const char *key;
6190         if (status && !string_format(file, "'%s'", status->new.name))
6191                 return;
6193         if (!status && line[1].type == LINE_STAT_NONE)
6194                 line++;
6196         switch (line->type) {
6197         case LINE_STAT_STAGED:
6198                 text = "Press %s to unstage %s for commit";
6199                 break;
6201         case LINE_STAT_UNSTAGED:
6202                 text = "Press %s to stage %s for commit";
6203                 break;
6205         case LINE_STAT_UNTRACKED:
6206                 text = "Press %s to stage %s for addition";
6207                 break;
6209         case LINE_STAT_HEAD:
6210         case LINE_STAT_NONE:
6211                 text = "Nothing to update";
6212                 break;
6214         default:
6215                 die("line type %d not handled in switch", line->type);
6216         }
6218         if (status && status->status == 'U') {
6219                 text = "Press %s to resolve conflict in %s";
6220                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6222         } else {
6223                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6224         }
6226         string_format(view->ref, text, key, file);
6227         if (status)
6228                 string_copy(opt_file, status->new.name);
6231 static bool
6232 status_grep(struct view *view, struct line *line)
6234         struct status *status = line->data;
6236         if (status) {
6237                 const char buf[2] = { status->status, 0 };
6238                 const char *text[] = { status->new.name, buf, NULL };
6240                 return grep_text(view, text);
6241         }
6243         return FALSE;
6246 static struct view_ops status_ops = {
6247         "file",
6248         NULL,
6249         status_open,
6250         NULL,
6251         status_draw,
6252         status_request,
6253         status_grep,
6254         status_select,
6255 };
6258 static bool
6259 stage_diff_write(struct io *io, struct line *line, struct line *end)
6261         while (line < end) {
6262                 if (!io_write(io, line->data, strlen(line->data)) ||
6263                     !io_write(io, "\n", 1))
6264                         return FALSE;
6265                 line++;
6266                 if (line->type == LINE_DIFF_CHUNK ||
6267                     line->type == LINE_DIFF_HEADER)
6268                         break;
6269         }
6271         return TRUE;
6274 static struct line *
6275 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6277         for (; view->line < line; line--)
6278                 if (line->type == type)
6279                         return line;
6281         return NULL;
6284 static bool
6285 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6287         const char *apply_argv[SIZEOF_ARG] = {
6288                 "git", "apply", "--whitespace=nowarn", NULL
6289         };
6290         struct line *diff_hdr;
6291         struct io io = {};
6292         int argc = 3;
6294         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6295         if (!diff_hdr)
6296                 return FALSE;
6298         if (!revert)
6299                 apply_argv[argc++] = "--cached";
6300         if (revert || stage_line_type == LINE_STAT_STAGED)
6301                 apply_argv[argc++] = "-R";
6302         apply_argv[argc++] = "-";
6303         apply_argv[argc++] = NULL;
6304         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6305                 return FALSE;
6307         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6308             !stage_diff_write(&io, chunk, view->line + view->lines))
6309                 chunk = NULL;
6311         io_done(&io);
6312         io_run_bg(update_index_argv);
6314         return chunk ? TRUE : FALSE;
6317 static bool
6318 stage_update(struct view *view, struct line *line)
6320         struct line *chunk = NULL;
6322         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6323                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6325         if (chunk) {
6326                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6327                         report("Failed to apply chunk");
6328                         return FALSE;
6329                 }
6331         } else if (!stage_status.status) {
6332                 view = VIEW(REQ_VIEW_STATUS);
6334                 for (line = view->line; line < view->line + view->lines; line++)
6335                         if (line->type == stage_line_type)
6336                                 break;
6338                 if (!status_update_files(view, line + 1)) {
6339                         report("Failed to update files");
6340                         return FALSE;
6341                 }
6343         } else if (!status_update_file(&stage_status, stage_line_type)) {
6344                 report("Failed to update file");
6345                 return FALSE;
6346         }
6348         return TRUE;
6351 static bool
6352 stage_revert(struct view *view, struct line *line)
6354         struct line *chunk = NULL;
6356         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6357                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6359         if (chunk) {
6360                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6361                         return FALSE;
6363                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6364                         report("Failed to revert chunk");
6365                         return FALSE;
6366                 }
6367                 return TRUE;
6369         } else {
6370                 return status_revert(stage_status.status ? &stage_status : NULL,
6371                                      stage_line_type, FALSE);
6372         }
6376 static void
6377 stage_next(struct view *view, struct line *line)
6379         int i;
6381         if (!stage_chunks) {
6382                 for (line = view->line; line < view->line + view->lines; line++) {
6383                         if (line->type != LINE_DIFF_CHUNK)
6384                                 continue;
6386                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6387                                 report("Allocation failure");
6388                                 return;
6389                         }
6391                         stage_chunk[stage_chunks++] = line - view->line;
6392                 }
6393         }
6395         for (i = 0; i < stage_chunks; i++) {
6396                 if (stage_chunk[i] > view->lineno) {
6397                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6398                         report("Chunk %d of %d", i + 1, stage_chunks);
6399                         return;
6400                 }
6401         }
6403         report("No next chunk found");
6406 static enum request
6407 stage_request(struct view *view, enum request request, struct line *line)
6409         switch (request) {
6410         case REQ_STATUS_UPDATE:
6411                 if (!stage_update(view, line))
6412                         return REQ_NONE;
6413                 break;
6415         case REQ_STATUS_REVERT:
6416                 if (!stage_revert(view, line))
6417                         return REQ_NONE;
6418                 break;
6420         case REQ_STAGE_NEXT:
6421                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6422                         report("File is untracked; press %s to add",
6423                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6424                         return REQ_NONE;
6425                 }
6426                 stage_next(view, line);
6427                 return REQ_NONE;
6429         case REQ_EDIT:
6430                 if (!stage_status.new.name[0])
6431                         return request;
6432                 if (stage_status.status == 'D') {
6433                         report("File has been deleted.");
6434                         return REQ_NONE;
6435                 }
6437                 open_editor(stage_status.new.name);
6438                 break;
6440         case REQ_REFRESH:
6441                 /* Reload everything ... */
6442                 break;
6444         case REQ_VIEW_BLAME:
6445                 if (stage_status.new.name[0]) {
6446                         string_copy(opt_file, stage_status.new.name);
6447                         opt_ref[0] = 0;
6448                 }
6449                 return request;
6451         case REQ_ENTER:
6452                 return pager_request(view, request, line);
6454         default:
6455                 return request;
6456         }
6458         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6459         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6461         /* Check whether the staged entry still exists, and close the
6462          * stage view if it doesn't. */
6463         if (!status_exists(&stage_status, stage_line_type)) {
6464                 status_restore(VIEW(REQ_VIEW_STATUS));
6465                 return REQ_VIEW_CLOSE;
6466         }
6468         if (stage_line_type == LINE_STAT_UNTRACKED) {
6469                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6470                         report("Cannot display a directory");
6471                         return REQ_NONE;
6472                 }
6474                 if (!prepare_update_file(view, stage_status.new.name)) {
6475                         report("Failed to open file: %s", strerror(errno));
6476                         return REQ_NONE;
6477                 }
6478         }
6479         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6481         return REQ_NONE;
6484 static struct view_ops stage_ops = {
6485         "line",
6486         NULL,
6487         NULL,
6488         pager_read,
6489         pager_draw,
6490         stage_request,
6491         pager_grep,
6492         pager_select,
6493 };
6496 /*
6497  * Revision graph
6498  */
6500 struct commit {
6501         char id[SIZEOF_REV];            /* SHA1 ID. */
6502         char title[128];                /* First line of the commit message. */
6503         const char *author;             /* Author of the commit. */
6504         struct time time;               /* Date from the author ident. */
6505         struct ref_list *refs;          /* Repository references. */
6506         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6507         size_t graph_size;              /* The width of the graph array. */
6508         bool has_parents;               /* Rewritten --parents seen. */
6509 };
6511 /* Size of rev graph with no  "padding" columns */
6512 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6514 struct rev_graph {
6515         struct rev_graph *prev, *next, *parents;
6516         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6517         size_t size;
6518         struct commit *commit;
6519         size_t pos;
6520         unsigned int boundary:1;
6521 };
6523 /* Parents of the commit being visualized. */
6524 static struct rev_graph graph_parents[4];
6526 /* The current stack of revisions on the graph. */
6527 static struct rev_graph graph_stacks[4] = {
6528         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6529         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6530         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6531         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6532 };
6534 static inline bool
6535 graph_parent_is_merge(struct rev_graph *graph)
6537         return graph->parents->size > 1;
6540 static inline void
6541 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6543         struct commit *commit = graph->commit;
6545         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6546                 commit->graph[commit->graph_size++] = symbol;
6549 static void
6550 clear_rev_graph(struct rev_graph *graph)
6552         graph->boundary = 0;
6553         graph->size = graph->pos = 0;
6554         graph->commit = NULL;
6555         memset(graph->parents, 0, sizeof(*graph->parents));
6558 static void
6559 done_rev_graph(struct rev_graph *graph)
6561         if (graph_parent_is_merge(graph) &&
6562             graph->pos < graph->size - 1 &&
6563             graph->next->size == graph->size + graph->parents->size - 1) {
6564                 size_t i = graph->pos + graph->parents->size - 1;
6566                 graph->commit->graph_size = i * 2;
6567                 while (i < graph->next->size - 1) {
6568                         append_to_rev_graph(graph, ' ');
6569                         append_to_rev_graph(graph, '\\');
6570                         i++;
6571                 }
6572         }
6574         clear_rev_graph(graph);
6577 static void
6578 push_rev_graph(struct rev_graph *graph, const char *parent)
6580         int i;
6582         /* "Collapse" duplicate parents lines.
6583          *
6584          * FIXME: This needs to also update update the drawn graph but
6585          * for now it just serves as a method for pruning graph lines. */
6586         for (i = 0; i < graph->size; i++)
6587                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6588                         return;
6590         if (graph->size < SIZEOF_REVITEMS) {
6591                 string_copy_rev(graph->rev[graph->size++], parent);
6592         }
6595 static chtype
6596 get_rev_graph_symbol(struct rev_graph *graph)
6598         chtype symbol;
6600         if (graph->boundary)
6601                 symbol = REVGRAPH_BOUND;
6602         else if (graph->parents->size == 0)
6603                 symbol = REVGRAPH_INIT;
6604         else if (graph_parent_is_merge(graph))
6605                 symbol = REVGRAPH_MERGE;
6606         else if (graph->pos >= graph->size)
6607                 symbol = REVGRAPH_BRANCH;
6608         else
6609                 symbol = REVGRAPH_COMMIT;
6611         return symbol;
6614 static void
6615 draw_rev_graph(struct rev_graph *graph)
6617         struct rev_filler {
6618                 chtype separator, line;
6619         };
6620         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6621         static struct rev_filler fillers[] = {
6622                 { ' ',  '|' },
6623                 { '`',  '.' },
6624                 { '\'', ' ' },
6625                 { '/',  ' ' },
6626         };
6627         chtype symbol = get_rev_graph_symbol(graph);
6628         struct rev_filler *filler;
6629         size_t i;
6631         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6632         filler = &fillers[DEFAULT];
6634         for (i = 0; i < graph->pos; i++) {
6635                 append_to_rev_graph(graph, filler->line);
6636                 if (graph_parent_is_merge(graph->prev) &&
6637                     graph->prev->pos == i)
6638                         filler = &fillers[RSHARP];
6640                 append_to_rev_graph(graph, filler->separator);
6641         }
6643         /* Place the symbol for this revision. */
6644         append_to_rev_graph(graph, symbol);
6646         if (graph->prev->size > graph->size)
6647                 filler = &fillers[RDIAG];
6648         else
6649                 filler = &fillers[DEFAULT];
6651         i++;
6653         for (; i < graph->size; i++) {
6654                 append_to_rev_graph(graph, filler->separator);
6655                 append_to_rev_graph(graph, filler->line);
6656                 if (graph_parent_is_merge(graph->prev) &&
6657                     i < graph->prev->pos + graph->parents->size)
6658                         filler = &fillers[RSHARP];
6659                 if (graph->prev->size > graph->size)
6660                         filler = &fillers[LDIAG];
6661         }
6663         if (graph->prev->size > graph->size) {
6664                 append_to_rev_graph(graph, filler->separator);
6665                 if (filler->line != ' ')
6666                         append_to_rev_graph(graph, filler->line);
6667         }
6670 /* Prepare the next rev graph */
6671 static void
6672 prepare_rev_graph(struct rev_graph *graph)
6674         size_t i;
6676         /* First, traverse all lines of revisions up to the active one. */
6677         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6678                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6679                         break;
6681                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6682         }
6684         /* Interleave the new revision parent(s). */
6685         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6686                 push_rev_graph(graph->next, graph->parents->rev[i]);
6688         /* Lastly, put any remaining revisions. */
6689         for (i = graph->pos + 1; i < graph->size; i++)
6690                 push_rev_graph(graph->next, graph->rev[i]);
6693 static void
6694 update_rev_graph(struct view *view, struct rev_graph *graph)
6696         /* If this is the finalizing update ... */
6697         if (graph->commit)
6698                 prepare_rev_graph(graph);
6700         /* Graph visualization needs a one rev look-ahead,
6701          * so the first update doesn't visualize anything. */
6702         if (!graph->prev->commit)
6703                 return;
6705         if (view->lines > 2)
6706                 view->line[view->lines - 3].dirty = 1;
6707         if (view->lines > 1)
6708                 view->line[view->lines - 2].dirty = 1;
6709         draw_rev_graph(graph->prev);
6710         done_rev_graph(graph->prev->prev);
6714 /*
6715  * Main view backend
6716  */
6718 static const char *main_argv[SIZEOF_ARG] = {
6719         "git", "log", "--no-color", "--pretty=raw", "--parents",
6720                       "--topo-order", "%(head)", NULL
6721 };
6723 static bool
6724 main_draw(struct view *view, struct line *line, unsigned int lineno)
6726         struct commit *commit = line->data;
6728         if (!commit->author)
6729                 return FALSE;
6731         if (opt_date && draw_date(view, &commit->time))
6732                 return TRUE;
6734         if (opt_author && draw_author(view, commit->author))
6735                 return TRUE;
6737         if (opt_rev_graph && commit->graph_size &&
6738             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6739                 return TRUE;
6741         if (opt_show_refs && commit->refs) {
6742                 size_t i;
6744                 for (i = 0; i < commit->refs->size; i++) {
6745                         struct ref *ref = commit->refs->refs[i];
6746                         enum line_type type;
6748                         if (ref->head)
6749                                 type = LINE_MAIN_HEAD;
6750                         else if (ref->ltag)
6751                                 type = LINE_MAIN_LOCAL_TAG;
6752                         else if (ref->tag)
6753                                 type = LINE_MAIN_TAG;
6754                         else if (ref->tracked)
6755                                 type = LINE_MAIN_TRACKED;
6756                         else if (ref->remote)
6757                                 type = LINE_MAIN_REMOTE;
6758                         else
6759                                 type = LINE_MAIN_REF;
6761                         if (draw_text(view, type, "[", TRUE) ||
6762                             draw_text(view, type, ref->name, TRUE) ||
6763                             draw_text(view, type, "]", TRUE))
6764                                 return TRUE;
6766                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6767                                 return TRUE;
6768                 }
6769         }
6771         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6772         return TRUE;
6775 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6776 static bool
6777 main_read(struct view *view, char *line)
6779         static struct rev_graph *graph = graph_stacks;
6780         enum line_type type;
6781         struct commit *commit;
6783         if (!line) {
6784                 int i;
6786                 if (!view->lines && !view->parent)
6787                         die("No revisions match the given arguments.");
6788                 if (view->lines > 0) {
6789                         commit = view->line[view->lines - 1].data;
6790                         view->line[view->lines - 1].dirty = 1;
6791                         if (!commit->author) {
6792                                 view->lines--;
6793                                 free(commit);
6794                                 graph->commit = NULL;
6795                         }
6796                 }
6797                 update_rev_graph(view, graph);
6799                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6800                         clear_rev_graph(&graph_stacks[i]);
6801                 return TRUE;
6802         }
6804         type = get_line_type(line);
6805         if (type == LINE_COMMIT) {
6806                 commit = calloc(1, sizeof(struct commit));
6807                 if (!commit)
6808                         return FALSE;
6810                 line += STRING_SIZE("commit ");
6811                 if (*line == '-') {
6812                         graph->boundary = 1;
6813                         line++;
6814                 }
6816                 string_copy_rev(commit->id, line);
6817                 commit->refs = get_ref_list(commit->id);
6818                 graph->commit = commit;
6819                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6821                 while ((line = strchr(line, ' '))) {
6822                         line++;
6823                         push_rev_graph(graph->parents, line);
6824                         commit->has_parents = TRUE;
6825                 }
6826                 return TRUE;
6827         }
6829         if (!view->lines)
6830                 return TRUE;
6831         commit = view->line[view->lines - 1].data;
6833         switch (type) {
6834         case LINE_PARENT:
6835                 if (commit->has_parents)
6836                         break;
6837                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6838                 break;
6840         case LINE_AUTHOR:
6841                 parse_author_line(line + STRING_SIZE("author "),
6842                                   &commit->author, &commit->time);
6843                 update_rev_graph(view, graph);
6844                 graph = graph->next;
6845                 break;
6847         default:
6848                 /* Fill in the commit title if it has not already been set. */
6849                 if (commit->title[0])
6850                         break;
6852                 /* Require titles to start with a non-space character at the
6853                  * offset used by git log. */
6854                 if (strncmp(line, "    ", 4))
6855                         break;
6856                 line += 4;
6857                 /* Well, if the title starts with a whitespace character,
6858                  * try to be forgiving.  Otherwise we end up with no title. */
6859                 while (isspace(*line))
6860                         line++;
6861                 if (*line == '\0')
6862                         break;
6863                 /* FIXME: More graceful handling of titles; append "..." to
6864                  * shortened titles, etc. */
6866                 string_expand(commit->title, sizeof(commit->title), line, 1);
6867                 view->line[view->lines - 1].dirty = 1;
6868         }
6870         return TRUE;
6873 static enum request
6874 main_request(struct view *view, enum request request, struct line *line)
6876         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6878         switch (request) {
6879         case REQ_ENTER:
6880                 open_view(view, REQ_VIEW_DIFF, flags);
6881                 break;
6882         case REQ_REFRESH:
6883                 load_refs();
6884                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6885                 break;
6886         default:
6887                 return request;
6888         }
6890         return REQ_NONE;
6893 static bool
6894 grep_refs(struct ref_list *list, regex_t *regex)
6896         regmatch_t pmatch;
6897         size_t i;
6899         if (!opt_show_refs || !list)
6900                 return FALSE;
6902         for (i = 0; i < list->size; i++) {
6903                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6904                         return TRUE;
6905         }
6907         return FALSE;
6910 static bool
6911 main_grep(struct view *view, struct line *line)
6913         struct commit *commit = line->data;
6914         const char *text[] = {
6915                 commit->title,
6916                 opt_author ? commit->author : "",
6917                 mkdate(&commit->time, opt_date),
6918                 NULL
6919         };
6921         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6924 static void
6925 main_select(struct view *view, struct line *line)
6927         struct commit *commit = line->data;
6929         string_copy_rev(view->ref, commit->id);
6930         string_copy_rev(ref_commit, view->ref);
6933 static struct view_ops main_ops = {
6934         "commit",
6935         main_argv,
6936         NULL,
6937         main_read,
6938         main_draw,
6939         main_request,
6940         main_grep,
6941         main_select,
6942 };
6945 /*
6946  * Status management
6947  */
6949 /* Whether or not the curses interface has been initialized. */
6950 static bool cursed = FALSE;
6952 /* Terminal hacks and workarounds. */
6953 static bool use_scroll_redrawwin;
6954 static bool use_scroll_status_wclear;
6956 /* The status window is used for polling keystrokes. */
6957 static WINDOW *status_win;
6959 /* Reading from the prompt? */
6960 static bool input_mode = FALSE;
6962 static bool status_empty = FALSE;
6964 /* Update status and title window. */
6965 static void
6966 report(const char *msg, ...)
6968         struct view *view = display[current_view];
6970         if (input_mode)
6971                 return;
6973         if (!view) {
6974                 char buf[SIZEOF_STR];
6975                 va_list args;
6977                 va_start(args, msg);
6978                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6979                         buf[sizeof(buf) - 1] = 0;
6980                         buf[sizeof(buf) - 2] = '.';
6981                         buf[sizeof(buf) - 3] = '.';
6982                         buf[sizeof(buf) - 4] = '.';
6983                 }
6984                 va_end(args);
6985                 die("%s", buf);
6986         }
6988         if (!status_empty || *msg) {
6989                 va_list args;
6991                 va_start(args, msg);
6993                 wmove(status_win, 0, 0);
6994                 if (view->has_scrolled && use_scroll_status_wclear)
6995                         wclear(status_win);
6996                 if (*msg) {
6997                         vwprintw(status_win, msg, args);
6998                         status_empty = FALSE;
6999                 } else {
7000                         status_empty = TRUE;
7001                 }
7002                 wclrtoeol(status_win);
7003                 wnoutrefresh(status_win);
7005                 va_end(args);
7006         }
7008         update_view_title(view);
7011 static void
7012 init_display(void)
7014         const char *term;
7015         int x, y;
7017         /* Initialize the curses library */
7018         if (isatty(STDIN_FILENO)) {
7019                 cursed = !!initscr();
7020                 opt_tty = stdin;
7021         } else {
7022                 /* Leave stdin and stdout alone when acting as a pager. */
7023                 opt_tty = fopen("/dev/tty", "r+");
7024                 if (!opt_tty)
7025                         die("Failed to open /dev/tty");
7026                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7027         }
7029         if (!cursed)
7030                 die("Failed to initialize curses");
7032         nonl();         /* Disable conversion and detect newlines from input. */
7033         cbreak();       /* Take input chars one at a time, no wait for \n */
7034         noecho();       /* Don't echo input */
7035         leaveok(stdscr, FALSE);
7037         if (has_colors())
7038                 init_colors();
7040         getmaxyx(stdscr, y, x);
7041         status_win = newwin(1, 0, y - 1, 0);
7042         if (!status_win)
7043                 die("Failed to create status window");
7045         /* Enable keyboard mapping */
7046         keypad(status_win, TRUE);
7047         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7049         TABSIZE = opt_tab_size;
7051         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7052         if (term && !strcmp(term, "gnome-terminal")) {
7053                 /* In the gnome-terminal-emulator, the message from
7054                  * scrolling up one line when impossible followed by
7055                  * scrolling down one line causes corruption of the
7056                  * status line. This is fixed by calling wclear. */
7057                 use_scroll_status_wclear = TRUE;
7058                 use_scroll_redrawwin = FALSE;
7060         } else if (term && !strcmp(term, "xrvt-xpm")) {
7061                 /* No problems with full optimizations in xrvt-(unicode)
7062                  * and aterm. */
7063                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7065         } else {
7066                 /* When scrolling in (u)xterm the last line in the
7067                  * scrolling direction will update slowly. */
7068                 use_scroll_redrawwin = TRUE;
7069                 use_scroll_status_wclear = FALSE;
7070         }
7073 static int
7074 get_input(int prompt_position)
7076         struct view *view;
7077         int i, key, cursor_y, cursor_x;
7078         bool loading = FALSE;
7080         if (prompt_position)
7081                 input_mode = TRUE;
7083         while (TRUE) {
7084                 foreach_view (view, i) {
7085                         update_view(view);
7086                         if (view_is_displayed(view) && view->has_scrolled &&
7087                             use_scroll_redrawwin)
7088                                 redrawwin(view->win);
7089                         view->has_scrolled = FALSE;
7090                         if (view->pipe)
7091                                 loading = TRUE;
7092                 }
7094                 /* Update the cursor position. */
7095                 if (prompt_position) {
7096                         getbegyx(status_win, cursor_y, cursor_x);
7097                         cursor_x = prompt_position;
7098                 } else {
7099                         view = display[current_view];
7100                         getbegyx(view->win, cursor_y, cursor_x);
7101                         cursor_x = view->width - 1;
7102                         cursor_y += view->lineno - view->offset;
7103                 }
7104                 setsyx(cursor_y, cursor_x);
7106                 /* Refresh, accept single keystroke of input */
7107                 doupdate();
7108                 nodelay(status_win, loading);
7109                 key = wgetch(status_win);
7111                 /* wgetch() with nodelay() enabled returns ERR when
7112                  * there's no input. */
7113                 if (key == ERR) {
7115                 } else if (key == KEY_RESIZE) {
7116                         int height, width;
7118                         getmaxyx(stdscr, height, width);
7120                         wresize(status_win, 1, width);
7121                         mvwin(status_win, height - 1, 0);
7122                         wnoutrefresh(status_win);
7123                         resize_display();
7124                         redraw_display(TRUE);
7126                 } else {
7127                         input_mode = FALSE;
7128                         return key;
7129                 }
7130         }
7133 static char *
7134 prompt_input(const char *prompt, input_handler handler, void *data)
7136         enum input_status status = INPUT_OK;
7137         static char buf[SIZEOF_STR];
7138         size_t pos = 0;
7140         buf[pos] = 0;
7142         while (status == INPUT_OK || status == INPUT_SKIP) {
7143                 int key;
7145                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7146                 wclrtoeol(status_win);
7148                 key = get_input(pos + 1);
7149                 switch (key) {
7150                 case KEY_RETURN:
7151                 case KEY_ENTER:
7152                 case '\n':
7153                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7154                         break;
7156                 case KEY_BACKSPACE:
7157                         if (pos > 0)
7158                                 buf[--pos] = 0;
7159                         else
7160                                 status = INPUT_CANCEL;
7161                         break;
7163                 case KEY_ESC:
7164                         status = INPUT_CANCEL;
7165                         break;
7167                 default:
7168                         if (pos >= sizeof(buf)) {
7169                                 report("Input string too long");
7170                                 return NULL;
7171                         }
7173                         status = handler(data, buf, key);
7174                         if (status == INPUT_OK)
7175                                 buf[pos++] = (char) key;
7176                 }
7177         }
7179         /* Clear the status window */
7180         status_empty = FALSE;
7181         report("");
7183         if (status == INPUT_CANCEL)
7184                 return NULL;
7186         buf[pos++] = 0;
7188         return buf;
7191 static enum input_status
7192 prompt_yesno_handler(void *data, char *buf, int c)
7194         if (c == 'y' || c == 'Y')
7195                 return INPUT_STOP;
7196         if (c == 'n' || c == 'N')
7197                 return INPUT_CANCEL;
7198         return INPUT_SKIP;
7201 static bool
7202 prompt_yesno(const char *prompt)
7204         char prompt2[SIZEOF_STR];
7206         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7207                 return FALSE;
7209         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7212 static enum input_status
7213 read_prompt_handler(void *data, char *buf, int c)
7215         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7218 static char *
7219 read_prompt(const char *prompt)
7221         return prompt_input(prompt, read_prompt_handler, NULL);
7224 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7226         enum input_status status = INPUT_OK;
7227         int size = 0;
7229         while (items[size].text)
7230                 size++;
7232         while (status == INPUT_OK) {
7233                 const struct menu_item *item = &items[*selected];
7234                 int key;
7235                 int i;
7237                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7238                           prompt, *selected + 1, size);
7239                 if (item->hotkey)
7240                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7241                 wprintw(status_win, "%s", item->text);
7242                 wclrtoeol(status_win);
7244                 key = get_input(COLS - 1);
7245                 switch (key) {
7246                 case KEY_RETURN:
7247                 case KEY_ENTER:
7248                 case '\n':
7249                         status = INPUT_STOP;
7250                         break;
7252                 case KEY_LEFT:
7253                 case KEY_UP:
7254                         *selected = *selected - 1;
7255                         if (*selected < 0)
7256                                 *selected = size - 1;
7257                         break;
7259                 case KEY_RIGHT:
7260                 case KEY_DOWN:
7261                         *selected = (*selected + 1) % size;
7262                         break;
7264                 case KEY_ESC:
7265                         status = INPUT_CANCEL;
7266                         break;
7268                 default:
7269                         for (i = 0; items[i].text; i++)
7270                                 if (items[i].hotkey == key) {
7271                                         *selected = i;
7272                                         status = INPUT_STOP;
7273                                         break;
7274                                 }
7275                 }
7276         }
7278         /* Clear the status window */
7279         status_empty = FALSE;
7280         report("");
7282         return status != INPUT_CANCEL;
7285 /*
7286  * Repository properties
7287  */
7289 static struct ref **refs = NULL;
7290 static size_t refs_size = 0;
7291 static struct ref *refs_head = NULL;
7293 static struct ref_list **ref_lists = NULL;
7294 static size_t ref_lists_size = 0;
7296 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7297 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7298 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7300 static int
7301 compare_refs(const void *ref1_, const void *ref2_)
7303         const struct ref *ref1 = *(const struct ref **)ref1_;
7304         const struct ref *ref2 = *(const struct ref **)ref2_;
7306         if (ref1->tag != ref2->tag)
7307                 return ref2->tag - ref1->tag;
7308         if (ref1->ltag != ref2->ltag)
7309                 return ref2->ltag - ref2->ltag;
7310         if (ref1->head != ref2->head)
7311                 return ref2->head - ref1->head;
7312         if (ref1->tracked != ref2->tracked)
7313                 return ref2->tracked - ref1->tracked;
7314         if (ref1->remote != ref2->remote)
7315                 return ref2->remote - ref1->remote;
7316         return strcmp(ref1->name, ref2->name);
7319 static void
7320 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7322         size_t i;
7324         for (i = 0; i < refs_size; i++)
7325                 if (!visitor(data, refs[i]))
7326                         break;
7329 static struct ref *
7330 get_ref_head()
7332         return refs_head;
7335 static struct ref_list *
7336 get_ref_list(const char *id)
7338         struct ref_list *list;
7339         size_t i;
7341         for (i = 0; i < ref_lists_size; i++)
7342                 if (!strcmp(id, ref_lists[i]->id))
7343                         return ref_lists[i];
7345         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7346                 return NULL;
7347         list = calloc(1, sizeof(*list));
7348         if (!list)
7349                 return NULL;
7351         for (i = 0; i < refs_size; i++) {
7352                 if (!strcmp(id, refs[i]->id) &&
7353                     realloc_refs_list(&list->refs, list->size, 1))
7354                         list->refs[list->size++] = refs[i];
7355         }
7357         if (!list->refs) {
7358                 free(list);
7359                 return NULL;
7360         }
7362         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7363         ref_lists[ref_lists_size++] = list;
7364         return list;
7367 static int
7368 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7370         struct ref *ref = NULL;
7371         bool tag = FALSE;
7372         bool ltag = FALSE;
7373         bool remote = FALSE;
7374         bool tracked = FALSE;
7375         bool head = FALSE;
7376         int from = 0, to = refs_size - 1;
7378         if (!prefixcmp(name, "refs/tags/")) {
7379                 if (!suffixcmp(name, namelen, "^{}")) {
7380                         namelen -= 3;
7381                         name[namelen] = 0;
7382                 } else {
7383                         ltag = TRUE;
7384                 }
7386                 tag = TRUE;
7387                 namelen -= STRING_SIZE("refs/tags/");
7388                 name    += STRING_SIZE("refs/tags/");
7390         } else if (!prefixcmp(name, "refs/remotes/")) {
7391                 remote = TRUE;
7392                 namelen -= STRING_SIZE("refs/remotes/");
7393                 name    += STRING_SIZE("refs/remotes/");
7394                 tracked  = !strcmp(opt_remote, name);
7396         } else if (!prefixcmp(name, "refs/heads/")) {
7397                 namelen -= STRING_SIZE("refs/heads/");
7398                 name    += STRING_SIZE("refs/heads/");
7399                 if (!strncmp(opt_head, name, namelen))
7400                         return OK;
7402         } else if (!strcmp(name, "HEAD")) {
7403                 head     = TRUE;
7404                 if (*opt_head) {
7405                         namelen  = strlen(opt_head);
7406                         name     = opt_head;
7407                 }
7408         }
7410         /* If we are reloading or it's an annotated tag, replace the
7411          * previous SHA1 with the resolved commit id; relies on the fact
7412          * git-ls-remote lists the commit id of an annotated tag right
7413          * before the commit id it points to. */
7414         while (from <= to) {
7415                 size_t pos = (to + from) / 2;
7416                 int cmp = strcmp(name, refs[pos]->name);
7418                 if (!cmp) {
7419                         ref = refs[pos];
7420                         break;
7421                 }
7423                 if (cmp < 0)
7424                         to = pos - 1;
7425                 else
7426                         from = pos + 1;
7427         }
7429         if (!ref) {
7430                 if (!realloc_refs(&refs, refs_size, 1))
7431                         return ERR;
7432                 ref = calloc(1, sizeof(*ref) + namelen);
7433                 if (!ref)
7434                         return ERR;
7435                 memmove(refs + from + 1, refs + from,
7436                         (refs_size - from) * sizeof(*refs));
7437                 refs[from] = ref;
7438                 strncpy(ref->name, name, namelen);
7439                 refs_size++;
7440         }
7442         ref->head = head;
7443         ref->tag = tag;
7444         ref->ltag = ltag;
7445         ref->remote = remote;
7446         ref->tracked = tracked;
7447         string_copy_rev(ref->id, id);
7449         if (head)
7450                 refs_head = ref;
7451         return OK;
7454 static int
7455 load_refs(void)
7457         const char *head_argv[] = {
7458                 "git", "symbolic-ref", "HEAD", NULL
7459         };
7460         static const char *ls_remote_argv[SIZEOF_ARG] = {
7461                 "git", "ls-remote", opt_git_dir, NULL
7462         };
7463         static bool init = FALSE;
7464         size_t i;
7466         if (!init) {
7467                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7468                         die("TIG_LS_REMOTE contains too many arguments");
7469                 init = TRUE;
7470         }
7472         if (!*opt_git_dir)
7473                 return OK;
7475         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7476             !prefixcmp(opt_head, "refs/heads/")) {
7477                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7479                 memmove(opt_head, offset, strlen(offset) + 1);
7480         }
7482         refs_head = NULL;
7483         for (i = 0; i < refs_size; i++)
7484                 refs[i]->id[0] = 0;
7486         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7487                 return ERR;
7489         /* Update the ref lists to reflect changes. */
7490         for (i = 0; i < ref_lists_size; i++) {
7491                 struct ref_list *list = ref_lists[i];
7492                 size_t old, new;
7494                 for (old = new = 0; old < list->size; old++)
7495                         if (!strcmp(list->id, list->refs[old]->id))
7496                                 list->refs[new++] = list->refs[old];
7497                 list->size = new;
7498         }
7500         return OK;
7503 static void
7504 set_remote_branch(const char *name, const char *value, size_t valuelen)
7506         if (!strcmp(name, ".remote")) {
7507                 string_ncopy(opt_remote, value, valuelen);
7509         } else if (*opt_remote && !strcmp(name, ".merge")) {
7510                 size_t from = strlen(opt_remote);
7512                 if (!prefixcmp(value, "refs/heads/"))
7513                         value += STRING_SIZE("refs/heads/");
7515                 if (!string_format_from(opt_remote, &from, "/%s", value))
7516                         opt_remote[0] = 0;
7517         }
7520 static void
7521 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7523         const char *argv[SIZEOF_ARG] = { name, "=" };
7524         int argc = 1 + (cmd == option_set_command);
7525         int error = ERR;
7527         if (!argv_from_string(argv, &argc, value))
7528                 config_msg = "Too many option arguments";
7529         else
7530                 error = cmd(argc, argv);
7532         if (error == ERR)
7533                 warn("Option 'tig.%s': %s", name, config_msg);
7536 static bool
7537 set_environment_variable(const char *name, const char *value)
7539         size_t len = strlen(name) + 1 + strlen(value) + 1;
7540         char *env = malloc(len);
7542         if (env &&
7543             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7544             putenv(env) == 0)
7545                 return TRUE;
7546         free(env);
7547         return FALSE;
7550 static void
7551 set_work_tree(const char *value)
7553         char cwd[SIZEOF_STR];
7555         if (!getcwd(cwd, sizeof(cwd)))
7556                 die("Failed to get cwd path: %s", strerror(errno));
7557         if (chdir(opt_git_dir) < 0)
7558                 die("Failed to chdir(%s): %s", strerror(errno));
7559         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7560                 die("Failed to get git path: %s", strerror(errno));
7561         if (chdir(cwd) < 0)
7562                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7563         if (chdir(value) < 0)
7564                 die("Failed to chdir(%s): %s", value, strerror(errno));
7565         if (!getcwd(cwd, sizeof(cwd)))
7566                 die("Failed to get cwd path: %s", strerror(errno));
7567         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7568                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7569         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7570                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7571         opt_is_inside_work_tree = TRUE;
7574 static int
7575 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7577         if (!strcmp(name, "i18n.commitencoding"))
7578                 string_ncopy(opt_encoding, value, valuelen);
7580         else if (!strcmp(name, "core.editor"))
7581                 string_ncopy(opt_editor, value, valuelen);
7583         else if (!strcmp(name, "core.worktree"))
7584                 set_work_tree(value);
7586         else if (!prefixcmp(name, "tig.color."))
7587                 set_repo_config_option(name + 10, value, option_color_command);
7589         else if (!prefixcmp(name, "tig.bind."))
7590                 set_repo_config_option(name + 9, value, option_bind_command);
7592         else if (!prefixcmp(name, "tig."))
7593                 set_repo_config_option(name + 4, value, option_set_command);
7595         else if (*opt_head && !prefixcmp(name, "branch.") &&
7596                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7597                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7599         return OK;
7602 static int
7603 load_git_config(void)
7605         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7607         return io_run_load(config_list_argv, "=", read_repo_config_option);
7610 static int
7611 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7613         if (!opt_git_dir[0]) {
7614                 string_ncopy(opt_git_dir, name, namelen);
7616         } else if (opt_is_inside_work_tree == -1) {
7617                 /* This can be 3 different values depending on the
7618                  * version of git being used. If git-rev-parse does not
7619                  * understand --is-inside-work-tree it will simply echo
7620                  * the option else either "true" or "false" is printed.
7621                  * Default to true for the unknown case. */
7622                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7624         } else if (*name == '.') {
7625                 string_ncopy(opt_cdup, name, namelen);
7627         } else {
7628                 string_ncopy(opt_prefix, name, namelen);
7629         }
7631         return OK;
7634 static int
7635 load_repo_info(void)
7637         const char *rev_parse_argv[] = {
7638                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7639                         "--show-cdup", "--show-prefix", NULL
7640         };
7642         return io_run_load(rev_parse_argv, "=", read_repo_info);
7646 /*
7647  * Main
7648  */
7650 static const char usage[] =
7651 "tig " TIG_VERSION " (" __DATE__ ")\n"
7652 "\n"
7653 "Usage: tig        [options] [revs] [--] [paths]\n"
7654 "   or: tig show   [options] [revs] [--] [paths]\n"
7655 "   or: tig blame  [rev] path\n"
7656 "   or: tig status\n"
7657 "   or: tig <      [git command output]\n"
7658 "\n"
7659 "Options:\n"
7660 "  -v, --version   Show version and exit\n"
7661 "  -h, --help      Show help message and exit";
7663 static void __NORETURN
7664 quit(int sig)
7666         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7667         if (cursed)
7668                 endwin();
7669         exit(0);
7672 static void __NORETURN
7673 die(const char *err, ...)
7675         va_list args;
7677         endwin();
7679         va_start(args, err);
7680         fputs("tig: ", stderr);
7681         vfprintf(stderr, err, args);
7682         fputs("\n", stderr);
7683         va_end(args);
7685         exit(1);
7688 static void
7689 warn(const char *msg, ...)
7691         va_list args;
7693         va_start(args, msg);
7694         fputs("tig warning: ", stderr);
7695         vfprintf(stderr, msg, args);
7696         fputs("\n", stderr);
7697         va_end(args);
7700 static enum request
7701 parse_options(int argc, const char *argv[])
7703         enum request request = REQ_VIEW_MAIN;
7704         const char *subcommand;
7705         bool seen_dashdash = FALSE;
7706         /* XXX: This is vulnerable to the user overriding options
7707          * required for the main view parser. */
7708         const char *custom_argv[SIZEOF_ARG] = {
7709                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7710                         "--topo-order", NULL
7711         };
7712         int i, j = 6;
7714         if (!isatty(STDIN_FILENO)) {
7715                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7716                 return REQ_VIEW_PAGER;
7717         }
7719         if (argc <= 1)
7720                 return REQ_NONE;
7722         subcommand = argv[1];
7723         if (!strcmp(subcommand, "status")) {
7724                 if (argc > 2)
7725                         warn("ignoring arguments after `%s'", subcommand);
7726                 return REQ_VIEW_STATUS;
7728         } else if (!strcmp(subcommand, "blame")) {
7729                 if (argc <= 2 || argc > 4)
7730                         die("invalid number of options to blame\n\n%s", usage);
7732                 i = 2;
7733                 if (argc == 4) {
7734                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7735                         i++;
7736                 }
7738                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7739                 return REQ_VIEW_BLAME;
7741         } else if (!strcmp(subcommand, "show")) {
7742                 request = REQ_VIEW_DIFF;
7744         } else {
7745                 subcommand = NULL;
7746         }
7748         if (subcommand) {
7749                 custom_argv[1] = subcommand;
7750                 j = 2;
7751         }
7753         for (i = 1 + !!subcommand; i < argc; i++) {
7754                 const char *opt = argv[i];
7756                 if (seen_dashdash || !strcmp(opt, "--")) {
7757                         seen_dashdash = TRUE;
7759                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7760                         printf("tig version %s\n", TIG_VERSION);
7761                         quit(0);
7763                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7764                         printf("%s\n", usage);
7765                         quit(0);
7766                 }
7768                 custom_argv[j++] = opt;
7769                 if (j >= ARRAY_SIZE(custom_argv))
7770                         die("command too long");
7771         }
7773         if (!prepare_update(VIEW(request), custom_argv, NULL))
7774                 die("Failed to format arguments");
7776         return request;
7779 int
7780 main(int argc, const char *argv[])
7782         const char *codeset = "UTF-8";
7783         enum request request = parse_options(argc, argv);
7784         struct view *view;
7785         size_t i;
7787         signal(SIGINT, quit);
7788         signal(SIGPIPE, SIG_IGN);
7790         if (setlocale(LC_ALL, "")) {
7791                 codeset = nl_langinfo(CODESET);
7792         }
7794         if (load_repo_info() == ERR)
7795                 die("Failed to load repo info.");
7797         if (load_options() == ERR)
7798                 die("Failed to load user config.");
7800         if (load_git_config() == ERR)
7801                 die("Failed to load repo config.");
7803         /* Require a git repository unless when running in pager mode. */
7804         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7805                 die("Not a git repository");
7807         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7808                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7809                 if (opt_iconv_in == ICONV_NONE)
7810                         die("Failed to initialize character set conversion");
7811         }
7813         if (codeset && strcmp(codeset, "UTF-8")) {
7814                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7815                 if (opt_iconv_out == ICONV_NONE)
7816                         die("Failed to initialize character set conversion");
7817         }
7819         if (load_refs() == ERR)
7820                 die("Failed to load refs.");
7822         foreach_view (view, i)
7823                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7824                         die("Too many arguments in the `%s` environment variable",
7825                             view->cmd_env);
7827         init_display();
7829         if (request != REQ_NONE)
7830                 open_view(NULL, request, OPEN_PREPARED);
7831         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7833         while (view_driver(display[current_view], request)) {
7834                 int key = get_input(0);
7836                 view = display[current_view];
7837                 request = get_keybinding(view->keymap, key);
7839                 /* Some low-level request handling. This keeps access to
7840                  * status_win restricted. */
7841                 switch (request) {
7842                 case REQ_NONE:
7843                         report("Unknown key, press %s for help",
7844                                get_key(view->keymap, REQ_VIEW_HELP));
7845                         break;
7846                 case REQ_PROMPT:
7847                 {
7848                         char *cmd = read_prompt(":");
7850                         if (cmd && isdigit(*cmd)) {
7851                                 int lineno = view->lineno + 1;
7853                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7854                                         select_view_line(view, lineno - 1);
7855                                         report("");
7856                                 } else {
7857                                         report("Unable to parse '%s' as a line number", cmd);
7858                                 }
7860                         } else if (cmd) {
7861                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7862                                 const char *argv[SIZEOF_ARG] = { "git" };
7863                                 int argc = 1;
7865                                 /* When running random commands, initially show the
7866                                  * command in the title. However, it maybe later be
7867                                  * overwritten if a commit line is selected. */
7868                                 string_ncopy(next->ref, cmd, strlen(cmd));
7870                                 if (!argv_from_string(argv, &argc, cmd)) {
7871                                         report("Too many arguments");
7872                                 } else if (!prepare_update(next, argv, NULL)) {
7873                                         report("Failed to format command");
7874                                 } else {
7875                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7876                                 }
7877                         }
7879                         request = REQ_NONE;
7880                         break;
7881                 }
7882                 case REQ_SEARCH:
7883                 case REQ_SEARCH_BACK:
7884                 {
7885                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7886                         char *search = read_prompt(prompt);
7888                         if (search)
7889                                 string_ncopy(opt_search, search, strlen(search));
7890                         else if (*opt_search)
7891                                 request = request == REQ_SEARCH ?
7892                                         REQ_FIND_NEXT :
7893                                         REQ_FIND_PREV;
7894                         else
7895                                 request = REQ_NONE;
7896                         break;
7897                 }
7898                 default:
7899                         break;
7900                 }
7901         }
7903         quit(0);
7905         return 0;