Code

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