Code

Allow built-in run requests to be unbound
[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(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1283 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1284 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1285 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1286 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1287 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1288 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1289 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1290 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1291 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1292 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1293 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1294 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1295 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1296 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1297 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1298 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1299 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1300 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1301 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1302 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1303 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1304 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1305 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1306 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1307 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1308 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1309 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1310 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1312 enum line_type {
1313 #define LINE(type, line, fg, bg, attr) \
1314         LINE_##type
1315         LINE_INFO,
1316         LINE_NONE
1317 #undef  LINE
1318 };
1320 struct line_info {
1321         const char *name;       /* Option name. */
1322         int namelen;            /* Size of option name. */
1323         const char *line;       /* The start of line to match. */
1324         int linelen;            /* Size of string to match. */
1325         int fg, bg, attr;       /* Color and text attributes for the lines. */
1326 };
1328 static struct line_info line_info[] = {
1329 #define LINE(type, line, fg, bg, attr) \
1330         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1331         LINE_INFO
1332 #undef  LINE
1333 };
1335 static enum line_type
1336 get_line_type(const char *line)
1338         int linelen = strlen(line);
1339         enum line_type type;
1341         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1342                 /* Case insensitive search matches Signed-off-by lines better. */
1343                 if (linelen >= line_info[type].linelen &&
1344                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1345                         return type;
1347         return LINE_DEFAULT;
1350 static inline int
1351 get_line_attr(enum line_type type)
1353         assert(type < ARRAY_SIZE(line_info));
1354         return COLOR_PAIR(type) | line_info[type].attr;
1357 static struct line_info *
1358 get_line_info(const char *name)
1360         size_t namelen = strlen(name);
1361         enum line_type type;
1363         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1364                 if (enum_equals(line_info[type], name, namelen))
1365                         return &line_info[type];
1367         return NULL;
1370 static void
1371 init_colors(void)
1373         int default_bg = line_info[LINE_DEFAULT].bg;
1374         int default_fg = line_info[LINE_DEFAULT].fg;
1375         enum line_type type;
1377         start_color();
1379         if (assume_default_colors(default_fg, default_bg) == ERR) {
1380                 default_bg = COLOR_BLACK;
1381                 default_fg = COLOR_WHITE;
1382         }
1384         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1385                 struct line_info *info = &line_info[type];
1386                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1387                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1389                 init_pair(type, fg, bg);
1390         }
1393 struct line {
1394         enum line_type type;
1396         /* State flags */
1397         unsigned int selected:1;
1398         unsigned int dirty:1;
1399         unsigned int cleareol:1;
1400         unsigned int other:16;
1402         void *data;             /* User data */
1403 };
1406 /*
1407  * Keys
1408  */
1410 struct keybinding {
1411         int alias;
1412         enum request request;
1413 };
1415 static struct keybinding default_keybindings[] = {
1416         /* View switching */
1417         { 'm',          REQ_VIEW_MAIN },
1418         { 'd',          REQ_VIEW_DIFF },
1419         { 'l',          REQ_VIEW_LOG },
1420         { 't',          REQ_VIEW_TREE },
1421         { 'f',          REQ_VIEW_BLOB },
1422         { 'B',          REQ_VIEW_BLAME },
1423         { 'H',          REQ_VIEW_BRANCH },
1424         { 'p',          REQ_VIEW_PAGER },
1425         { 'h',          REQ_VIEW_HELP },
1426         { 'S',          REQ_VIEW_STATUS },
1427         { 'c',          REQ_VIEW_STAGE },
1429         /* View manipulation */
1430         { 'q',          REQ_VIEW_CLOSE },
1431         { KEY_TAB,      REQ_VIEW_NEXT },
1432         { KEY_RETURN,   REQ_ENTER },
1433         { KEY_UP,       REQ_PREVIOUS },
1434         { KEY_DOWN,     REQ_NEXT },
1435         { 'R',          REQ_REFRESH },
1436         { KEY_F(5),     REQ_REFRESH },
1437         { 'O',          REQ_MAXIMIZE },
1439         /* Cursor navigation */
1440         { 'k',          REQ_MOVE_UP },
1441         { 'j',          REQ_MOVE_DOWN },
1442         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1443         { KEY_END,      REQ_MOVE_LAST_LINE },
1444         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1445         { ' ',          REQ_MOVE_PAGE_DOWN },
1446         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1447         { 'b',          REQ_MOVE_PAGE_UP },
1448         { '-',          REQ_MOVE_PAGE_UP },
1450         /* Scrolling */
1451         { KEY_LEFT,     REQ_SCROLL_LEFT },
1452         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1453         { KEY_IC,       REQ_SCROLL_LINE_UP },
1454         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1455         { 'w',          REQ_SCROLL_PAGE_UP },
1456         { 's',          REQ_SCROLL_PAGE_DOWN },
1458         /* Searching */
1459         { '/',          REQ_SEARCH },
1460         { '?',          REQ_SEARCH_BACK },
1461         { 'n',          REQ_FIND_NEXT },
1462         { 'N',          REQ_FIND_PREV },
1464         /* Misc */
1465         { 'Q',          REQ_QUIT },
1466         { 'z',          REQ_STOP_LOADING },
1467         { 'v',          REQ_SHOW_VERSION },
1468         { 'r',          REQ_SCREEN_REDRAW },
1469         { 'o',          REQ_OPTIONS },
1470         { '.',          REQ_TOGGLE_LINENO },
1471         { 'D',          REQ_TOGGLE_DATE },
1472         { 'A',          REQ_TOGGLE_AUTHOR },
1473         { 'g',          REQ_TOGGLE_REV_GRAPH },
1474         { 'F',          REQ_TOGGLE_REFS },
1475         { 'I',          REQ_TOGGLE_SORT_ORDER },
1476         { 'i',          REQ_TOGGLE_SORT_FIELD },
1477         { ':',          REQ_PROMPT },
1478         { 'u',          REQ_STATUS_UPDATE },
1479         { '!',          REQ_STATUS_REVERT },
1480         { 'M',          REQ_STATUS_MERGE },
1481         { '@',          REQ_STAGE_NEXT },
1482         { ',',          REQ_PARENT },
1483         { 'e',          REQ_EDIT },
1484 };
1486 #define KEYMAP_INFO \
1487         KEYMAP_(GENERIC), \
1488         KEYMAP_(MAIN), \
1489         KEYMAP_(DIFF), \
1490         KEYMAP_(LOG), \
1491         KEYMAP_(TREE), \
1492         KEYMAP_(BLOB), \
1493         KEYMAP_(BLAME), \
1494         KEYMAP_(BRANCH), \
1495         KEYMAP_(PAGER), \
1496         KEYMAP_(HELP), \
1497         KEYMAP_(STATUS), \
1498         KEYMAP_(STAGE)
1500 enum keymap {
1501 #define KEYMAP_(name) KEYMAP_##name
1502         KEYMAP_INFO
1503 #undef  KEYMAP_
1504 };
1506 static const struct enum_map keymap_table[] = {
1507 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1508         KEYMAP_INFO
1509 #undef  KEYMAP_
1510 };
1512 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1514 struct keybinding_table {
1515         struct keybinding *data;
1516         size_t size;
1517 };
1519 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1521 static void
1522 add_keybinding(enum keymap keymap, enum request request, int key)
1524         struct keybinding_table *table = &keybindings[keymap];
1526         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1527         if (!table->data)
1528                 die("Failed to allocate keybinding");
1529         table->data[table->size].alias = key;
1530         table->data[table->size++].request = request;
1532         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1533                 int i;
1535                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1536                         if (default_keybindings[i].alias == key)
1537                                 default_keybindings[i].request = REQ_NONE;
1538         }
1541 /* Looks for a key binding first in the given map, then in the generic map, and
1542  * lastly in the default keybindings. */
1543 static enum request
1544 get_keybinding(enum keymap keymap, int key)
1546         size_t i;
1548         for (i = 0; i < keybindings[keymap].size; i++)
1549                 if (keybindings[keymap].data[i].alias == key)
1550                         return keybindings[keymap].data[i].request;
1552         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1553                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1554                         return keybindings[KEYMAP_GENERIC].data[i].request;
1556         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1557                 if (default_keybindings[i].alias == key)
1558                         return default_keybindings[i].request;
1560         return (enum request) key;
1564 struct key {
1565         const char *name;
1566         int value;
1567 };
1569 static const struct key key_table[] = {
1570         { "Enter",      KEY_RETURN },
1571         { "Space",      ' ' },
1572         { "Backspace",  KEY_BACKSPACE },
1573         { "Tab",        KEY_TAB },
1574         { "Escape",     KEY_ESC },
1575         { "Left",       KEY_LEFT },
1576         { "Right",      KEY_RIGHT },
1577         { "Up",         KEY_UP },
1578         { "Down",       KEY_DOWN },
1579         { "Insert",     KEY_IC },
1580         { "Delete",     KEY_DC },
1581         { "Hash",       '#' },
1582         { "Home",       KEY_HOME },
1583         { "End",        KEY_END },
1584         { "PageUp",     KEY_PPAGE },
1585         { "PageDown",   KEY_NPAGE },
1586         { "F1",         KEY_F(1) },
1587         { "F2",         KEY_F(2) },
1588         { "F3",         KEY_F(3) },
1589         { "F4",         KEY_F(4) },
1590         { "F5",         KEY_F(5) },
1591         { "F6",         KEY_F(6) },
1592         { "F7",         KEY_F(7) },
1593         { "F8",         KEY_F(8) },
1594         { "F9",         KEY_F(9) },
1595         { "F10",        KEY_F(10) },
1596         { "F11",        KEY_F(11) },
1597         { "F12",        KEY_F(12) },
1598 };
1600 static int
1601 get_key_value(const char *name)
1603         int i;
1605         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1606                 if (!strcasecmp(key_table[i].name, name))
1607                         return key_table[i].value;
1609         if (strlen(name) == 1 && isprint(*name))
1610                 return (int) *name;
1612         return ERR;
1615 static const char *
1616 get_key_name(int key_value)
1618         static char key_char[] = "'X'";
1619         const char *seq = NULL;
1620         int key;
1622         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1623                 if (key_table[key].value == key_value)
1624                         seq = key_table[key].name;
1626         if (seq == NULL &&
1627             key_value < 127 &&
1628             isprint(key_value)) {
1629                 key_char[1] = (char) key_value;
1630                 seq = key_char;
1631         }
1633         return seq ? seq : "(no key)";
1636 static bool
1637 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1639         const char *sep = *pos > 0 ? ", " : "";
1640         const char *keyname = get_key_name(keybinding->alias);
1642         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1645 static bool
1646 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1647                            enum keymap keymap, bool all)
1649         int i;
1651         for (i = 0; i < keybindings[keymap].size; i++) {
1652                 if (keybindings[keymap].data[i].request == request) {
1653                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1654                                 return FALSE;
1655                         if (!all)
1656                                 break;
1657                 }
1658         }
1660         return TRUE;
1663 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1665 static const char *
1666 get_keys(enum keymap keymap, enum request request, bool all)
1668         static char buf[BUFSIZ];
1669         size_t pos = 0;
1670         int i;
1672         buf[pos] = 0;
1674         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1675                 return "Too many keybindings!";
1676         if (pos > 0 && !all)
1677                 return buf;
1679         if (keymap != KEYMAP_GENERIC) {
1680                 /* Only the generic keymap includes the default keybindings when
1681                  * listing all keys. */
1682                 if (all)
1683                         return buf;
1685                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1686                         return "Too many keybindings!";
1687                 if (pos)
1688                         return buf;
1689         }
1691         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1692                 if (default_keybindings[i].request == request) {
1693                         if (!append_key(buf, &pos, &default_keybindings[i]))
1694                                 return "Too many keybindings!";
1695                         if (!all)
1696                                 return buf;
1697                 }
1698         }
1700         return buf;
1703 struct run_request {
1704         enum keymap keymap;
1705         int key;
1706         const char *argv[SIZEOF_ARG];
1707 };
1709 static struct run_request *run_request;
1710 static size_t run_requests;
1712 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1714 static enum request
1715 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1717         struct run_request *req;
1719         if (argc >= ARRAY_SIZE(req->argv) - 1)
1720                 return REQ_NONE;
1722         if (!realloc_run_requests(&run_request, run_requests, 1))
1723                 return REQ_NONE;
1725         req = &run_request[run_requests];
1726         req->keymap = keymap;
1727         req->key = key;
1728         req->argv[0] = NULL;
1730         if (!format_argv(req->argv, argv, FORMAT_NONE))
1731                 return REQ_NONE;
1733         return REQ_NONE + ++run_requests;
1736 static struct run_request *
1737 get_run_request(enum request request)
1739         if (request <= REQ_NONE)
1740                 return NULL;
1741         return &run_request[request - REQ_NONE - 1];
1744 static void
1745 add_builtin_run_requests(void)
1747         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1748         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1749         const char *commit[] = { "git", "commit", NULL };
1750         const char *gc[] = { "git", "gc", NULL };
1751         struct {
1752                 enum keymap keymap;
1753                 int key;
1754                 int argc;
1755                 const char **argv;
1756         } reqs[] = {
1757                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1758                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1759                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1760                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1761         };
1762         int i;
1764         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1765                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1767                 if (req != reqs[i].key)
1768                         continue;
1769                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1770                 if (req != REQ_NONE)
1771                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1772         }
1775 /*
1776  * User config file handling.
1777  */
1779 static int   config_lineno;
1780 static bool  config_errors;
1781 static const char *config_msg;
1783 static const struct enum_map color_map[] = {
1784 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1785         COLOR_MAP(DEFAULT),
1786         COLOR_MAP(BLACK),
1787         COLOR_MAP(BLUE),
1788         COLOR_MAP(CYAN),
1789         COLOR_MAP(GREEN),
1790         COLOR_MAP(MAGENTA),
1791         COLOR_MAP(RED),
1792         COLOR_MAP(WHITE),
1793         COLOR_MAP(YELLOW),
1794 };
1796 static const struct enum_map attr_map[] = {
1797 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1798         ATTR_MAP(NORMAL),
1799         ATTR_MAP(BLINK),
1800         ATTR_MAP(BOLD),
1801         ATTR_MAP(DIM),
1802         ATTR_MAP(REVERSE),
1803         ATTR_MAP(STANDOUT),
1804         ATTR_MAP(UNDERLINE),
1805 };
1807 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1809 static int parse_step(double *opt, const char *arg)
1811         *opt = atoi(arg);
1812         if (!strchr(arg, '%'))
1813                 return OK;
1815         /* "Shift down" so 100% and 1 does not conflict. */
1816         *opt = (*opt - 1) / 100;
1817         if (*opt >= 1.0) {
1818                 *opt = 0.99;
1819                 config_msg = "Step value larger than 100%";
1820                 return ERR;
1821         }
1822         if (*opt < 0.0) {
1823                 *opt = 1;
1824                 config_msg = "Invalid step value";
1825                 return ERR;
1826         }
1827         return OK;
1830 static int
1831 parse_int(int *opt, const char *arg, int min, int max)
1833         int value = atoi(arg);
1835         if (min <= value && value <= max) {
1836                 *opt = value;
1837                 return OK;
1838         }
1840         config_msg = "Integer value out of bound";
1841         return ERR;
1844 static bool
1845 set_color(int *color, const char *name)
1847         if (map_enum(color, color_map, name))
1848                 return TRUE;
1849         if (!prefixcmp(name, "color"))
1850                 return parse_int(color, name + 5, 0, 255) == OK;
1851         return FALSE;
1854 /* Wants: object fgcolor bgcolor [attribute] */
1855 static int
1856 option_color_command(int argc, const char *argv[])
1858         struct line_info *info;
1860         if (argc < 3) {
1861                 config_msg = "Wrong number of arguments given to color command";
1862                 return ERR;
1863         }
1865         info = get_line_info(argv[0]);
1866         if (!info) {
1867                 static const struct enum_map obsolete[] = {
1868                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1869                         ENUM_MAP("main-date",   LINE_DATE),
1870                         ENUM_MAP("main-author", LINE_AUTHOR),
1871                 };
1872                 int index;
1874                 if (!map_enum(&index, obsolete, argv[0])) {
1875                         config_msg = "Unknown color name";
1876                         return ERR;
1877                 }
1878                 info = &line_info[index];
1879         }
1881         if (!set_color(&info->fg, argv[1]) ||
1882             !set_color(&info->bg, argv[2])) {
1883                 config_msg = "Unknown color";
1884                 return ERR;
1885         }
1887         info->attr = 0;
1888         while (argc-- > 3) {
1889                 int attr;
1891                 if (!set_attribute(&attr, argv[argc])) {
1892                         config_msg = "Unknown attribute";
1893                         return ERR;
1894                 }
1895                 info->attr |= attr;
1896         }
1898         return OK;
1901 static int parse_bool(bool *opt, const char *arg)
1903         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1904                 ? TRUE : FALSE;
1905         return OK;
1908 static int parse_enum_do(unsigned int *opt, const char *arg,
1909                          const struct enum_map *map, size_t map_size)
1911         bool is_true;
1913         assert(map_size > 1);
1915         if (map_enum_do(map, map_size, (int *) opt, arg))
1916                 return OK;
1918         if (parse_bool(&is_true, arg) != OK)
1919                 return ERR;
1921         *opt = is_true ? map[1].value : map[0].value;
1922         return OK;
1925 #define parse_enum(opt, arg, map) \
1926         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1928 static int
1929 parse_string(char *opt, const char *arg, size_t optsize)
1931         int arglen = strlen(arg);
1933         switch (arg[0]) {
1934         case '\"':
1935         case '\'':
1936                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1937                         config_msg = "Unmatched quotation";
1938                         return ERR;
1939                 }
1940                 arg += 1; arglen -= 2;
1941         default:
1942                 string_ncopy_do(opt, optsize, arg, arglen);
1943                 return OK;
1944         }
1947 /* Wants: name = value */
1948 static int
1949 option_set_command(int argc, const char *argv[])
1951         if (argc != 3) {
1952                 config_msg = "Wrong number of arguments given to set command";
1953                 return ERR;
1954         }
1956         if (strcmp(argv[1], "=")) {
1957                 config_msg = "No value assigned";
1958                 return ERR;
1959         }
1961         if (!strcmp(argv[0], "show-author"))
1962                 return parse_enum(&opt_author, argv[2], author_map);
1964         if (!strcmp(argv[0], "show-date"))
1965                 return parse_enum(&opt_date, argv[2], date_map);
1967         if (!strcmp(argv[0], "show-rev-graph"))
1968                 return parse_bool(&opt_rev_graph, argv[2]);
1970         if (!strcmp(argv[0], "show-refs"))
1971                 return parse_bool(&opt_show_refs, argv[2]);
1973         if (!strcmp(argv[0], "show-line-numbers"))
1974                 return parse_bool(&opt_line_number, argv[2]);
1976         if (!strcmp(argv[0], "line-graphics"))
1977                 return parse_bool(&opt_line_graphics, argv[2]);
1979         if (!strcmp(argv[0], "line-number-interval"))
1980                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1982         if (!strcmp(argv[0], "author-width"))
1983                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1985         if (!strcmp(argv[0], "horizontal-scroll"))
1986                 return parse_step(&opt_hscroll, argv[2]);
1988         if (!strcmp(argv[0], "split-view-height"))
1989                 return parse_step(&opt_scale_split_view, argv[2]);
1991         if (!strcmp(argv[0], "tab-size"))
1992                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1994         if (!strcmp(argv[0], "commit-encoding"))
1995                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1997         config_msg = "Unknown variable name";
1998         return ERR;
2001 /* Wants: mode request key */
2002 static int
2003 option_bind_command(int argc, const char *argv[])
2005         enum request request;
2006         int keymap = -1;
2007         int key;
2009         if (argc < 3) {
2010                 config_msg = "Wrong number of arguments given to bind command";
2011                 return ERR;
2012         }
2014         if (!set_keymap(&keymap, argv[0])) {
2015                 config_msg = "Unknown key map";
2016                 return ERR;
2017         }
2019         key = get_key_value(argv[1]);
2020         if (key == ERR) {
2021                 config_msg = "Unknown key";
2022                 return ERR;
2023         }
2025         request = get_request(argv[2]);
2026         if (request == REQ_UNKNOWN) {
2027                 static const struct enum_map obsolete[] = {
2028                         ENUM_MAP("cherry-pick",         REQ_NONE),
2029                         ENUM_MAP("screen-resize",       REQ_NONE),
2030                         ENUM_MAP("tree-parent",         REQ_PARENT),
2031                 };
2032                 int alias;
2034                 if (map_enum(&alias, obsolete, argv[2])) {
2035                         if (alias != REQ_NONE)
2036                                 add_keybinding(keymap, alias, key);
2037                         config_msg = "Obsolete request name";
2038                         return ERR;
2039                 }
2040         }
2041         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2042                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2043         if (request == REQ_UNKNOWN) {
2044                 config_msg = "Unknown request name";
2045                 return ERR;
2046         }
2048         add_keybinding(keymap, request, key);
2050         return OK;
2053 static int
2054 set_option(const char *opt, char *value)
2056         const char *argv[SIZEOF_ARG];
2057         int argc = 0;
2059         if (!argv_from_string(argv, &argc, value)) {
2060                 config_msg = "Too many option arguments";
2061                 return ERR;
2062         }
2064         if (!strcmp(opt, "color"))
2065                 return option_color_command(argc, argv);
2067         if (!strcmp(opt, "set"))
2068                 return option_set_command(argc, argv);
2070         if (!strcmp(opt, "bind"))
2071                 return option_bind_command(argc, argv);
2073         config_msg = "Unknown option command";
2074         return ERR;
2077 static int
2078 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2080         int status = OK;
2082         config_lineno++;
2083         config_msg = "Internal error";
2085         /* Check for comment markers, since read_properties() will
2086          * only ensure opt and value are split at first " \t". */
2087         optlen = strcspn(opt, "#");
2088         if (optlen == 0)
2089                 return OK;
2091         if (opt[optlen] != 0) {
2092                 config_msg = "No option value";
2093                 status = ERR;
2095         }  else {
2096                 /* Look for comment endings in the value. */
2097                 size_t len = strcspn(value, "#");
2099                 if (len < valuelen) {
2100                         valuelen = len;
2101                         value[valuelen] = 0;
2102                 }
2104                 status = set_option(opt, value);
2105         }
2107         if (status == ERR) {
2108                 warn("Error on line %d, near '%.*s': %s",
2109                      config_lineno, (int) optlen, opt, config_msg);
2110                 config_errors = TRUE;
2111         }
2113         /* Always keep going if errors are encountered. */
2114         return OK;
2117 static void
2118 load_option_file(const char *path)
2120         struct io io = {};
2122         /* It's OK that the file doesn't exist. */
2123         if (!io_open(&io, "%s", path))
2124                 return;
2126         config_lineno = 0;
2127         config_errors = FALSE;
2129         if (io_load(&io, " \t", read_option) == ERR ||
2130             config_errors == TRUE)
2131                 warn("Errors while loading %s.", path);
2134 static int
2135 load_options(void)
2137         const char *home = getenv("HOME");
2138         const char *tigrc_user = getenv("TIGRC_USER");
2139         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2140         char buf[SIZEOF_STR];
2142         if (!tigrc_system)
2143                 tigrc_system = SYSCONFDIR "/tigrc";
2144         load_option_file(tigrc_system);
2146         if (!tigrc_user) {
2147                 if (!home || !string_format(buf, "%s/.tigrc", home))
2148                         return ERR;
2149                 tigrc_user = buf;
2150         }
2151         load_option_file(tigrc_user);
2153         /* Add _after_ loading config files to avoid adding run requests
2154          * that conflict with keybindings. */
2155         add_builtin_run_requests();
2157         return OK;
2161 /*
2162  * The viewer
2163  */
2165 struct view;
2166 struct view_ops;
2168 /* The display array of active views and the index of the current view. */
2169 static struct view *display[2];
2170 static unsigned int current_view;
2172 #define foreach_displayed_view(view, i) \
2173         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2175 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2177 /* Current head and commit ID */
2178 static char ref_blob[SIZEOF_REF]        = "";
2179 static char ref_commit[SIZEOF_REF]      = "HEAD";
2180 static char ref_head[SIZEOF_REF]        = "HEAD";
2181 static char ref_branch[SIZEOF_REF]      = "";
2183 enum view_type {
2184         VIEW_MAIN,
2185         VIEW_DIFF,
2186         VIEW_LOG,
2187         VIEW_TREE,
2188         VIEW_BLOB,
2189         VIEW_BLAME,
2190         VIEW_BRANCH,
2191         VIEW_HELP,
2192         VIEW_PAGER,
2193         VIEW_STATUS,
2194         VIEW_STAGE,
2195 };
2197 struct view {
2198         enum view_type type;    /* View type */
2199         const char *name;       /* View name */
2200         const char *cmd_env;    /* Command line set via environment */
2201         const char *id;         /* Points to either of ref_{head,commit,blob} */
2203         struct view_ops *ops;   /* View operations */
2205         enum keymap keymap;     /* What keymap does this view have */
2206         bool git_dir;           /* Whether the view requires a git directory. */
2207         bool refresh;           /* Whether the view supports refreshing. */
2209         char ref[SIZEOF_REF];   /* Hovered commit reference */
2210         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2212         int height, width;      /* The width and height of the main window */
2213         WINDOW *win;            /* The main window */
2214         WINDOW *title;          /* The title window living below the main window */
2216         /* Navigation */
2217         unsigned long offset;   /* Offset of the window top */
2218         unsigned long yoffset;  /* Offset from the window side. */
2219         unsigned long lineno;   /* Current line number */
2220         unsigned long p_offset; /* Previous offset of the window top */
2221         unsigned long p_yoffset;/* Previous offset from the window side */
2222         unsigned long p_lineno; /* Previous current line number */
2223         bool p_restore;         /* Should the previous position be restored. */
2225         /* Searching */
2226         char grep[SIZEOF_STR];  /* Search string */
2227         regex_t *regex;         /* Pre-compiled regexp */
2229         /* If non-NULL, points to the view that opened this view. If this view
2230          * is closed tig will switch back to the parent view. */
2231         struct view *parent;
2233         /* Buffering */
2234         size_t lines;           /* Total number of lines */
2235         struct line *line;      /* Line index */
2236         unsigned int digits;    /* Number of digits in the lines member. */
2238         /* Drawing */
2239         struct line *curline;   /* Line currently being drawn. */
2240         enum line_type curtype; /* Attribute currently used for drawing. */
2241         unsigned long col;      /* Column when drawing. */
2242         bool has_scrolled;      /* View was scrolled. */
2244         /* Loading */
2245         struct io io;
2246         struct io *pipe;
2247         time_t start_time;
2248         time_t update_secs;
2249 };
2251 struct view_ops {
2252         /* What type of content being displayed. Used in the title bar. */
2253         const char *type;
2254         /* Default command arguments. */
2255         const char **argv;
2256         /* Open and reads in all view content. */
2257         bool (*open)(struct view *view);
2258         /* Read one line; updates view->line. */
2259         bool (*read)(struct view *view, char *data);
2260         /* Draw one line; @lineno must be < view->height. */
2261         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2262         /* Depending on view handle a special requests. */
2263         enum request (*request)(struct view *view, enum request request, struct line *line);
2264         /* Search for regexp in a line. */
2265         bool (*grep)(struct view *view, struct line *line);
2266         /* Select line */
2267         void (*select)(struct view *view, struct line *line);
2268         /* Prepare view for loading */
2269         bool (*prepare)(struct view *view);
2270 };
2272 static struct view_ops blame_ops;
2273 static struct view_ops blob_ops;
2274 static struct view_ops diff_ops;
2275 static struct view_ops help_ops;
2276 static struct view_ops log_ops;
2277 static struct view_ops main_ops;
2278 static struct view_ops pager_ops;
2279 static struct view_ops stage_ops;
2280 static struct view_ops status_ops;
2281 static struct view_ops tree_ops;
2282 static struct view_ops branch_ops;
2284 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2285         { type, name, #env, ref, ops, map, git, refresh }
2287 #define VIEW_(id, name, ops, git, refresh, ref) \
2288         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2290 static struct view views[] = {
2291         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  TRUE,  ref_head),
2292         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  FALSE, ref_commit),
2293         VIEW_(LOG,    "log",    &log_ops,    TRUE,  TRUE,  ref_head),
2294         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  FALSE, ref_commit),
2295         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  FALSE, ref_blob),
2296         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  FALSE, ref_commit),
2297         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  TRUE,  ref_head),
2298         VIEW_(HELP,   "help",   &help_ops,   FALSE, FALSE, ""),
2299         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, FALSE, "stdin"),
2300         VIEW_(STATUS, "status", &status_ops, TRUE,  TRUE,  ""),
2301         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  TRUE,  ""),
2302 };
2304 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2306 #define foreach_view(view, i) \
2307         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2309 #define view_is_displayed(view) \
2310         (view == display[0] || view == display[1])
2312 #define view_has_parent(view, child_type, parent_type) \
2313         (view->type == child_type && view->parent && view->parent->type == parent_type)
2315 static inline void
2316 set_view_attr(struct view *view, enum line_type type)
2318         if (!view->curline->selected && view->curtype != type) {
2319                 (void) wattrset(view->win, get_line_attr(type));
2320                 wchgat(view->win, -1, 0, type, NULL);
2321                 view->curtype = type;
2322         }
2325 static int
2326 draw_chars(struct view *view, enum line_type type, const char *string,
2327            int max_len, bool use_tilde)
2329         static char out_buffer[BUFSIZ * 2];
2330         int len = 0;
2331         int col = 0;
2332         int trimmed = FALSE;
2333         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2335         if (max_len <= 0)
2336                 return 0;
2338         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2340         set_view_attr(view, type);
2341         if (len > 0) {
2342                 if (opt_iconv_out != ICONV_NONE) {
2343                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2344                         size_t inlen = len + 1;
2346                         char *outbuf = out_buffer;
2347                         size_t outlen = sizeof(out_buffer);
2349                         size_t ret;
2351                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2352                         if (ret != (size_t) -1) {
2353                                 string = out_buffer;
2354                                 len = sizeof(out_buffer) - outlen;
2355                         }
2356                 }
2358                 waddnstr(view->win, string, len);
2359         }
2360         if (trimmed && use_tilde) {
2361                 set_view_attr(view, LINE_DELIMITER);
2362                 waddch(view->win, '~');
2363                 col++;
2364         }
2366         return col;
2369 static int
2370 draw_space(struct view *view, enum line_type type, int max, int spaces)
2372         static char space[] = "                    ";
2373         int col = 0;
2375         spaces = MIN(max, spaces);
2377         while (spaces > 0) {
2378                 int len = MIN(spaces, sizeof(space) - 1);
2380                 col += draw_chars(view, type, space, len, FALSE);
2381                 spaces -= len;
2382         }
2384         return col;
2387 static bool
2388 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2390         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2391         return view->width + view->yoffset <= view->col;
2394 static bool
2395 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2397         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2398         int max = view->width + view->yoffset - view->col;
2399         int i;
2401         if (max < size)
2402                 size = max;
2404         set_view_attr(view, type);
2405         /* Using waddch() instead of waddnstr() ensures that
2406          * they'll be rendered correctly for the cursor line. */
2407         for (i = skip; i < size; i++)
2408                 waddch(view->win, graphic[i]);
2410         view->col += size;
2411         if (size < max && skip <= size)
2412                 waddch(view->win, ' ');
2413         view->col++;
2415         return view->width + view->yoffset <= view->col;
2418 static bool
2419 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2421         int max = MIN(view->width + view->yoffset - view->col, len);
2422         int col;
2424         if (text)
2425                 col = draw_chars(view, type, text, max - 1, trim);
2426         else
2427                 col = draw_space(view, type, max - 1, max - 1);
2429         view->col += col;
2430         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2431         return view->width + view->yoffset <= view->col;
2434 static bool
2435 draw_date(struct view *view, struct time *time)
2437         const char *date = mkdate(time, opt_date);
2438         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2440         return draw_field(view, LINE_DATE, date, cols, FALSE);
2443 static bool
2444 draw_author(struct view *view, const char *author)
2446         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2447         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2449         if (abbreviate && author)
2450                 author = get_author_initials(author);
2452         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2455 static bool
2456 draw_mode(struct view *view, mode_t mode)
2458         const char *str;
2460         if (S_ISDIR(mode))
2461                 str = "drwxr-xr-x";
2462         else if (S_ISLNK(mode))
2463                 str = "lrwxrwxrwx";
2464         else if (S_ISGITLINK(mode))
2465                 str = "m---------";
2466         else if (S_ISREG(mode) && mode & S_IXUSR)
2467                 str = "-rwxr-xr-x";
2468         else if (S_ISREG(mode))
2469                 str = "-rw-r--r--";
2470         else
2471                 str = "----------";
2473         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2476 static bool
2477 draw_lineno(struct view *view, unsigned int lineno)
2479         char number[10];
2480         int digits3 = view->digits < 3 ? 3 : view->digits;
2481         int max = MIN(view->width + view->yoffset - view->col, digits3);
2482         char *text = NULL;
2483         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2485         lineno += view->offset + 1;
2486         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2487                 static char fmt[] = "%1ld";
2489                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2490                 if (string_format(number, fmt, lineno))
2491                         text = number;
2492         }
2493         if (text)
2494                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2495         else
2496                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2497         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2500 static bool
2501 draw_view_line(struct view *view, unsigned int lineno)
2503         struct line *line;
2504         bool selected = (view->offset + lineno == view->lineno);
2506         assert(view_is_displayed(view));
2508         if (view->offset + lineno >= view->lines)
2509                 return FALSE;
2511         line = &view->line[view->offset + lineno];
2513         wmove(view->win, lineno, 0);
2514         if (line->cleareol)
2515                 wclrtoeol(view->win);
2516         view->col = 0;
2517         view->curline = line;
2518         view->curtype = LINE_NONE;
2519         line->selected = FALSE;
2520         line->dirty = line->cleareol = 0;
2522         if (selected) {
2523                 set_view_attr(view, LINE_CURSOR);
2524                 line->selected = TRUE;
2525                 view->ops->select(view, line);
2526         }
2528         return view->ops->draw(view, line, lineno);
2531 static void
2532 redraw_view_dirty(struct view *view)
2534         bool dirty = FALSE;
2535         int lineno;
2537         for (lineno = 0; lineno < view->height; lineno++) {
2538                 if (view->offset + lineno >= view->lines)
2539                         break;
2540                 if (!view->line[view->offset + lineno].dirty)
2541                         continue;
2542                 dirty = TRUE;
2543                 if (!draw_view_line(view, lineno))
2544                         break;
2545         }
2547         if (!dirty)
2548                 return;
2549         wnoutrefresh(view->win);
2552 static void
2553 redraw_view_from(struct view *view, int lineno)
2555         assert(0 <= lineno && lineno < view->height);
2557         for (; lineno < view->height; lineno++) {
2558                 if (!draw_view_line(view, lineno))
2559                         break;
2560         }
2562         wnoutrefresh(view->win);
2565 static void
2566 redraw_view(struct view *view)
2568         werase(view->win);
2569         redraw_view_from(view, 0);
2573 static void
2574 update_view_title(struct view *view)
2576         char buf[SIZEOF_STR];
2577         char state[SIZEOF_STR];
2578         size_t bufpos = 0, statelen = 0;
2580         assert(view_is_displayed(view));
2582         if (view->type != VIEW_STATUS && view->lines) {
2583                 unsigned int view_lines = view->offset + view->height;
2584                 unsigned int lines = view->lines
2585                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2586                                    : 0;
2588                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2589                                    view->ops->type,
2590                                    view->lineno + 1,
2591                                    view->lines,
2592                                    lines);
2594         }
2596         if (view->pipe) {
2597                 time_t secs = time(NULL) - view->start_time;
2599                 /* Three git seconds are a long time ... */
2600                 if (secs > 2)
2601                         string_format_from(state, &statelen, " loading %lds", secs);
2602         }
2604         string_format_from(buf, &bufpos, "[%s]", view->name);
2605         if (*view->ref && bufpos < view->width) {
2606                 size_t refsize = strlen(view->ref);
2607                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2609                 if (minsize < view->width)
2610                         refsize = view->width - minsize + 7;
2611                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2612         }
2614         if (statelen && bufpos < view->width) {
2615                 string_format_from(buf, &bufpos, "%s", state);
2616         }
2618         if (view == display[current_view])
2619                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2620         else
2621                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2623         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2624         wclrtoeol(view->title);
2625         wnoutrefresh(view->title);
2628 static int
2629 apply_step(double step, int value)
2631         if (step >= 1)
2632                 return (int) step;
2633         value *= step + 0.01;
2634         return value ? value : 1;
2637 static void
2638 resize_display(void)
2640         int offset, i;
2641         struct view *base = display[0];
2642         struct view *view = display[1] ? display[1] : display[0];
2644         /* Setup window dimensions */
2646         getmaxyx(stdscr, base->height, base->width);
2648         /* Make room for the status window. */
2649         base->height -= 1;
2651         if (view != base) {
2652                 /* Horizontal split. */
2653                 view->width   = base->width;
2654                 view->height  = apply_step(opt_scale_split_view, base->height);
2655                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2656                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2657                 base->height -= view->height;
2659                 /* Make room for the title bar. */
2660                 view->height -= 1;
2661         }
2663         /* Make room for the title bar. */
2664         base->height -= 1;
2666         offset = 0;
2668         foreach_displayed_view (view, i) {
2669                 if (!view->win) {
2670                         view->win = newwin(view->height, 0, offset, 0);
2671                         if (!view->win)
2672                                 die("Failed to create %s view", view->name);
2674                         scrollok(view->win, FALSE);
2676                         view->title = newwin(1, 0, offset + view->height, 0);
2677                         if (!view->title)
2678                                 die("Failed to create title window");
2680                 } else {
2681                         wresize(view->win, view->height, view->width);
2682                         mvwin(view->win,   offset, 0);
2683                         mvwin(view->title, offset + view->height, 0);
2684                 }
2686                 offset += view->height + 1;
2687         }
2690 static void
2691 redraw_display(bool clear)
2693         struct view *view;
2694         int i;
2696         foreach_displayed_view (view, i) {
2697                 if (clear)
2698                         wclear(view->win);
2699                 redraw_view(view);
2700                 update_view_title(view);
2701         }
2704 static void
2705 toggle_enum_option_do(unsigned int *opt, const char *help,
2706                       const struct enum_map *map, size_t size)
2708         *opt = (*opt + 1) % size;
2709         redraw_display(FALSE);
2710         report("Displaying %s %s", enum_name(map[*opt]), help);
2713 #define toggle_enum_option(opt, help, map) \
2714         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2716 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2717 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2719 static void
2720 toggle_view_option(bool *option, const char *help)
2722         *option = !*option;
2723         redraw_display(FALSE);
2724         report("%sabling %s", *option ? "En" : "Dis", help);
2727 static void
2728 open_option_menu(void)
2730         const struct menu_item menu[] = {
2731                 { '.', "line numbers", &opt_line_number },
2732                 { 'D', "date display", &opt_date },
2733                 { 'A', "author display", &opt_author },
2734                 { 'g', "revision graph display", &opt_rev_graph },
2735                 { 'F', "reference display", &opt_show_refs },
2736                 { 0 }
2737         };
2738         int selected = 0;
2740         if (prompt_menu("Toggle option", menu, &selected)) {
2741                 if (menu[selected].data == &opt_date)
2742                         toggle_date();
2743                 else if (menu[selected].data == &opt_author)
2744                         toggle_author();
2745                 else
2746                         toggle_view_option(menu[selected].data, menu[selected].text);
2747         }
2750 static void
2751 maximize_view(struct view *view)
2753         memset(display, 0, sizeof(display));
2754         current_view = 0;
2755         display[current_view] = view;
2756         resize_display();
2757         redraw_display(FALSE);
2758         report("");
2762 /*
2763  * Navigation
2764  */
2766 static bool
2767 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2769         if (lineno >= view->lines)
2770                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2772         if (offset > lineno || offset + view->height <= lineno) {
2773                 unsigned long half = view->height / 2;
2775                 if (lineno > half)
2776                         offset = lineno - half;
2777                 else
2778                         offset = 0;
2779         }
2781         if (offset != view->offset || lineno != view->lineno) {
2782                 view->offset = offset;
2783                 view->lineno = lineno;
2784                 return TRUE;
2785         }
2787         return FALSE;
2790 /* Scrolling backend */
2791 static void
2792 do_scroll_view(struct view *view, int lines)
2794         bool redraw_current_line = FALSE;
2796         /* The rendering expects the new offset. */
2797         view->offset += lines;
2799         assert(0 <= view->offset && view->offset < view->lines);
2800         assert(lines);
2802         /* Move current line into the view. */
2803         if (view->lineno < view->offset) {
2804                 view->lineno = view->offset;
2805                 redraw_current_line = TRUE;
2806         } else if (view->lineno >= view->offset + view->height) {
2807                 view->lineno = view->offset + view->height - 1;
2808                 redraw_current_line = TRUE;
2809         }
2811         assert(view->offset <= view->lineno && view->lineno < view->lines);
2813         /* Redraw the whole screen if scrolling is pointless. */
2814         if (view->height < ABS(lines)) {
2815                 redraw_view(view);
2817         } else {
2818                 int line = lines > 0 ? view->height - lines : 0;
2819                 int end = line + ABS(lines);
2821                 scrollok(view->win, TRUE);
2822                 wscrl(view->win, lines);
2823                 scrollok(view->win, FALSE);
2825                 while (line < end && draw_view_line(view, line))
2826                         line++;
2828                 if (redraw_current_line)
2829                         draw_view_line(view, view->lineno - view->offset);
2830                 wnoutrefresh(view->win);
2831         }
2833         view->has_scrolled = TRUE;
2834         report("");
2837 /* Scroll frontend */
2838 static void
2839 scroll_view(struct view *view, enum request request)
2841         int lines = 1;
2843         assert(view_is_displayed(view));
2845         switch (request) {
2846         case REQ_SCROLL_LEFT:
2847                 if (view->yoffset == 0) {
2848                         report("Cannot scroll beyond the first column");
2849                         return;
2850                 }
2851                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2852                         view->yoffset = 0;
2853                 else
2854                         view->yoffset -= apply_step(opt_hscroll, view->width);
2855                 redraw_view_from(view, 0);
2856                 report("");
2857                 return;
2858         case REQ_SCROLL_RIGHT:
2859                 view->yoffset += apply_step(opt_hscroll, view->width);
2860                 redraw_view(view);
2861                 report("");
2862                 return;
2863         case REQ_SCROLL_PAGE_DOWN:
2864                 lines = view->height;
2865         case REQ_SCROLL_LINE_DOWN:
2866                 if (view->offset + lines > view->lines)
2867                         lines = view->lines - view->offset;
2869                 if (lines == 0 || view->offset + view->height >= view->lines) {
2870                         report("Cannot scroll beyond the last line");
2871                         return;
2872                 }
2873                 break;
2875         case REQ_SCROLL_PAGE_UP:
2876                 lines = view->height;
2877         case REQ_SCROLL_LINE_UP:
2878                 if (lines > view->offset)
2879                         lines = view->offset;
2881                 if (lines == 0) {
2882                         report("Cannot scroll beyond the first line");
2883                         return;
2884                 }
2886                 lines = -lines;
2887                 break;
2889         default:
2890                 die("request %d not handled in switch", request);
2891         }
2893         do_scroll_view(view, lines);
2896 /* Cursor moving */
2897 static void
2898 move_view(struct view *view, enum request request)
2900         int scroll_steps = 0;
2901         int steps;
2903         switch (request) {
2904         case REQ_MOVE_FIRST_LINE:
2905                 steps = -view->lineno;
2906                 break;
2908         case REQ_MOVE_LAST_LINE:
2909                 steps = view->lines - view->lineno - 1;
2910                 break;
2912         case REQ_MOVE_PAGE_UP:
2913                 steps = view->height > view->lineno
2914                       ? -view->lineno : -view->height;
2915                 break;
2917         case REQ_MOVE_PAGE_DOWN:
2918                 steps = view->lineno + view->height >= view->lines
2919                       ? view->lines - view->lineno - 1 : view->height;
2920                 break;
2922         case REQ_MOVE_UP:
2923                 steps = -1;
2924                 break;
2926         case REQ_MOVE_DOWN:
2927                 steps = 1;
2928                 break;
2930         default:
2931                 die("request %d not handled in switch", request);
2932         }
2934         if (steps <= 0 && view->lineno == 0) {
2935                 report("Cannot move beyond the first line");
2936                 return;
2938         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2939                 report("Cannot move beyond the last line");
2940                 return;
2941         }
2943         /* Move the current line */
2944         view->lineno += steps;
2945         assert(0 <= view->lineno && view->lineno < view->lines);
2947         /* Check whether the view needs to be scrolled */
2948         if (view->lineno < view->offset ||
2949             view->lineno >= view->offset + view->height) {
2950                 scroll_steps = steps;
2951                 if (steps < 0 && -steps > view->offset) {
2952                         scroll_steps = -view->offset;
2954                 } else if (steps > 0) {
2955                         if (view->lineno == view->lines - 1 &&
2956                             view->lines > view->height) {
2957                                 scroll_steps = view->lines - view->offset - 1;
2958                                 if (scroll_steps >= view->height)
2959                                         scroll_steps -= view->height - 1;
2960                         }
2961                 }
2962         }
2964         if (!view_is_displayed(view)) {
2965                 view->offset += scroll_steps;
2966                 assert(0 <= view->offset && view->offset < view->lines);
2967                 view->ops->select(view, &view->line[view->lineno]);
2968                 return;
2969         }
2971         /* Repaint the old "current" line if we be scrolling */
2972         if (ABS(steps) < view->height)
2973                 draw_view_line(view, view->lineno - steps - view->offset);
2975         if (scroll_steps) {
2976                 do_scroll_view(view, scroll_steps);
2977                 return;
2978         }
2980         /* Draw the current line */
2981         draw_view_line(view, view->lineno - view->offset);
2983         wnoutrefresh(view->win);
2984         report("");
2988 /*
2989  * Searching
2990  */
2992 static void search_view(struct view *view, enum request request);
2994 static bool
2995 grep_text(struct view *view, const char *text[])
2997         regmatch_t pmatch;
2998         size_t i;
3000         for (i = 0; text[i]; i++)
3001                 if (*text[i] &&
3002                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3003                         return TRUE;
3004         return FALSE;
3007 static void
3008 select_view_line(struct view *view, unsigned long lineno)
3010         unsigned long old_lineno = view->lineno;
3011         unsigned long old_offset = view->offset;
3013         if (goto_view_line(view, view->offset, lineno)) {
3014                 if (view_is_displayed(view)) {
3015                         if (old_offset != view->offset) {
3016                                 redraw_view(view);
3017                         } else {
3018                                 draw_view_line(view, old_lineno - view->offset);
3019                                 draw_view_line(view, view->lineno - view->offset);
3020                                 wnoutrefresh(view->win);
3021                         }
3022                 } else {
3023                         view->ops->select(view, &view->line[view->lineno]);
3024                 }
3025         }
3028 static void
3029 find_next(struct view *view, enum request request)
3031         unsigned long lineno = view->lineno;
3032         int direction;
3034         if (!*view->grep) {
3035                 if (!*opt_search)
3036                         report("No previous search");
3037                 else
3038                         search_view(view, request);
3039                 return;
3040         }
3042         switch (request) {
3043         case REQ_SEARCH:
3044         case REQ_FIND_NEXT:
3045                 direction = 1;
3046                 break;
3048         case REQ_SEARCH_BACK:
3049         case REQ_FIND_PREV:
3050                 direction = -1;
3051                 break;
3053         default:
3054                 return;
3055         }
3057         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3058                 lineno += direction;
3060         /* Note, lineno is unsigned long so will wrap around in which case it
3061          * will become bigger than view->lines. */
3062         for (; lineno < view->lines; lineno += direction) {
3063                 if (view->ops->grep(view, &view->line[lineno])) {
3064                         select_view_line(view, lineno);
3065                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3066                         return;
3067                 }
3068         }
3070         report("No match found for '%s'", view->grep);
3073 static void
3074 search_view(struct view *view, enum request request)
3076         int regex_err;
3078         if (view->regex) {
3079                 regfree(view->regex);
3080                 *view->grep = 0;
3081         } else {
3082                 view->regex = calloc(1, sizeof(*view->regex));
3083                 if (!view->regex)
3084                         return;
3085         }
3087         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3088         if (regex_err != 0) {
3089                 char buf[SIZEOF_STR] = "unknown error";
3091                 regerror(regex_err, view->regex, buf, sizeof(buf));
3092                 report("Search failed: %s", buf);
3093                 return;
3094         }
3096         string_copy(view->grep, opt_search);
3098         find_next(view, request);
3101 /*
3102  * Incremental updating
3103  */
3105 static void
3106 reset_view(struct view *view)
3108         int i;
3110         for (i = 0; i < view->lines; i++)
3111                 free(view->line[i].data);
3112         free(view->line);
3114         view->p_offset = view->offset;
3115         view->p_yoffset = view->yoffset;
3116         view->p_lineno = view->lineno;
3118         view->line = NULL;
3119         view->offset = 0;
3120         view->yoffset = 0;
3121         view->lines  = 0;
3122         view->lineno = 0;
3123         view->vid[0] = 0;
3124         view->update_secs = 0;
3127 static void
3128 free_argv(const char *argv[])
3130         int argc;
3132         for (argc = 0; argv[argc]; argc++)
3133                 free((void *) argv[argc]);
3136 static const char *
3137 format_arg(const char *name)
3139         static struct {
3140                 const char *name;
3141                 size_t namelen;
3142                 const char *value;
3143                 const char *value_if_empty;
3144         } vars[] = {
3145 #define FORMAT_VAR(name, value, value_if_empty) \
3146         { name, STRING_SIZE(name), value, value_if_empty }
3147                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3148                 FORMAT_VAR("%(file)",           opt_file,       ""),
3149                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3150                 FORMAT_VAR("%(head)",           ref_head,       ""),
3151                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3152                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3153                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3154         };
3155         int i;
3157         for (i = 0; i < ARRAY_SIZE(vars); i++)
3158                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3159                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3161         report("Unknown replacement: `%s`", name);
3162         return NULL;
3165 static bool
3166 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3168         char buf[SIZEOF_STR];
3169         int argc;
3170         bool noreplace = flags == FORMAT_NONE;
3172         free_argv(dst_argv);
3174         for (argc = 0; src_argv[argc]; argc++) {
3175                 const char *arg = src_argv[argc];
3176                 size_t bufpos = 0;
3178                 while (arg) {
3179                         char *next = strstr(arg, "%(");
3180                         int len = next - arg;
3181                         const char *value;
3183                         if (!next || noreplace) {
3184                                 len = strlen(arg);
3185                                 value = "";
3187                         } else {
3188                                 value = format_arg(next);
3190                                 if (!value) {
3191                                         return FALSE;
3192                                 }
3193                         }
3195                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3196                                 return FALSE;
3198                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3199                 }
3201                 dst_argv[argc] = strdup(buf);
3202                 if (!dst_argv[argc])
3203                         break;
3204         }
3206         dst_argv[argc] = NULL;
3208         return src_argv[argc] == NULL;
3211 static bool
3212 restore_view_position(struct view *view)
3214         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3215                 return FALSE;
3217         /* Changing the view position cancels the restoring. */
3218         /* FIXME: Changing back to the first line is not detected. */
3219         if (view->offset != 0 || view->lineno != 0) {
3220                 view->p_restore = FALSE;
3221                 return FALSE;
3222         }
3224         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3225             view_is_displayed(view))
3226                 werase(view->win);
3228         view->yoffset = view->p_yoffset;
3229         view->p_restore = FALSE;
3231         return TRUE;
3234 static void
3235 end_update(struct view *view, bool force)
3237         if (!view->pipe)
3238                 return;
3239         while (!view->ops->read(view, NULL))
3240                 if (!force)
3241                         return;
3242         if (force)
3243                 io_kill(view->pipe);
3244         io_done(view->pipe);
3245         view->pipe = NULL;
3248 static void
3249 setup_update(struct view *view, const char *vid)
3251         reset_view(view);
3252         string_copy_rev(view->vid, vid);
3253         view->pipe = &view->io;
3254         view->start_time = time(NULL);
3257 static bool
3258 prepare_update(struct view *view, const char *argv[], const char *dir)
3260         if (view->pipe)
3261                 end_update(view, TRUE);
3262         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3265 static bool
3266 prepare_update_file(struct view *view, const char *name)
3268         if (view->pipe)
3269                 end_update(view, TRUE);
3270         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3273 static bool
3274 begin_update(struct view *view, bool refresh)
3276         if (view->pipe)
3277                 end_update(view, TRUE);
3279         if (!refresh) {
3280                 if (view->ops->prepare) {
3281                         if (!view->ops->prepare(view))
3282                                 return FALSE;
3283                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3284                         return FALSE;
3285                 }
3287                 /* Put the current ref_* value to the view title ref
3288                  * member. This is needed by the blob view. Most other
3289                  * views sets it automatically after loading because the
3290                  * first line is a commit line. */
3291                 string_copy_rev(view->ref, view->id);
3292         }
3294         if (!io_start(&view->io))
3295                 return FALSE;
3297         setup_update(view, view->id);
3299         return TRUE;
3302 static bool
3303 update_view(struct view *view)
3305         char out_buffer[BUFSIZ * 2];
3306         char *line;
3307         /* Clear the view and redraw everything since the tree sorting
3308          * might have rearranged things. */
3309         bool redraw = view->lines == 0;
3310         bool can_read = TRUE;
3312         if (!view->pipe)
3313                 return TRUE;
3315         if (!io_can_read(view->pipe)) {
3316                 if (view->lines == 0 && view_is_displayed(view)) {
3317                         time_t secs = time(NULL) - view->start_time;
3319                         if (secs > 1 && secs > view->update_secs) {
3320                                 if (view->update_secs == 0)
3321                                         redraw_view(view);
3322                                 update_view_title(view);
3323                                 view->update_secs = secs;
3324                         }
3325                 }
3326                 return TRUE;
3327         }
3329         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3330                 if (opt_iconv_in != ICONV_NONE) {
3331                         ICONV_CONST char *inbuf = line;
3332                         size_t inlen = strlen(line) + 1;
3334                         char *outbuf = out_buffer;
3335                         size_t outlen = sizeof(out_buffer);
3337                         size_t ret;
3339                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3340                         if (ret != (size_t) -1)
3341                                 line = out_buffer;
3342                 }
3344                 if (!view->ops->read(view, line)) {
3345                         report("Allocation failure");
3346                         end_update(view, TRUE);
3347                         return FALSE;
3348                 }
3349         }
3351         {
3352                 unsigned long lines = view->lines;
3353                 int digits;
3355                 for (digits = 0; lines; digits++)
3356                         lines /= 10;
3358                 /* Keep the displayed view in sync with line number scaling. */
3359                 if (digits != view->digits) {
3360                         view->digits = digits;
3361                         if (opt_line_number || view->type == VIEW_BLAME)
3362                                 redraw = TRUE;
3363                 }
3364         }
3366         if (io_error(view->pipe)) {
3367                 report("Failed to read: %s", io_strerror(view->pipe));
3368                 end_update(view, TRUE);
3370         } else if (io_eof(view->pipe)) {
3371                 report("");
3372                 end_update(view, FALSE);
3373         }
3375         if (restore_view_position(view))
3376                 redraw = TRUE;
3378         if (!view_is_displayed(view))
3379                 return TRUE;
3381         if (redraw)
3382                 redraw_view_from(view, 0);
3383         else
3384                 redraw_view_dirty(view);
3386         /* Update the title _after_ the redraw so that if the redraw picks up a
3387          * commit reference in view->ref it'll be available here. */
3388         update_view_title(view);
3389         return TRUE;
3392 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3394 static struct line *
3395 add_line_data(struct view *view, void *data, enum line_type type)
3397         struct line *line;
3399         if (!realloc_lines(&view->line, view->lines, 1))
3400                 return NULL;
3402         line = &view->line[view->lines++];
3403         memset(line, 0, sizeof(*line));
3404         line->type = type;
3405         line->data = data;
3406         line->dirty = 1;
3408         return line;
3411 static struct line *
3412 add_line_text(struct view *view, const char *text, enum line_type type)
3414         char *data = text ? strdup(text) : NULL;
3416         return data ? add_line_data(view, data, type) : NULL;
3419 static struct line *
3420 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3422         char buf[SIZEOF_STR];
3423         va_list args;
3425         va_start(args, fmt);
3426         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3427                 buf[0] = 0;
3428         va_end(args);
3430         return buf[0] ? add_line_text(view, buf, type) : NULL;
3433 /*
3434  * View opening
3435  */
3437 enum open_flags {
3438         OPEN_DEFAULT = 0,       /* Use default view switching. */
3439         OPEN_SPLIT = 1,         /* Split current view. */
3440         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3441         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3442         OPEN_PREPARED = 32,     /* Open already prepared command. */
3443 };
3445 static void
3446 open_view(struct view *prev, enum request request, enum open_flags flags)
3448         bool split = !!(flags & OPEN_SPLIT);
3449         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3450         bool nomaximize = !!(flags & OPEN_REFRESH);
3451         struct view *view = VIEW(request);
3452         int nviews = displayed_views();
3453         struct view *base_view = display[0];
3455         if (view == prev && nviews == 1 && !reload) {
3456                 report("Already in %s view", view->name);
3457                 return;
3458         }
3460         if (view->git_dir && !opt_git_dir[0]) {
3461                 report("The %s view is disabled in pager view", view->name);
3462                 return;
3463         }
3465         if (split) {
3466                 display[1] = view;
3467                 current_view = 1;
3468         } else if (!nomaximize) {
3469                 /* Maximize the current view. */
3470                 memset(display, 0, sizeof(display));
3471                 current_view = 0;
3472                 display[current_view] = view;
3473         }
3475         /* No parent signals that this is the first loaded view. */
3476         if (prev && view != prev) {
3477                 view->parent = prev;
3478         }
3480         /* Resize the view when switching between split- and full-screen,
3481          * or when switching between two different full-screen views. */
3482         if (nviews != displayed_views() ||
3483             (nviews == 1 && base_view != display[0]))
3484                 resize_display();
3486         if (view->ops->open) {
3487                 if (view->pipe)
3488                         end_update(view, TRUE);
3489                 if (!view->ops->open(view)) {
3490                         report("Failed to load %s view", view->name);
3491                         return;
3492                 }
3493                 restore_view_position(view);
3495         } else if ((reload || strcmp(view->vid, view->id)) &&
3496                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3497                 report("Failed to load %s view", view->name);
3498                 return;
3499         }
3501         if (split && prev->lineno - prev->offset >= prev->height) {
3502                 /* Take the title line into account. */
3503                 int lines = prev->lineno - prev->offset - prev->height + 1;
3505                 /* Scroll the view that was split if the current line is
3506                  * outside the new limited view. */
3507                 do_scroll_view(prev, lines);
3508         }
3510         if (prev && view != prev && split && view_is_displayed(prev)) {
3511                 /* "Blur" the previous view. */
3512                 update_view_title(prev);
3513         }
3515         if (view->pipe && view->lines == 0) {
3516                 /* Clear the old view and let the incremental updating refill
3517                  * the screen. */
3518                 werase(view->win);
3519                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3520                 report("");
3521         } else if (view_is_displayed(view)) {
3522                 redraw_view(view);
3523                 report("");
3524         }
3527 static void
3528 open_external_viewer(const char *argv[], const char *dir)
3530         def_prog_mode();           /* save current tty modes */
3531         endwin();                  /* restore original tty modes */
3532         io_run_fg(argv, dir);
3533         fprintf(stderr, "Press Enter to continue");
3534         getc(opt_tty);
3535         reset_prog_mode();
3536         redraw_display(TRUE);
3539 static void
3540 open_mergetool(const char *file)
3542         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3544         open_external_viewer(mergetool_argv, opt_cdup);
3547 static void
3548 open_editor(const char *file)
3550         const char *editor_argv[] = { "vi", file, NULL };
3551         const char *editor;
3553         editor = getenv("GIT_EDITOR");
3554         if (!editor && *opt_editor)
3555                 editor = opt_editor;
3556         if (!editor)
3557                 editor = getenv("VISUAL");
3558         if (!editor)
3559                 editor = getenv("EDITOR");
3560         if (!editor)
3561                 editor = "vi";
3563         editor_argv[0] = editor;
3564         open_external_viewer(editor_argv, opt_cdup);
3567 static void
3568 open_run_request(enum request request)
3570         struct run_request *req = get_run_request(request);
3571         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3573         if (!req) {
3574                 report("Unknown run request");
3575                 return;
3576         }
3578         if (format_argv(argv, req->argv, FORMAT_ALL))
3579                 open_external_viewer(argv, NULL);
3580         free_argv(argv);
3583 /*
3584  * User request switch noodle
3585  */
3587 static int
3588 view_driver(struct view *view, enum request request)
3590         int i;
3592         if (request == REQ_NONE)
3593                 return TRUE;
3595         if (request > REQ_NONE) {
3596                 open_run_request(request);
3597                 /* FIXME: When all views can refresh always do this. */
3598                 if (view->refresh)
3599                         request = REQ_REFRESH;
3600                 else
3601                         return TRUE;
3602         }
3604         if (view && view->lines) {
3605                 request = view->ops->request(view, request, &view->line[view->lineno]);
3606                 if (request == REQ_NONE)
3607                         return TRUE;
3608         }
3610         switch (request) {
3611         case REQ_MOVE_UP:
3612         case REQ_MOVE_DOWN:
3613         case REQ_MOVE_PAGE_UP:
3614         case REQ_MOVE_PAGE_DOWN:
3615         case REQ_MOVE_FIRST_LINE:
3616         case REQ_MOVE_LAST_LINE:
3617                 move_view(view, request);
3618                 break;
3620         case REQ_SCROLL_LEFT:
3621         case REQ_SCROLL_RIGHT:
3622         case REQ_SCROLL_LINE_DOWN:
3623         case REQ_SCROLL_LINE_UP:
3624         case REQ_SCROLL_PAGE_DOWN:
3625         case REQ_SCROLL_PAGE_UP:
3626                 scroll_view(view, request);
3627                 break;
3629         case REQ_VIEW_BLAME:
3630                 if (!opt_file[0]) {
3631                         report("No file chosen, press %s to open tree view",
3632                                get_key(view->keymap, REQ_VIEW_TREE));
3633                         break;
3634                 }
3635                 open_view(view, request, OPEN_DEFAULT);
3636                 break;
3638         case REQ_VIEW_BLOB:
3639                 if (!ref_blob[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_PAGER:
3648                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3649                         report("No pager content, press %s to run command from prompt",
3650                                get_key(view->keymap, REQ_PROMPT));
3651                         break;
3652                 }
3653                 open_view(view, request, OPEN_DEFAULT);
3654                 break;
3656         case REQ_VIEW_STAGE:
3657                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3658                         report("No stage content, press %s to open the status view and choose file",
3659                                get_key(view->keymap, REQ_VIEW_STATUS));
3660                         break;
3661                 }
3662                 open_view(view, request, OPEN_DEFAULT);
3663                 break;
3665         case REQ_VIEW_STATUS:
3666                 if (opt_is_inside_work_tree == FALSE) {
3667                         report("The status view requires a working tree");
3668                         break;
3669                 }
3670                 open_view(view, request, OPEN_DEFAULT);
3671                 break;
3673         case REQ_VIEW_MAIN:
3674         case REQ_VIEW_DIFF:
3675         case REQ_VIEW_LOG:
3676         case REQ_VIEW_TREE:
3677         case REQ_VIEW_HELP:
3678         case REQ_VIEW_BRANCH:
3679                 open_view(view, request, OPEN_DEFAULT);
3680                 break;
3682         case REQ_NEXT:
3683         case REQ_PREVIOUS:
3684                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3686                 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3687                     view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3688                     view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3689                     view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3690                     view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3691                         int line;
3693                         view = view->parent;
3694                         line = view->lineno;
3695                         move_view(view, request);
3696                         if (view_is_displayed(view))
3697                                 update_view_title(view);
3698                         if (line != view->lineno)
3699                                 view->ops->request(view, REQ_ENTER,
3700                                                    &view->line[view->lineno]);
3702                 } else {
3703                         move_view(view, request);
3704                 }
3705                 break;
3707         case REQ_VIEW_NEXT:
3708         {
3709                 int nviews = displayed_views();
3710                 int next_view = (current_view + 1) % nviews;
3712                 if (next_view == current_view) {
3713                         report("Only one view is displayed");
3714                         break;
3715                 }
3717                 current_view = next_view;
3718                 /* Blur out the title of the previous view. */
3719                 update_view_title(view);
3720                 report("");
3721                 break;
3722         }
3723         case REQ_REFRESH:
3724                 report("Refreshing is not yet supported for the %s view", view->name);
3725                 break;
3727         case REQ_MAXIMIZE:
3728                 if (displayed_views() == 2)
3729                         maximize_view(view);
3730                 break;
3732         case REQ_OPTIONS:
3733                 open_option_menu();
3734                 break;
3736         case REQ_TOGGLE_LINENO:
3737                 toggle_view_option(&opt_line_number, "line numbers");
3738                 break;
3740         case REQ_TOGGLE_DATE:
3741                 toggle_date();
3742                 break;
3744         case REQ_TOGGLE_AUTHOR:
3745                 toggle_author();
3746                 break;
3748         case REQ_TOGGLE_REV_GRAPH:
3749                 toggle_view_option(&opt_rev_graph, "revision graph display");
3750                 break;
3752         case REQ_TOGGLE_REFS:
3753                 toggle_view_option(&opt_show_refs, "reference display");
3754                 break;
3756         case REQ_TOGGLE_SORT_FIELD:
3757         case REQ_TOGGLE_SORT_ORDER:
3758                 report("Sorting is not yet supported for the %s view", view->name);
3759                 break;
3761         case REQ_SEARCH:
3762         case REQ_SEARCH_BACK:
3763                 search_view(view, request);
3764                 break;
3766         case REQ_FIND_NEXT:
3767         case REQ_FIND_PREV:
3768                 find_next(view, request);
3769                 break;
3771         case REQ_STOP_LOADING:
3772                 foreach_view(view, i) {
3773                         if (view->pipe)
3774                                 report("Stopped loading the %s view", view->name),
3775                         end_update(view, TRUE);
3776                 }
3777                 break;
3779         case REQ_SHOW_VERSION:
3780                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3781                 return TRUE;
3783         case REQ_SCREEN_REDRAW:
3784                 redraw_display(TRUE);
3785                 break;
3787         case REQ_EDIT:
3788                 report("Nothing to edit");
3789                 break;
3791         case REQ_ENTER:
3792                 report("Nothing to enter");
3793                 break;
3795         case REQ_VIEW_CLOSE:
3796                 /* XXX: Mark closed views by letting view->parent point to the
3797                  * view itself. Parents to closed view should never be
3798                  * followed. */
3799                 if (view->parent &&
3800                     view->parent->parent != view->parent) {
3801                         maximize_view(view->parent);
3802                         view->parent = view;
3803                         break;
3804                 }
3805                 /* Fall-through */
3806         case REQ_QUIT:
3807                 return FALSE;
3809         default:
3810                 report("Unknown key, press %s for help",
3811                        get_key(view->keymap, REQ_VIEW_HELP));
3812                 return TRUE;
3813         }
3815         return TRUE;
3819 /*
3820  * View backend utilities
3821  */
3823 enum sort_field {
3824         ORDERBY_NAME,
3825         ORDERBY_DATE,
3826         ORDERBY_AUTHOR,
3827 };
3829 struct sort_state {
3830         const enum sort_field *fields;
3831         size_t size, current;
3832         bool reverse;
3833 };
3835 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3836 #define get_sort_field(state) ((state).fields[(state).current])
3837 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3839 static void
3840 sort_view(struct view *view, enum request request, struct sort_state *state,
3841           int (*compare)(const void *, const void *))
3843         switch (request) {
3844         case REQ_TOGGLE_SORT_FIELD:
3845                 state->current = (state->current + 1) % state->size;
3846                 break;
3848         case REQ_TOGGLE_SORT_ORDER:
3849                 state->reverse = !state->reverse;
3850                 break;
3851         default:
3852                 die("Not a sort request");
3853         }
3855         qsort(view->line, view->lines, sizeof(*view->line), compare);
3856         redraw_view(view);
3859 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3861 /* Small author cache to reduce memory consumption. It uses binary
3862  * search to lookup or find place to position new entries. No entries
3863  * are ever freed. */
3864 static const char *
3865 get_author(const char *name)
3867         static const char **authors;
3868         static size_t authors_size;
3869         int from = 0, to = authors_size - 1;
3871         while (from <= to) {
3872                 size_t pos = (to + from) / 2;
3873                 int cmp = strcmp(name, authors[pos]);
3875                 if (!cmp)
3876                         return authors[pos];
3878                 if (cmp < 0)
3879                         to = pos - 1;
3880                 else
3881                         from = pos + 1;
3882         }
3884         if (!realloc_authors(&authors, authors_size, 1))
3885                 return NULL;
3886         name = strdup(name);
3887         if (!name)
3888                 return NULL;
3890         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3891         authors[from] = name;
3892         authors_size++;
3894         return name;
3897 static void
3898 parse_timesec(struct time *time, const char *sec)
3900         time->sec = (time_t) atol(sec);
3903 static void
3904 parse_timezone(struct time *time, const char *zone)
3906         long tz;
3908         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3909         tz += ('0' - zone[2]) * 60 * 60;
3910         tz += ('0' - zone[3]) * 60 * 10;
3911         tz += ('0' - zone[4]) * 60;
3913         if (zone[0] == '-')
3914                 tz = -tz;
3916         time->tz = tz;
3917         time->sec -= tz;
3920 /* Parse author lines where the name may be empty:
3921  *      author  <email@address.tld> 1138474660 +0100
3922  */
3923 static void
3924 parse_author_line(char *ident, const char **author, struct time *time)
3926         char *nameend = strchr(ident, '<');
3927         char *emailend = strchr(ident, '>');
3929         if (nameend && emailend)
3930                 *nameend = *emailend = 0;
3931         ident = chomp_string(ident);
3932         if (!*ident) {
3933                 if (nameend)
3934                         ident = chomp_string(nameend + 1);
3935                 if (!*ident)
3936                         ident = "Unknown";
3937         }
3939         *author = get_author(ident);
3941         /* Parse epoch and timezone */
3942         if (emailend && emailend[1] == ' ') {
3943                 char *secs = emailend + 2;
3944                 char *zone = strchr(secs, ' ');
3946                 parse_timesec(time, secs);
3948                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3949                         parse_timezone(time, zone + 1);
3950         }
3953 static bool
3954 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3956         char rev[SIZEOF_REV];
3957         const char *revlist_argv[] = {
3958                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3959         };
3960         struct menu_item *items;
3961         char text[SIZEOF_STR];
3962         bool ok = TRUE;
3963         int i;
3965         items = calloc(*parents + 1, sizeof(*items));
3966         if (!items)
3967                 return FALSE;
3969         for (i = 0; i < *parents; i++) {
3970                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3971                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3972                     !(items[i].text = strdup(text))) {
3973                         ok = FALSE;
3974                         break;
3975                 }
3976         }
3978         if (ok) {
3979                 *parents = 0;
3980                 ok = prompt_menu("Select parent", items, parents);
3981         }
3982         for (i = 0; items[i].text; i++)
3983                 free((char *) items[i].text);
3984         free(items);
3985         return ok;
3988 static bool
3989 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3991         char buf[SIZEOF_STR * 4];
3992         const char *revlist_argv[] = {
3993                 "git", "log", "--no-color", "-1",
3994                         "--pretty=format:%P", id, "--", path, NULL
3995         };
3996         int parents;
3998         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3999             (parents = strlen(buf) / 40) < 0) {
4000                 report("Failed to get parent information");
4001                 return FALSE;
4003         } else if (parents == 0) {
4004                 if (path)
4005                         report("Path '%s' does not exist in the parent", path);
4006                 else
4007                         report("The selected commit has no parents");
4008                 return FALSE;
4009         }
4011         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
4012                 return FALSE;
4014         string_copy_rev(rev, &buf[41 * parents]);
4015         return TRUE;
4018 /*
4019  * Pager backend
4020  */
4022 static bool
4023 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4025         char text[SIZEOF_STR];
4027         if (opt_line_number && draw_lineno(view, lineno))
4028                 return TRUE;
4030         string_expand(text, sizeof(text), line->data, opt_tab_size);
4031         draw_text(view, line->type, text, TRUE);
4032         return TRUE;
4035 static bool
4036 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4038         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4039         char ref[SIZEOF_STR];
4041         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4042                 return TRUE;
4044         /* This is the only fatal call, since it can "corrupt" the buffer. */
4045         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4046                 return FALSE;
4048         return TRUE;
4051 static void
4052 add_pager_refs(struct view *view, struct line *line)
4054         char buf[SIZEOF_STR];
4055         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4056         struct ref_list *list;
4057         size_t bufpos = 0, i;
4058         const char *sep = "Refs: ";
4059         bool is_tag = FALSE;
4061         assert(line->type == LINE_COMMIT);
4063         list = get_ref_list(commit_id);
4064         if (!list) {
4065                 if (view->type == VIEW_DIFF)
4066                         goto try_add_describe_ref;
4067                 return;
4068         }
4070         for (i = 0; i < list->size; i++) {
4071                 struct ref *ref = list->refs[i];
4072                 const char *fmt = ref->tag    ? "%s[%s]" :
4073                                   ref->remote ? "%s<%s>" : "%s%s";
4075                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4076                         return;
4077                 sep = ", ";
4078                 if (ref->tag)
4079                         is_tag = TRUE;
4080         }
4082         if (!is_tag && view->type == VIEW_DIFF) {
4083 try_add_describe_ref:
4084                 /* Add <tag>-g<commit_id> "fake" reference. */
4085                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4086                         return;
4087         }
4089         if (bufpos == 0)
4090                 return;
4092         add_line_text(view, buf, LINE_PP_REFS);
4095 static bool
4096 pager_read(struct view *view, char *data)
4098         struct line *line;
4100         if (!data)
4101                 return TRUE;
4103         line = add_line_text(view, data, get_line_type(data));
4104         if (!line)
4105                 return FALSE;
4107         if (line->type == LINE_COMMIT &&
4108             (view->type == VIEW_DIFF ||
4109              view->type == VIEW_LOG))
4110                 add_pager_refs(view, line);
4112         return TRUE;
4115 static enum request
4116 pager_request(struct view *view, enum request request, struct line *line)
4118         int split = 0;
4120         if (request != REQ_ENTER)
4121                 return request;
4123         if (line->type == LINE_COMMIT &&
4124            (view->type == VIEW_LOG ||
4125             view->type == VIEW_PAGER)) {
4126                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4127                 split = 1;
4128         }
4130         /* Always scroll the view even if it was split. That way
4131          * you can use Enter to scroll through the log view and
4132          * split open each commit diff. */
4133         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4135         /* FIXME: A minor workaround. Scrolling the view will call report("")
4136          * but if we are scrolling a non-current view this won't properly
4137          * update the view title. */
4138         if (split)
4139                 update_view_title(view);
4141         return REQ_NONE;
4144 static bool
4145 pager_grep(struct view *view, struct line *line)
4147         const char *text[] = { line->data, NULL };
4149         return grep_text(view, text);
4152 static void
4153 pager_select(struct view *view, struct line *line)
4155         if (line->type == LINE_COMMIT) {
4156                 char *text = (char *)line->data + STRING_SIZE("commit ");
4158                 if (view->type != VIEW_PAGER)
4159                         string_copy_rev(view->ref, text);
4160                 string_copy_rev(ref_commit, text);
4161         }
4164 static struct view_ops pager_ops = {
4165         "line",
4166         NULL,
4167         NULL,
4168         pager_read,
4169         pager_draw,
4170         pager_request,
4171         pager_grep,
4172         pager_select,
4173 };
4175 static const char *log_argv[SIZEOF_ARG] = {
4176         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4177 };
4179 static enum request
4180 log_request(struct view *view, enum request request, struct line *line)
4182         switch (request) {
4183         case REQ_REFRESH:
4184                 load_refs();
4185                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4186                 return REQ_NONE;
4187         default:
4188                 return pager_request(view, request, line);
4189         }
4192 static struct view_ops log_ops = {
4193         "line",
4194         log_argv,
4195         NULL,
4196         pager_read,
4197         pager_draw,
4198         log_request,
4199         pager_grep,
4200         pager_select,
4201 };
4203 static const char *diff_argv[SIZEOF_ARG] = {
4204         "git", "show", "--pretty=fuller", "--no-color", "--root",
4205                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4206 };
4208 static struct view_ops diff_ops = {
4209         "line",
4210         diff_argv,
4211         NULL,
4212         pager_read,
4213         pager_draw,
4214         pager_request,
4215         pager_grep,
4216         pager_select,
4217 };
4219 /*
4220  * Help backend
4221  */
4223 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4225 static bool
4226 help_open_keymap_title(struct view *view, enum keymap keymap)
4228         struct line *line;
4230         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4231                                help_keymap_hidden[keymap] ? '+' : '-',
4232                                enum_name(keymap_table[keymap]));
4233         if (line)
4234                 line->other = keymap;
4236         return help_keymap_hidden[keymap];
4239 static void
4240 help_open_keymap(struct view *view, enum keymap keymap)
4242         const char *group = NULL;
4243         char buf[SIZEOF_STR];
4244         size_t bufpos;
4245         bool add_title = TRUE;
4246         int i;
4248         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4249                 const char *key = NULL;
4251                 if (req_info[i].request == REQ_NONE)
4252                         continue;
4254                 if (!req_info[i].request) {
4255                         group = req_info[i].help;
4256                         continue;
4257                 }
4259                 key = get_keys(keymap, req_info[i].request, TRUE);
4260                 if (!key || !*key)
4261                         continue;
4263                 if (add_title && help_open_keymap_title(view, keymap))
4264                         return;
4265                 add_title = FALSE;
4267                 if (group) {
4268                         add_line_text(view, group, LINE_HELP_GROUP);
4269                         group = NULL;
4270                 }
4272                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4273                                 enum_name(req_info[i]), req_info[i].help);
4274         }
4276         group = "External commands:";
4278         for (i = 0; i < run_requests; i++) {
4279                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4280                 const char *key;
4281                 int argc;
4283                 if (!req || req->keymap != keymap)
4284                         continue;
4286                 key = get_key_name(req->key);
4287                 if (!*key)
4288                         key = "(no key defined)";
4290                 if (add_title && help_open_keymap_title(view, keymap))
4291                         return;
4292                 if (group) {
4293                         add_line_text(view, group, LINE_HELP_GROUP);
4294                         group = NULL;
4295                 }
4297                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4298                         if (!string_format_from(buf, &bufpos, "%s%s",
4299                                                 argc ? " " : "", req->argv[argc]))
4300                                 return;
4302                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4303         }
4306 static bool
4307 help_open(struct view *view)
4309         enum keymap keymap;
4311         reset_view(view);
4312         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4313         add_line_text(view, "", LINE_DEFAULT);
4315         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4316                 help_open_keymap(view, keymap);
4318         return TRUE;
4321 static enum request
4322 help_request(struct view *view, enum request request, struct line *line)
4324         switch (request) {
4325         case REQ_ENTER:
4326                 if (line->type == LINE_HELP_KEYMAP) {
4327                         help_keymap_hidden[line->other] =
4328                                 !help_keymap_hidden[line->other];
4329                         view->p_restore = TRUE;
4330                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4331                 }
4333                 return REQ_NONE;
4334         default:
4335                 return pager_request(view, request, line);
4336         }
4339 static struct view_ops help_ops = {
4340         "line",
4341         NULL,
4342         help_open,
4343         NULL,
4344         pager_draw,
4345         help_request,
4346         pager_grep,
4347         pager_select,
4348 };
4351 /*
4352  * Tree backend
4353  */
4355 struct tree_stack_entry {
4356         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4357         unsigned long lineno;           /* Line number to restore */
4358         char *name;                     /* Position of name in opt_path */
4359 };
4361 /* The top of the path stack. */
4362 static struct tree_stack_entry *tree_stack = NULL;
4363 unsigned long tree_lineno = 0;
4365 static void
4366 pop_tree_stack_entry(void)
4368         struct tree_stack_entry *entry = tree_stack;
4370         tree_lineno = entry->lineno;
4371         entry->name[0] = 0;
4372         tree_stack = entry->prev;
4373         free(entry);
4376 static void
4377 push_tree_stack_entry(const char *name, unsigned long lineno)
4379         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4380         size_t pathlen = strlen(opt_path);
4382         if (!entry)
4383                 return;
4385         entry->prev = tree_stack;
4386         entry->name = opt_path + pathlen;
4387         tree_stack = entry;
4389         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4390                 pop_tree_stack_entry();
4391                 return;
4392         }
4394         /* Move the current line to the first tree entry. */
4395         tree_lineno = 1;
4396         entry->lineno = lineno;
4399 /* Parse output from git-ls-tree(1):
4400  *
4401  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4402  */
4404 #define SIZEOF_TREE_ATTR \
4405         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4407 #define SIZEOF_TREE_MODE \
4408         STRING_SIZE("100644 ")
4410 #define TREE_ID_OFFSET \
4411         STRING_SIZE("100644 blob ")
4413 struct tree_entry {
4414         char id[SIZEOF_REV];
4415         mode_t mode;
4416         struct time time;               /* Date from the author ident. */
4417         const char *author;             /* Author of the commit. */
4418         char name[1];
4419 };
4421 static const char *
4422 tree_path(const struct line *line)
4424         return ((struct tree_entry *) line->data)->name;
4427 static int
4428 tree_compare_entry(const struct line *line1, const struct line *line2)
4430         if (line1->type != line2->type)
4431                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4432         return strcmp(tree_path(line1), tree_path(line2));
4435 static const enum sort_field tree_sort_fields[] = {
4436         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4437 };
4438 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4440 static int
4441 tree_compare(const void *l1, const void *l2)
4443         const struct line *line1 = (const struct line *) l1;
4444         const struct line *line2 = (const struct line *) l2;
4445         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4446         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4448         if (line1->type == LINE_TREE_HEAD)
4449                 return -1;
4450         if (line2->type == LINE_TREE_HEAD)
4451                 return 1;
4453         switch (get_sort_field(tree_sort_state)) {
4454         case ORDERBY_DATE:
4455                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4457         case ORDERBY_AUTHOR:
4458                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4460         case ORDERBY_NAME:
4461         default:
4462                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4463         }
4467 static struct line *
4468 tree_entry(struct view *view, enum line_type type, const char *path,
4469            const char *mode, const char *id)
4471         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4472         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4474         if (!entry || !line) {
4475                 free(entry);
4476                 return NULL;
4477         }
4479         strncpy(entry->name, path, strlen(path));
4480         if (mode)
4481                 entry->mode = strtoul(mode, NULL, 8);
4482         if (id)
4483                 string_copy_rev(entry->id, id);
4485         return line;
4488 static bool
4489 tree_read_date(struct view *view, char *text, bool *read_date)
4491         static const char *author_name;
4492         static struct time author_time;
4494         if (!text && *read_date) {
4495                 *read_date = FALSE;
4496                 return TRUE;
4498         } else if (!text) {
4499                 char *path = *opt_path ? opt_path : ".";
4500                 /* Find next entry to process */
4501                 const char *log_file[] = {
4502                         "git", "log", "--no-color", "--pretty=raw",
4503                                 "--cc", "--raw", view->id, "--", path, NULL
4504                 };
4505                 struct io io = {};
4507                 if (!view->lines) {
4508                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4509                         report("Tree is empty");
4510                         return TRUE;
4511                 }
4513                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4514                         report("Failed to load tree data");
4515                         return TRUE;
4516                 }
4518                 io_done(view->pipe);
4519                 view->io = io;
4520                 *read_date = TRUE;
4521                 return FALSE;
4523         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4524                 parse_author_line(text + STRING_SIZE("author "),
4525                                   &author_name, &author_time);
4527         } else if (*text == ':') {
4528                 char *pos;
4529                 size_t annotated = 1;
4530                 size_t i;
4532                 pos = strchr(text, '\t');
4533                 if (!pos)
4534                         return TRUE;
4535                 text = pos + 1;
4536                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4537                         text += strlen(opt_path);
4538                 pos = strchr(text, '/');
4539                 if (pos)
4540                         *pos = 0;
4542                 for (i = 1; i < view->lines; i++) {
4543                         struct line *line = &view->line[i];
4544                         struct tree_entry *entry = line->data;
4546                         annotated += !!entry->author;
4547                         if (entry->author || strcmp(entry->name, text))
4548                                 continue;
4550                         entry->author = author_name;
4551                         entry->time = author_time;
4552                         line->dirty = 1;
4553                         break;
4554                 }
4556                 if (annotated == view->lines)
4557                         io_kill(view->pipe);
4558         }
4559         return TRUE;
4562 static bool
4563 tree_read(struct view *view, char *text)
4565         static bool read_date = FALSE;
4566         struct tree_entry *data;
4567         struct line *entry, *line;
4568         enum line_type type;
4569         size_t textlen = text ? strlen(text) : 0;
4570         char *path = text + SIZEOF_TREE_ATTR;
4572         if (read_date || !text)
4573                 return tree_read_date(view, text, &read_date);
4575         if (textlen <= SIZEOF_TREE_ATTR)
4576                 return FALSE;
4577         if (view->lines == 0 &&
4578             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4579                 return FALSE;
4581         /* Strip the path part ... */
4582         if (*opt_path) {
4583                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4584                 size_t striplen = strlen(opt_path);
4586                 if (pathlen > striplen)
4587                         memmove(path, path + striplen,
4588                                 pathlen - striplen + 1);
4590                 /* Insert "link" to parent directory. */
4591                 if (view->lines == 1 &&
4592                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4593                         return FALSE;
4594         }
4596         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4597         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4598         if (!entry)
4599                 return FALSE;
4600         data = entry->data;
4602         /* Skip "Directory ..." and ".." line. */
4603         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4604                 if (tree_compare_entry(line, entry) <= 0)
4605                         continue;
4607                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4609                 line->data = data;
4610                 line->type = type;
4611                 for (; line <= entry; line++)
4612                         line->dirty = line->cleareol = 1;
4613                 return TRUE;
4614         }
4616         if (tree_lineno > view->lineno) {
4617                 view->lineno = tree_lineno;
4618                 tree_lineno = 0;
4619         }
4621         return TRUE;
4624 static bool
4625 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4627         struct tree_entry *entry = line->data;
4629         if (line->type == LINE_TREE_HEAD) {
4630                 if (draw_text(view, line->type, "Directory path /", TRUE))
4631                         return TRUE;
4632         } else {
4633                 if (draw_mode(view, entry->mode))
4634                         return TRUE;
4636                 if (opt_author && draw_author(view, entry->author))
4637                         return TRUE;
4639                 if (opt_date && draw_date(view, &entry->time))
4640                         return TRUE;
4641         }
4642         if (draw_text(view, line->type, entry->name, TRUE))
4643                 return TRUE;
4644         return TRUE;
4647 static void
4648 open_blob_editor()
4650         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4651         int fd = mkstemp(file);
4653         if (fd == -1)
4654                 report("Failed to create temporary file");
4655         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4656                 report("Failed to save blob data to file");
4657         else
4658                 open_editor(file);
4659         if (fd != -1)
4660                 unlink(file);
4663 static enum request
4664 tree_request(struct view *view, enum request request, struct line *line)
4666         enum open_flags flags;
4668         switch (request) {
4669         case REQ_VIEW_BLAME:
4670                 if (line->type != LINE_TREE_FILE) {
4671                         report("Blame only supported for files");
4672                         return REQ_NONE;
4673                 }
4675                 string_copy(opt_ref, view->vid);
4676                 return request;
4678         case REQ_EDIT:
4679                 if (line->type != LINE_TREE_FILE) {
4680                         report("Edit only supported for files");
4681                 } else if (!is_head_commit(view->vid)) {
4682                         open_blob_editor();
4683                 } else {
4684                         open_editor(opt_file);
4685                 }
4686                 return REQ_NONE;
4688         case REQ_TOGGLE_SORT_FIELD:
4689         case REQ_TOGGLE_SORT_ORDER:
4690                 sort_view(view, request, &tree_sort_state, tree_compare);
4691                 return REQ_NONE;
4693         case REQ_PARENT:
4694                 if (!*opt_path) {
4695                         /* quit view if at top of tree */
4696                         return REQ_VIEW_CLOSE;
4697                 }
4698                 /* fake 'cd  ..' */
4699                 line = &view->line[1];
4700                 break;
4702         case REQ_ENTER:
4703                 break;
4705         default:
4706                 return request;
4707         }
4709         /* Cleanup the stack if the tree view is at a different tree. */
4710         while (!*opt_path && tree_stack)
4711                 pop_tree_stack_entry();
4713         switch (line->type) {
4714         case LINE_TREE_DIR:
4715                 /* Depending on whether it is a subdirectory or parent link
4716                  * mangle the path buffer. */
4717                 if (line == &view->line[1] && *opt_path) {
4718                         pop_tree_stack_entry();
4720                 } else {
4721                         const char *basename = tree_path(line);
4723                         push_tree_stack_entry(basename, view->lineno);
4724                 }
4726                 /* Trees and subtrees share the same ID, so they are not not
4727                  * unique like blobs. */
4728                 flags = OPEN_RELOAD;
4729                 request = REQ_VIEW_TREE;
4730                 break;
4732         case LINE_TREE_FILE:
4733                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4734                 request = REQ_VIEW_BLOB;
4735                 break;
4737         default:
4738                 return REQ_NONE;
4739         }
4741         open_view(view, request, flags);
4742         if (request == REQ_VIEW_TREE)
4743                 view->lineno = tree_lineno;
4745         return REQ_NONE;
4748 static bool
4749 tree_grep(struct view *view, struct line *line)
4751         struct tree_entry *entry = line->data;
4752         const char *text[] = {
4753                 entry->name,
4754                 opt_author ? entry->author : "",
4755                 mkdate(&entry->time, opt_date),
4756                 NULL
4757         };
4759         return grep_text(view, text);
4762 static void
4763 tree_select(struct view *view, struct line *line)
4765         struct tree_entry *entry = line->data;
4767         if (line->type == LINE_TREE_FILE) {
4768                 string_copy_rev(ref_blob, entry->id);
4769                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4771         } else if (line->type != LINE_TREE_DIR) {
4772                 return;
4773         }
4775         string_copy_rev(view->ref, entry->id);
4778 static bool
4779 tree_prepare(struct view *view)
4781         if (view->lines == 0 && opt_prefix[0]) {
4782                 char *pos = opt_prefix;
4784                 while (pos && *pos) {
4785                         char *end = strchr(pos, '/');
4787                         if (end)
4788                                 *end = 0;
4789                         push_tree_stack_entry(pos, 0);
4790                         pos = end;
4791                         if (end) {
4792                                 *end = '/';
4793                                 pos++;
4794                         }
4795                 }
4797         } else if (strcmp(view->vid, view->id)) {
4798                 opt_path[0] = 0;
4799         }
4801         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4804 static const char *tree_argv[SIZEOF_ARG] = {
4805         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4806 };
4808 static struct view_ops tree_ops = {
4809         "file",
4810         tree_argv,
4811         NULL,
4812         tree_read,
4813         tree_draw,
4814         tree_request,
4815         tree_grep,
4816         tree_select,
4817         tree_prepare,
4818 };
4820 static bool
4821 blob_read(struct view *view, char *line)
4823         if (!line)
4824                 return TRUE;
4825         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4828 static enum request
4829 blob_request(struct view *view, enum request request, struct line *line)
4831         switch (request) {
4832         case REQ_EDIT:
4833                 open_blob_editor();
4834                 return REQ_NONE;
4835         default:
4836                 return pager_request(view, request, line);
4837         }
4840 static const char *blob_argv[SIZEOF_ARG] = {
4841         "git", "cat-file", "blob", "%(blob)", NULL
4842 };
4844 static struct view_ops blob_ops = {
4845         "line",
4846         blob_argv,
4847         NULL,
4848         blob_read,
4849         pager_draw,
4850         blob_request,
4851         pager_grep,
4852         pager_select,
4853 };
4855 /*
4856  * Blame backend
4857  *
4858  * Loading the blame view is a two phase job:
4859  *
4860  *  1. File content is read either using opt_file from the
4861  *     filesystem or using git-cat-file.
4862  *  2. Then blame information is incrementally added by
4863  *     reading output from git-blame.
4864  */
4866 static const char *blame_head_argv[] = {
4867         "git", "blame", "--incremental", "--", "%(file)", NULL
4868 };
4870 static const char *blame_ref_argv[] = {
4871         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4872 };
4874 static const char *blame_cat_file_argv[] = {
4875         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4876 };
4878 struct blame_commit {
4879         char id[SIZEOF_REV];            /* SHA1 ID. */
4880         char title[128];                /* First line of the commit message. */
4881         const char *author;             /* Author of the commit. */
4882         struct time time;               /* Date from the author ident. */
4883         char filename[128];             /* Name of file. */
4884         bool has_previous;              /* Was a "previous" line detected. */
4885 };
4887 struct blame {
4888         struct blame_commit *commit;
4889         unsigned long lineno;
4890         char text[1];
4891 };
4893 static bool
4894 blame_open(struct view *view)
4896         char path[SIZEOF_STR];
4898         if (!view->parent && *opt_prefix) {
4899                 string_copy(path, opt_file);
4900                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4901                         return FALSE;
4902         }
4904         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4905                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4906                         return FALSE;
4907         }
4909         setup_update(view, opt_file);
4910         string_format(view->ref, "%s ...", opt_file);
4912         return TRUE;
4915 static struct blame_commit *
4916 get_blame_commit(struct view *view, const char *id)
4918         size_t i;
4920         for (i = 0; i < view->lines; i++) {
4921                 struct blame *blame = view->line[i].data;
4923                 if (!blame->commit)
4924                         continue;
4926                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4927                         return blame->commit;
4928         }
4930         {
4931                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4933                 if (commit)
4934                         string_ncopy(commit->id, id, SIZEOF_REV);
4935                 return commit;
4936         }
4939 static bool
4940 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4942         const char *pos = *posref;
4944         *posref = NULL;
4945         pos = strchr(pos + 1, ' ');
4946         if (!pos || !isdigit(pos[1]))
4947                 return FALSE;
4948         *number = atoi(pos + 1);
4949         if (*number < min || *number > max)
4950                 return FALSE;
4952         *posref = pos;
4953         return TRUE;
4956 static struct blame_commit *
4957 parse_blame_commit(struct view *view, const char *text, int *blamed)
4959         struct blame_commit *commit;
4960         struct blame *blame;
4961         const char *pos = text + SIZEOF_REV - 2;
4962         size_t orig_lineno = 0;
4963         size_t lineno;
4964         size_t group;
4966         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4967                 return NULL;
4969         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4970             !parse_number(&pos, &lineno, 1, view->lines) ||
4971             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4972                 return NULL;
4974         commit = get_blame_commit(view, text);
4975         if (!commit)
4976                 return NULL;
4978         *blamed += group;
4979         while (group--) {
4980                 struct line *line = &view->line[lineno + group - 1];
4982                 blame = line->data;
4983                 blame->commit = commit;
4984                 blame->lineno = orig_lineno + group - 1;
4985                 line->dirty = 1;
4986         }
4988         return commit;
4991 static bool
4992 blame_read_file(struct view *view, const char *line, bool *read_file)
4994         if (!line) {
4995                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4996                 struct io io = {};
4998                 if (view->lines == 0 && !view->parent)
4999                         die("No blame exist for %s", view->vid);
5001                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
5002                         report("Failed to load blame data");
5003                         return TRUE;
5004                 }
5006                 io_done(view->pipe);
5007                 view->io = io;
5008                 *read_file = FALSE;
5009                 return FALSE;
5011         } else {
5012                 size_t linelen = strlen(line);
5013                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5015                 if (!blame)
5016                         return FALSE;
5018                 blame->commit = NULL;
5019                 strncpy(blame->text, line, linelen);
5020                 blame->text[linelen] = 0;
5021                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5022         }
5025 static bool
5026 match_blame_header(const char *name, char **line)
5028         size_t namelen = strlen(name);
5029         bool matched = !strncmp(name, *line, namelen);
5031         if (matched)
5032                 *line += namelen;
5034         return matched;
5037 static bool
5038 blame_read(struct view *view, char *line)
5040         static struct blame_commit *commit = NULL;
5041         static int blamed = 0;
5042         static bool read_file = TRUE;
5044         if (read_file)
5045                 return blame_read_file(view, line, &read_file);
5047         if (!line) {
5048                 /* Reset all! */
5049                 commit = NULL;
5050                 blamed = 0;
5051                 read_file = TRUE;
5052                 string_format(view->ref, "%s", view->vid);
5053                 if (view_is_displayed(view)) {
5054                         update_view_title(view);
5055                         redraw_view_from(view, 0);
5056                 }
5057                 return TRUE;
5058         }
5060         if (!commit) {
5061                 commit = parse_blame_commit(view, line, &blamed);
5062                 string_format(view->ref, "%s %2d%%", view->vid,
5063                               view->lines ? blamed * 100 / view->lines : 0);
5065         } else if (match_blame_header("author ", &line)) {
5066                 commit->author = get_author(line);
5068         } else if (match_blame_header("author-time ", &line)) {
5069                 parse_timesec(&commit->time, line);
5071         } else if (match_blame_header("author-tz ", &line)) {
5072                 parse_timezone(&commit->time, line);
5074         } else if (match_blame_header("summary ", &line)) {
5075                 string_ncopy(commit->title, line, strlen(line));
5077         } else if (match_blame_header("previous ", &line)) {
5078                 commit->has_previous = TRUE;
5080         } else if (match_blame_header("filename ", &line)) {
5081                 string_ncopy(commit->filename, line, strlen(line));
5082                 commit = NULL;
5083         }
5085         return TRUE;
5088 static bool
5089 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5091         struct blame *blame = line->data;
5092         struct time *time = NULL;
5093         const char *id = NULL, *author = NULL;
5094         char text[SIZEOF_STR];
5096         if (blame->commit && *blame->commit->filename) {
5097                 id = blame->commit->id;
5098                 author = blame->commit->author;
5099                 time = &blame->commit->time;
5100         }
5102         if (opt_date && draw_date(view, time))
5103                 return TRUE;
5105         if (opt_author && draw_author(view, author))
5106                 return TRUE;
5108         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5109                 return TRUE;
5111         if (draw_lineno(view, lineno))
5112                 return TRUE;
5114         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5115         draw_text(view, LINE_DEFAULT, text, TRUE);
5116         return TRUE;
5119 static bool
5120 check_blame_commit(struct blame *blame, bool check_null_id)
5122         if (!blame->commit)
5123                 report("Commit data not loaded yet");
5124         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5125                 report("No commit exist for the selected line");
5126         else
5127                 return TRUE;
5128         return FALSE;
5131 static void
5132 setup_blame_parent_line(struct view *view, struct blame *blame)
5134         const char *diff_tree_argv[] = {
5135                 "git", "diff-tree", "-U0", blame->commit->id,
5136                         "--", blame->commit->filename, NULL
5137         };
5138         struct io io = {};
5139         int parent_lineno = -1;
5140         int blamed_lineno = -1;
5141         char *line;
5143         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5144                 return;
5146         while ((line = io_get(&io, '\n', TRUE))) {
5147                 if (*line == '@') {
5148                         char *pos = strchr(line, '+');
5150                         parent_lineno = atoi(line + 4);
5151                         if (pos)
5152                                 blamed_lineno = atoi(pos + 1);
5154                 } else if (*line == '+' && parent_lineno != -1) {
5155                         if (blame->lineno == blamed_lineno - 1 &&
5156                             !strcmp(blame->text, line + 1)) {
5157                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5158                                 break;
5159                         }
5160                         blamed_lineno++;
5161                 }
5162         }
5164         io_done(&io);
5167 static enum request
5168 blame_request(struct view *view, enum request request, struct line *line)
5170         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5171         struct blame *blame = line->data;
5173         switch (request) {
5174         case REQ_VIEW_BLAME:
5175                 if (check_blame_commit(blame, TRUE)) {
5176                         string_copy(opt_ref, blame->commit->id);
5177                         string_copy(opt_file, blame->commit->filename);
5178                         if (blame->lineno)
5179                                 view->lineno = blame->lineno;
5180                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5181                 }
5182                 break;
5184         case REQ_PARENT:
5185                 if (check_blame_commit(blame, TRUE) &&
5186                     select_commit_parent(blame->commit->id, opt_ref,
5187                                          blame->commit->filename)) {
5188                         string_copy(opt_file, blame->commit->filename);
5189                         setup_blame_parent_line(view, blame);
5190                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5191                 }
5192                 break;
5194         case REQ_ENTER:
5195                 if (!check_blame_commit(blame, FALSE))
5196                         break;
5198                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5199                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5200                         break;
5202                 if (!strcmp(blame->commit->id, NULL_ID)) {
5203                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5204                         const char *diff_index_argv[] = {
5205                                 "git", "diff-index", "--root", "--patch-with-stat",
5206                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5207                         };
5209                         if (!blame->commit->has_previous) {
5210                                 diff_index_argv[1] = "diff";
5211                                 diff_index_argv[2] = "--no-color";
5212                                 diff_index_argv[6] = "--";
5213                                 diff_index_argv[7] = "/dev/null";
5214                         }
5216                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5217                                 report("Failed to allocate diff command");
5218                                 break;
5219                         }
5220                         flags |= OPEN_PREPARED;
5221                 }
5223                 open_view(view, REQ_VIEW_DIFF, flags);
5224                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5225                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5226                 break;
5228         default:
5229                 return request;
5230         }
5232         return REQ_NONE;
5235 static bool
5236 blame_grep(struct view *view, struct line *line)
5238         struct blame *blame = line->data;
5239         struct blame_commit *commit = blame->commit;
5240         const char *text[] = {
5241                 blame->text,
5242                 commit ? commit->title : "",
5243                 commit ? commit->id : "",
5244                 commit && opt_author ? commit->author : "",
5245                 commit ? mkdate(&commit->time, opt_date) : "",
5246                 NULL
5247         };
5249         return grep_text(view, text);
5252 static void
5253 blame_select(struct view *view, struct line *line)
5255         struct blame *blame = line->data;
5256         struct blame_commit *commit = blame->commit;
5258         if (!commit)
5259                 return;
5261         if (!strcmp(commit->id, NULL_ID))
5262                 string_ncopy(ref_commit, "HEAD", 4);
5263         else
5264                 string_copy_rev(ref_commit, commit->id);
5267 static struct view_ops blame_ops = {
5268         "line",
5269         NULL,
5270         blame_open,
5271         blame_read,
5272         blame_draw,
5273         blame_request,
5274         blame_grep,
5275         blame_select,
5276 };
5278 /*
5279  * Branch backend
5280  */
5282 struct branch {
5283         const char *author;             /* Author of the last commit. */
5284         struct time time;               /* Date of the last activity. */
5285         const struct ref *ref;          /* Name and commit ID information. */
5286 };
5288 static const struct ref branch_all;
5290 static const enum sort_field branch_sort_fields[] = {
5291         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5292 };
5293 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5295 static int
5296 branch_compare(const void *l1, const void *l2)
5298         const struct branch *branch1 = ((const struct line *) l1)->data;
5299         const struct branch *branch2 = ((const struct line *) l2)->data;
5301         switch (get_sort_field(branch_sort_state)) {
5302         case ORDERBY_DATE:
5303                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5305         case ORDERBY_AUTHOR:
5306                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5308         case ORDERBY_NAME:
5309         default:
5310                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5311         }
5314 static bool
5315 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5317         struct branch *branch = line->data;
5318         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5320         if (opt_date && draw_date(view, &branch->time))
5321                 return TRUE;
5323         if (opt_author && draw_author(view, branch->author))
5324                 return TRUE;
5326         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5327         return TRUE;
5330 static enum request
5331 branch_request(struct view *view, enum request request, struct line *line)
5333         struct branch *branch = line->data;
5335         switch (request) {
5336         case REQ_REFRESH:
5337                 load_refs();
5338                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5339                 return REQ_NONE;
5341         case REQ_TOGGLE_SORT_FIELD:
5342         case REQ_TOGGLE_SORT_ORDER:
5343                 sort_view(view, request, &branch_sort_state, branch_compare);
5344                 return REQ_NONE;
5346         case REQ_ENTER:
5347                 if (branch->ref == &branch_all) {
5348                         const char *all_branches_argv[] = {
5349                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5350                                       "--topo-order", "--all", NULL
5351                         };
5352                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5354                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5355                                 report("Failed to load view of all branches");
5356                                 return REQ_NONE;
5357                         }
5358                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5359                 } else {
5360                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5361                 }
5362                 return REQ_NONE;
5364         default:
5365                 return request;
5366         }
5369 static bool
5370 branch_read(struct view *view, char *line)
5372         static char id[SIZEOF_REV];
5373         struct branch *reference;
5374         size_t i;
5376         if (!line)
5377                 return TRUE;
5379         switch (get_line_type(line)) {
5380         case LINE_COMMIT:
5381                 string_copy_rev(id, line + STRING_SIZE("commit "));
5382                 return TRUE;
5384         case LINE_AUTHOR:
5385                 for (i = 0, reference = NULL; i < view->lines; i++) {
5386                         struct branch *branch = view->line[i].data;
5388                         if (strcmp(branch->ref->id, id))
5389                                 continue;
5391                         view->line[i].dirty = TRUE;
5392                         if (reference) {
5393                                 branch->author = reference->author;
5394                                 branch->time = reference->time;
5395                                 continue;
5396                         }
5398                         parse_author_line(line + STRING_SIZE("author "),
5399                                           &branch->author, &branch->time);
5400                         reference = branch;
5401                 }
5402                 return TRUE;
5404         default:
5405                 return TRUE;
5406         }
5410 static bool
5411 branch_open_visitor(void *data, const struct ref *ref)
5413         struct view *view = data;
5414         struct branch *branch;
5416         if (ref->tag || ref->ltag || ref->remote)
5417                 return TRUE;
5419         branch = calloc(1, sizeof(*branch));
5420         if (!branch)
5421                 return FALSE;
5423         branch->ref = ref;
5424         return !!add_line_data(view, branch, LINE_DEFAULT);
5427 static bool
5428 branch_open(struct view *view)
5430         const char *branch_log[] = {
5431                 "git", "log", "--no-color", "--pretty=raw",
5432                         "--simplify-by-decoration", "--all", NULL
5433         };
5435         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5436                 report("Failed to load branch data");
5437                 return TRUE;
5438         }
5440         setup_update(view, view->id);
5441         branch_open_visitor(view, &branch_all);
5442         foreach_ref(branch_open_visitor, view);
5443         view->p_restore = TRUE;
5445         return TRUE;
5448 static bool
5449 branch_grep(struct view *view, struct line *line)
5451         struct branch *branch = line->data;
5452         const char *text[] = {
5453                 branch->ref->name,
5454                 branch->author,
5455                 NULL
5456         };
5458         return grep_text(view, text);
5461 static void
5462 branch_select(struct view *view, struct line *line)
5464         struct branch *branch = line->data;
5466         string_copy_rev(view->ref, branch->ref->id);
5467         string_copy_rev(ref_commit, branch->ref->id);
5468         string_copy_rev(ref_head, branch->ref->id);
5469         string_copy_rev(ref_branch, branch->ref->name);
5472 static struct view_ops branch_ops = {
5473         "branch",
5474         NULL,
5475         branch_open,
5476         branch_read,
5477         branch_draw,
5478         branch_request,
5479         branch_grep,
5480         branch_select,
5481 };
5483 /*
5484  * Status backend
5485  */
5487 struct status {
5488         char status;
5489         struct {
5490                 mode_t mode;
5491                 char rev[SIZEOF_REV];
5492                 char name[SIZEOF_STR];
5493         } old;
5494         struct {
5495                 mode_t mode;
5496                 char rev[SIZEOF_REV];
5497                 char name[SIZEOF_STR];
5498         } new;
5499 };
5501 static char status_onbranch[SIZEOF_STR];
5502 static struct status stage_status;
5503 static enum line_type stage_line_type;
5504 static size_t stage_chunks;
5505 static int *stage_chunk;
5507 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5509 /* This should work even for the "On branch" line. */
5510 static inline bool
5511 status_has_none(struct view *view, struct line *line)
5513         return line < view->line + view->lines && !line[1].data;
5516 /* Get fields from the diff line:
5517  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5518  */
5519 static inline bool
5520 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5522         const char *old_mode = buf +  1;
5523         const char *new_mode = buf +  8;
5524         const char *old_rev  = buf + 15;
5525         const char *new_rev  = buf + 56;
5526         const char *status   = buf + 97;
5528         if (bufsize < 98 ||
5529             old_mode[-1] != ':' ||
5530             new_mode[-1] != ' ' ||
5531             old_rev[-1]  != ' ' ||
5532             new_rev[-1]  != ' ' ||
5533             status[-1]   != ' ')
5534                 return FALSE;
5536         file->status = *status;
5538         string_copy_rev(file->old.rev, old_rev);
5539         string_copy_rev(file->new.rev, new_rev);
5541         file->old.mode = strtoul(old_mode, NULL, 8);
5542         file->new.mode = strtoul(new_mode, NULL, 8);
5544         file->old.name[0] = file->new.name[0] = 0;
5546         return TRUE;
5549 static bool
5550 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5552         struct status *unmerged = NULL;
5553         char *buf;
5554         struct io io = {};
5556         if (!io_run(&io, argv, opt_cdup, IO_RD))
5557                 return FALSE;
5559         add_line_data(view, NULL, type);
5561         while ((buf = io_get(&io, 0, TRUE))) {
5562                 struct status *file = unmerged;
5564                 if (!file) {
5565                         file = calloc(1, sizeof(*file));
5566                         if (!file || !add_line_data(view, file, type))
5567                                 goto error_out;
5568                 }
5570                 /* Parse diff info part. */
5571                 if (status) {
5572                         file->status = status;
5573                         if (status == 'A')
5574                                 string_copy(file->old.rev, NULL_ID);
5576                 } else if (!file->status || file == unmerged) {
5577                         if (!status_get_diff(file, buf, strlen(buf)))
5578                                 goto error_out;
5580                         buf = io_get(&io, 0, TRUE);
5581                         if (!buf)
5582                                 break;
5584                         /* Collapse all modified entries that follow an
5585                          * associated unmerged entry. */
5586                         if (unmerged == file) {
5587                                 unmerged->status = 'U';
5588                                 unmerged = NULL;
5589                         } else if (file->status == 'U') {
5590                                 unmerged = file;
5591                         }
5592                 }
5594                 /* Grab the old name for rename/copy. */
5595                 if (!*file->old.name &&
5596                     (file->status == 'R' || file->status == 'C')) {
5597                         string_ncopy(file->old.name, buf, strlen(buf));
5599                         buf = io_get(&io, 0, TRUE);
5600                         if (!buf)
5601                                 break;
5602                 }
5604                 /* git-ls-files just delivers a NUL separated list of
5605                  * file names similar to the second half of the
5606                  * git-diff-* output. */
5607                 string_ncopy(file->new.name, buf, strlen(buf));
5608                 if (!*file->old.name)
5609                         string_copy(file->old.name, file->new.name);
5610                 file = NULL;
5611         }
5613         if (io_error(&io)) {
5614 error_out:
5615                 io_done(&io);
5616                 return FALSE;
5617         }
5619         if (!view->line[view->lines - 1].data)
5620                 add_line_data(view, NULL, LINE_STAT_NONE);
5622         io_done(&io);
5623         return TRUE;
5626 /* Don't show unmerged entries in the staged section. */
5627 static const char *status_diff_index_argv[] = {
5628         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5629                              "--cached", "-M", "HEAD", NULL
5630 };
5632 static const char *status_diff_files_argv[] = {
5633         "git", "diff-files", "-z", NULL
5634 };
5636 static const char *status_list_other_argv[] = {
5637         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5638 };
5640 static const char *status_list_no_head_argv[] = {
5641         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5642 };
5644 static const char *update_index_argv[] = {
5645         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5646 };
5648 /* Restore the previous line number to stay in the context or select a
5649  * line with something that can be updated. */
5650 static void
5651 status_restore(struct view *view)
5653         if (view->p_lineno >= view->lines)
5654                 view->p_lineno = view->lines - 1;
5655         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5656                 view->p_lineno++;
5657         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5658                 view->p_lineno--;
5660         /* If the above fails, always skip the "On branch" line. */
5661         if (view->p_lineno < view->lines)
5662                 view->lineno = view->p_lineno;
5663         else
5664                 view->lineno = 1;
5666         if (view->lineno < view->offset)
5667                 view->offset = view->lineno;
5668         else if (view->offset + view->height <= view->lineno)
5669                 view->offset = view->lineno - view->height + 1;
5671         view->p_restore = FALSE;
5674 static void
5675 status_update_onbranch(void)
5677         static const char *paths[][2] = {
5678                 { "rebase-apply/rebasing",      "Rebasing" },
5679                 { "rebase-apply/applying",      "Applying mailbox" },
5680                 { "rebase-apply/",              "Rebasing mailbox" },
5681                 { "rebase-merge/interactive",   "Interactive rebase" },
5682                 { "rebase-merge/",              "Rebase merge" },
5683                 { "MERGE_HEAD",                 "Merging" },
5684                 { "BISECT_LOG",                 "Bisecting" },
5685                 { "HEAD",                       "On branch" },
5686         };
5687         char buf[SIZEOF_STR];
5688         struct stat stat;
5689         int i;
5691         if (is_initial_commit()) {
5692                 string_copy(status_onbranch, "Initial commit");
5693                 return;
5694         }
5696         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5697                 char *head = opt_head;
5699                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5700                     lstat(buf, &stat) < 0)
5701                         continue;
5703                 if (!*opt_head) {
5704                         struct io io = {};
5706                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5707                             io_read_buf(&io, buf, sizeof(buf))) {
5708                                 head = buf;
5709                                 if (!prefixcmp(head, "refs/heads/"))
5710                                         head += STRING_SIZE("refs/heads/");
5711                         }
5712                 }
5714                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5715                         string_copy(status_onbranch, opt_head);
5716                 return;
5717         }
5719         string_copy(status_onbranch, "Not currently on any branch");
5722 /* First parse staged info using git-diff-index(1), then parse unstaged
5723  * info using git-diff-files(1), and finally untracked files using
5724  * git-ls-files(1). */
5725 static bool
5726 status_open(struct view *view)
5728         reset_view(view);
5730         add_line_data(view, NULL, LINE_STAT_HEAD);
5731         status_update_onbranch();
5733         io_run_bg(update_index_argv);
5735         if (is_initial_commit()) {
5736                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5737                         return FALSE;
5738         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5739                 return FALSE;
5740         }
5742         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5743             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5744                 return FALSE;
5746         /* Restore the exact position or use the specialized restore
5747          * mode? */
5748         if (!view->p_restore)
5749                 status_restore(view);
5750         return TRUE;
5753 static bool
5754 status_draw(struct view *view, struct line *line, unsigned int lineno)
5756         struct status *status = line->data;
5757         enum line_type type;
5758         const char *text;
5760         if (!status) {
5761                 switch (line->type) {
5762                 case LINE_STAT_STAGED:
5763                         type = LINE_STAT_SECTION;
5764                         text = "Changes to be committed:";
5765                         break;
5767                 case LINE_STAT_UNSTAGED:
5768                         type = LINE_STAT_SECTION;
5769                         text = "Changed but not updated:";
5770                         break;
5772                 case LINE_STAT_UNTRACKED:
5773                         type = LINE_STAT_SECTION;
5774                         text = "Untracked files:";
5775                         break;
5777                 case LINE_STAT_NONE:
5778                         type = LINE_DEFAULT;
5779                         text = "  (no files)";
5780                         break;
5782                 case LINE_STAT_HEAD:
5783                         type = LINE_STAT_HEAD;
5784                         text = status_onbranch;
5785                         break;
5787                 default:
5788                         return FALSE;
5789                 }
5790         } else {
5791                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5793                 buf[0] = status->status;
5794                 if (draw_text(view, line->type, buf, TRUE))
5795                         return TRUE;
5796                 type = LINE_DEFAULT;
5797                 text = status->new.name;
5798         }
5800         draw_text(view, type, text, TRUE);
5801         return TRUE;
5804 static enum request
5805 status_load_error(struct view *view, struct view *stage, const char *path)
5807         if (displayed_views() == 2 || display[current_view] != view)
5808                 maximize_view(view);
5809         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5810         return REQ_NONE;
5813 static enum request
5814 status_enter(struct view *view, struct line *line)
5816         struct status *status = line->data;
5817         const char *oldpath = status ? status->old.name : NULL;
5818         /* Diffs for unmerged entries are empty when passing the new
5819          * path, so leave it empty. */
5820         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5821         const char *info;
5822         enum open_flags split;
5823         struct view *stage = VIEW(REQ_VIEW_STAGE);
5825         if (line->type == LINE_STAT_NONE ||
5826             (!status && line[1].type == LINE_STAT_NONE)) {
5827                 report("No file to diff");
5828                 return REQ_NONE;
5829         }
5831         switch (line->type) {
5832         case LINE_STAT_STAGED:
5833                 if (is_initial_commit()) {
5834                         const char *no_head_diff_argv[] = {
5835                                 "git", "diff", "--no-color", "--patch-with-stat",
5836                                         "--", "/dev/null", newpath, NULL
5837                         };
5839                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5840                                 return status_load_error(view, stage, newpath);
5841                 } else {
5842                         const char *index_show_argv[] = {
5843                                 "git", "diff-index", "--root", "--patch-with-stat",
5844                                         "-C", "-M", "--cached", "HEAD", "--",
5845                                         oldpath, newpath, NULL
5846                         };
5848                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5849                                 return status_load_error(view, stage, newpath);
5850                 }
5852                 if (status)
5853                         info = "Staged changes to %s";
5854                 else
5855                         info = "Staged changes";
5856                 break;
5858         case LINE_STAT_UNSTAGED:
5859         {
5860                 const char *files_show_argv[] = {
5861                         "git", "diff-files", "--root", "--patch-with-stat",
5862                                 "-C", "-M", "--", oldpath, newpath, NULL
5863                 };
5865                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5866                         return status_load_error(view, stage, newpath);
5867                 if (status)
5868                         info = "Unstaged changes to %s";
5869                 else
5870                         info = "Unstaged changes";
5871                 break;
5872         }
5873         case LINE_STAT_UNTRACKED:
5874                 if (!newpath) {
5875                         report("No file to show");
5876                         return REQ_NONE;
5877                 }
5879                 if (!suffixcmp(status->new.name, -1, "/")) {
5880                         report("Cannot display a directory");
5881                         return REQ_NONE;
5882                 }
5884                 if (!prepare_update_file(stage, newpath))
5885                         return status_load_error(view, stage, newpath);
5886                 info = "Untracked file %s";
5887                 break;
5889         case LINE_STAT_HEAD:
5890                 return REQ_NONE;
5892         default:
5893                 die("line type %d not handled in switch", line->type);
5894         }
5896         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5897         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5898         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5899                 if (status) {
5900                         stage_status = *status;
5901                 } else {
5902                         memset(&stage_status, 0, sizeof(stage_status));
5903                 }
5905                 stage_line_type = line->type;
5906                 stage_chunks = 0;
5907                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5908         }
5910         return REQ_NONE;
5913 static bool
5914 status_exists(struct status *status, enum line_type type)
5916         struct view *view = VIEW(REQ_VIEW_STATUS);
5917         unsigned long lineno;
5919         for (lineno = 0; lineno < view->lines; lineno++) {
5920                 struct line *line = &view->line[lineno];
5921                 struct status *pos = line->data;
5923                 if (line->type != type)
5924                         continue;
5925                 if (!pos && (!status || !status->status) && line[1].data) {
5926                         select_view_line(view, lineno);
5927                         return TRUE;
5928                 }
5929                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5930                         select_view_line(view, lineno);
5931                         return TRUE;
5932                 }
5933         }
5935         return FALSE;
5939 static bool
5940 status_update_prepare(struct io *io, enum line_type type)
5942         const char *staged_argv[] = {
5943                 "git", "update-index", "-z", "--index-info", NULL
5944         };
5945         const char *others_argv[] = {
5946                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5947         };
5949         switch (type) {
5950         case LINE_STAT_STAGED:
5951                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5953         case LINE_STAT_UNSTAGED:
5954         case LINE_STAT_UNTRACKED:
5955                 return io_run(io, others_argv, opt_cdup, IO_WR);
5957         default:
5958                 die("line type %d not handled in switch", type);
5959                 return FALSE;
5960         }
5963 static bool
5964 status_update_write(struct io *io, struct status *status, enum line_type type)
5966         char buf[SIZEOF_STR];
5967         size_t bufsize = 0;
5969         switch (type) {
5970         case LINE_STAT_STAGED:
5971                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5972                                         status->old.mode,
5973                                         status->old.rev,
5974                                         status->old.name, 0))
5975                         return FALSE;
5976                 break;
5978         case LINE_STAT_UNSTAGED:
5979         case LINE_STAT_UNTRACKED:
5980                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5981                         return FALSE;
5982                 break;
5984         default:
5985                 die("line type %d not handled in switch", type);
5986         }
5988         return io_write(io, buf, bufsize);
5991 static bool
5992 status_update_file(struct status *status, enum line_type type)
5994         struct io io = {};
5995         bool result;
5997         if (!status_update_prepare(&io, type))
5998                 return FALSE;
6000         result = status_update_write(&io, status, type);
6001         return io_done(&io) && result;
6004 static bool
6005 status_update_files(struct view *view, struct line *line)
6007         char buf[sizeof(view->ref)];
6008         struct io io = {};
6009         bool result = TRUE;
6010         struct line *pos = view->line + view->lines;
6011         int files = 0;
6012         int file, done;
6013         int cursor_y = -1, cursor_x = -1;
6015         if (!status_update_prepare(&io, line->type))
6016                 return FALSE;
6018         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6019                 files++;
6021         string_copy(buf, view->ref);
6022         getsyx(cursor_y, cursor_x);
6023         for (file = 0, done = 5; result && file < files; line++, file++) {
6024                 int almost_done = file * 100 / files;
6026                 if (almost_done > done) {
6027                         done = almost_done;
6028                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6029                                       file, files, done);
6030                         update_view_title(view);
6031                         setsyx(cursor_y, cursor_x);
6032                         doupdate();
6033                 }
6034                 result = status_update_write(&io, line->data, line->type);
6035         }
6036         string_copy(view->ref, buf);
6038         return io_done(&io) && result;
6041 static bool
6042 status_update(struct view *view)
6044         struct line *line = &view->line[view->lineno];
6046         assert(view->lines);
6048         if (!line->data) {
6049                 /* This should work even for the "On branch" line. */
6050                 if (line < view->line + view->lines && !line[1].data) {
6051                         report("Nothing to update");
6052                         return FALSE;
6053                 }
6055                 if (!status_update_files(view, line + 1)) {
6056                         report("Failed to update file status");
6057                         return FALSE;
6058                 }
6060         } else if (!status_update_file(line->data, line->type)) {
6061                 report("Failed to update file status");
6062                 return FALSE;
6063         }
6065         return TRUE;
6068 static bool
6069 status_revert(struct status *status, enum line_type type, bool has_none)
6071         if (!status || type != LINE_STAT_UNSTAGED) {
6072                 if (type == LINE_STAT_STAGED) {
6073                         report("Cannot revert changes to staged files");
6074                 } else if (type == LINE_STAT_UNTRACKED) {
6075                         report("Cannot revert changes to untracked files");
6076                 } else if (has_none) {
6077                         report("Nothing to revert");
6078                 } else {
6079                         report("Cannot revert changes to multiple files");
6080                 }
6082         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6083                 char mode[10] = "100644";
6084                 const char *reset_argv[] = {
6085                         "git", "update-index", "--cacheinfo", mode,
6086                                 status->old.rev, status->old.name, NULL
6087                 };
6088                 const char *checkout_argv[] = {
6089                         "git", "checkout", "--", status->old.name, NULL
6090                 };
6092                 if (status->status == 'U') {
6093                         string_format(mode, "%5o", status->old.mode);
6095                         if (status->old.mode == 0 && status->new.mode == 0) {
6096                                 reset_argv[2] = "--force-remove";
6097                                 reset_argv[3] = status->old.name;
6098                                 reset_argv[4] = NULL;
6099                         }
6101                         if (!io_run_fg(reset_argv, opt_cdup))
6102                                 return FALSE;
6103                         if (status->old.mode == 0 && status->new.mode == 0)
6104                                 return TRUE;
6105                 }
6107                 return io_run_fg(checkout_argv, opt_cdup);
6108         }
6110         return FALSE;
6113 static enum request
6114 status_request(struct view *view, enum request request, struct line *line)
6116         struct status *status = line->data;
6118         switch (request) {
6119         case REQ_STATUS_UPDATE:
6120                 if (!status_update(view))
6121                         return REQ_NONE;
6122                 break;
6124         case REQ_STATUS_REVERT:
6125                 if (!status_revert(status, line->type, status_has_none(view, line)))
6126                         return REQ_NONE;
6127                 break;
6129         case REQ_STATUS_MERGE:
6130                 if (!status || status->status != 'U') {
6131                         report("Merging only possible for files with unmerged status ('U').");
6132                         return REQ_NONE;
6133                 }
6134                 open_mergetool(status->new.name);
6135                 break;
6137         case REQ_EDIT:
6138                 if (!status)
6139                         return request;
6140                 if (status->status == 'D') {
6141                         report("File has been deleted.");
6142                         return REQ_NONE;
6143                 }
6145                 open_editor(status->new.name);
6146                 break;
6148         case REQ_VIEW_BLAME:
6149                 if (status)
6150                         opt_ref[0] = 0;
6151                 return request;
6153         case REQ_ENTER:
6154                 /* After returning the status view has been split to
6155                  * show the stage view. No further reloading is
6156                  * necessary. */
6157                 return status_enter(view, line);
6159         case REQ_REFRESH:
6160                 /* Simply reload the view. */
6161                 break;
6163         default:
6164                 return request;
6165         }
6167         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6169         return REQ_NONE;
6172 static void
6173 status_select(struct view *view, struct line *line)
6175         struct status *status = line->data;
6176         char file[SIZEOF_STR] = "all files";
6177         const char *text;
6178         const char *key;
6180         if (status && !string_format(file, "'%s'", status->new.name))
6181                 return;
6183         if (!status && line[1].type == LINE_STAT_NONE)
6184                 line++;
6186         switch (line->type) {
6187         case LINE_STAT_STAGED:
6188                 text = "Press %s to unstage %s for commit";
6189                 break;
6191         case LINE_STAT_UNSTAGED:
6192                 text = "Press %s to stage %s for commit";
6193                 break;
6195         case LINE_STAT_UNTRACKED:
6196                 text = "Press %s to stage %s for addition";
6197                 break;
6199         case LINE_STAT_HEAD:
6200         case LINE_STAT_NONE:
6201                 text = "Nothing to update";
6202                 break;
6204         default:
6205                 die("line type %d not handled in switch", line->type);
6206         }
6208         if (status && status->status == 'U') {
6209                 text = "Press %s to resolve conflict in %s";
6210                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6212         } else {
6213                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6214         }
6216         string_format(view->ref, text, key, file);
6217         if (status)
6218                 string_copy(opt_file, status->new.name);
6221 static bool
6222 status_grep(struct view *view, struct line *line)
6224         struct status *status = line->data;
6226         if (status) {
6227                 const char buf[2] = { status->status, 0 };
6228                 const char *text[] = { status->new.name, buf, NULL };
6230                 return grep_text(view, text);
6231         }
6233         return FALSE;
6236 static struct view_ops status_ops = {
6237         "file",
6238         NULL,
6239         status_open,
6240         NULL,
6241         status_draw,
6242         status_request,
6243         status_grep,
6244         status_select,
6245 };
6248 static bool
6249 stage_diff_write(struct io *io, struct line *line, struct line *end)
6251         while (line < end) {
6252                 if (!io_write(io, line->data, strlen(line->data)) ||
6253                     !io_write(io, "\n", 1))
6254                         return FALSE;
6255                 line++;
6256                 if (line->type == LINE_DIFF_CHUNK ||
6257                     line->type == LINE_DIFF_HEADER)
6258                         break;
6259         }
6261         return TRUE;
6264 static struct line *
6265 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6267         for (; view->line < line; line--)
6268                 if (line->type == type)
6269                         return line;
6271         return NULL;
6274 static bool
6275 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6277         const char *apply_argv[SIZEOF_ARG] = {
6278                 "git", "apply", "--whitespace=nowarn", NULL
6279         };
6280         struct line *diff_hdr;
6281         struct io io = {};
6282         int argc = 3;
6284         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6285         if (!diff_hdr)
6286                 return FALSE;
6288         if (!revert)
6289                 apply_argv[argc++] = "--cached";
6290         if (revert || stage_line_type == LINE_STAT_STAGED)
6291                 apply_argv[argc++] = "-R";
6292         apply_argv[argc++] = "-";
6293         apply_argv[argc++] = NULL;
6294         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6295                 return FALSE;
6297         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6298             !stage_diff_write(&io, chunk, view->line + view->lines))
6299                 chunk = NULL;
6301         io_done(&io);
6302         io_run_bg(update_index_argv);
6304         return chunk ? TRUE : FALSE;
6307 static bool
6308 stage_update(struct view *view, struct line *line)
6310         struct line *chunk = NULL;
6312         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6313                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6315         if (chunk) {
6316                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6317                         report("Failed to apply chunk");
6318                         return FALSE;
6319                 }
6321         } else if (!stage_status.status) {
6322                 view = VIEW(REQ_VIEW_STATUS);
6324                 for (line = view->line; line < view->line + view->lines; line++)
6325                         if (line->type == stage_line_type)
6326                                 break;
6328                 if (!status_update_files(view, line + 1)) {
6329                         report("Failed to update files");
6330                         return FALSE;
6331                 }
6333         } else if (!status_update_file(&stage_status, stage_line_type)) {
6334                 report("Failed to update file");
6335                 return FALSE;
6336         }
6338         return TRUE;
6341 static bool
6342 stage_revert(struct view *view, struct line *line)
6344         struct line *chunk = NULL;
6346         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6347                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6349         if (chunk) {
6350                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6351                         return FALSE;
6353                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6354                         report("Failed to revert chunk");
6355                         return FALSE;
6356                 }
6357                 return TRUE;
6359         } else {
6360                 return status_revert(stage_status.status ? &stage_status : NULL,
6361                                      stage_line_type, FALSE);
6362         }
6366 static void
6367 stage_next(struct view *view, struct line *line)
6369         int i;
6371         if (!stage_chunks) {
6372                 for (line = view->line; line < view->line + view->lines; line++) {
6373                         if (line->type != LINE_DIFF_CHUNK)
6374                                 continue;
6376                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6377                                 report("Allocation failure");
6378                                 return;
6379                         }
6381                         stage_chunk[stage_chunks++] = line - view->line;
6382                 }
6383         }
6385         for (i = 0; i < stage_chunks; i++) {
6386                 if (stage_chunk[i] > view->lineno) {
6387                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6388                         report("Chunk %d of %d", i + 1, stage_chunks);
6389                         return;
6390                 }
6391         }
6393         report("No next chunk found");
6396 static enum request
6397 stage_request(struct view *view, enum request request, struct line *line)
6399         switch (request) {
6400         case REQ_STATUS_UPDATE:
6401                 if (!stage_update(view, line))
6402                         return REQ_NONE;
6403                 break;
6405         case REQ_STATUS_REVERT:
6406                 if (!stage_revert(view, line))
6407                         return REQ_NONE;
6408                 break;
6410         case REQ_STAGE_NEXT:
6411                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6412                         report("File is untracked; press %s to add",
6413                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6414                         return REQ_NONE;
6415                 }
6416                 stage_next(view, line);
6417                 return REQ_NONE;
6419         case REQ_EDIT:
6420                 if (!stage_status.new.name[0])
6421                         return request;
6422                 if (stage_status.status == 'D') {
6423                         report("File has been deleted.");
6424                         return REQ_NONE;
6425                 }
6427                 open_editor(stage_status.new.name);
6428                 break;
6430         case REQ_REFRESH:
6431                 /* Reload everything ... */
6432                 break;
6434         case REQ_VIEW_BLAME:
6435                 if (stage_status.new.name[0]) {
6436                         string_copy(opt_file, stage_status.new.name);
6437                         opt_ref[0] = 0;
6438                 }
6439                 return request;
6441         case REQ_ENTER:
6442                 return pager_request(view, request, line);
6444         default:
6445                 return request;
6446         }
6448         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6449         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6451         /* Check whether the staged entry still exists, and close the
6452          * stage view if it doesn't. */
6453         if (!status_exists(&stage_status, stage_line_type)) {
6454                 status_restore(VIEW(REQ_VIEW_STATUS));
6455                 return REQ_VIEW_CLOSE;
6456         }
6458         if (stage_line_type == LINE_STAT_UNTRACKED) {
6459                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6460                         report("Cannot display a directory");
6461                         return REQ_NONE;
6462                 }
6464                 if (!prepare_update_file(view, stage_status.new.name)) {
6465                         report("Failed to open file: %s", strerror(errno));
6466                         return REQ_NONE;
6467                 }
6468         }
6469         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6471         return REQ_NONE;
6474 static struct view_ops stage_ops = {
6475         "line",
6476         NULL,
6477         NULL,
6478         pager_read,
6479         pager_draw,
6480         stage_request,
6481         pager_grep,
6482         pager_select,
6483 };
6486 /*
6487  * Revision graph
6488  */
6490 struct commit {
6491         char id[SIZEOF_REV];            /* SHA1 ID. */
6492         char title[128];                /* First line of the commit message. */
6493         const char *author;             /* Author of the commit. */
6494         struct time time;               /* Date from the author ident. */
6495         struct ref_list *refs;          /* Repository references. */
6496         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6497         size_t graph_size;              /* The width of the graph array. */
6498         bool has_parents;               /* Rewritten --parents seen. */
6499 };
6501 /* Size of rev graph with no  "padding" columns */
6502 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6504 struct rev_graph {
6505         struct rev_graph *prev, *next, *parents;
6506         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6507         size_t size;
6508         struct commit *commit;
6509         size_t pos;
6510         unsigned int boundary:1;
6511 };
6513 /* Parents of the commit being visualized. */
6514 static struct rev_graph graph_parents[4];
6516 /* The current stack of revisions on the graph. */
6517 static struct rev_graph graph_stacks[4] = {
6518         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6519         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6520         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6521         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6522 };
6524 static inline bool
6525 graph_parent_is_merge(struct rev_graph *graph)
6527         return graph->parents->size > 1;
6530 static inline void
6531 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6533         struct commit *commit = graph->commit;
6535         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6536                 commit->graph[commit->graph_size++] = symbol;
6539 static void
6540 clear_rev_graph(struct rev_graph *graph)
6542         graph->boundary = 0;
6543         graph->size = graph->pos = 0;
6544         graph->commit = NULL;
6545         memset(graph->parents, 0, sizeof(*graph->parents));
6548 static void
6549 done_rev_graph(struct rev_graph *graph)
6551         if (graph_parent_is_merge(graph) &&
6552             graph->pos < graph->size - 1 &&
6553             graph->next->size == graph->size + graph->parents->size - 1) {
6554                 size_t i = graph->pos + graph->parents->size - 1;
6556                 graph->commit->graph_size = i * 2;
6557                 while (i < graph->next->size - 1) {
6558                         append_to_rev_graph(graph, ' ');
6559                         append_to_rev_graph(graph, '\\');
6560                         i++;
6561                 }
6562         }
6564         clear_rev_graph(graph);
6567 static void
6568 push_rev_graph(struct rev_graph *graph, const char *parent)
6570         int i;
6572         /* "Collapse" duplicate parents lines.
6573          *
6574          * FIXME: This needs to also update update the drawn graph but
6575          * for now it just serves as a method for pruning graph lines. */
6576         for (i = 0; i < graph->size; i++)
6577                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6578                         return;
6580         if (graph->size < SIZEOF_REVITEMS) {
6581                 string_copy_rev(graph->rev[graph->size++], parent);
6582         }
6585 static chtype
6586 get_rev_graph_symbol(struct rev_graph *graph)
6588         chtype symbol;
6590         if (graph->boundary)
6591                 symbol = REVGRAPH_BOUND;
6592         else if (graph->parents->size == 0)
6593                 symbol = REVGRAPH_INIT;
6594         else if (graph_parent_is_merge(graph))
6595                 symbol = REVGRAPH_MERGE;
6596         else if (graph->pos >= graph->size)
6597                 symbol = REVGRAPH_BRANCH;
6598         else
6599                 symbol = REVGRAPH_COMMIT;
6601         return symbol;
6604 static void
6605 draw_rev_graph(struct rev_graph *graph)
6607         struct rev_filler {
6608                 chtype separator, line;
6609         };
6610         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6611         static struct rev_filler fillers[] = {
6612                 { ' ',  '|' },
6613                 { '`',  '.' },
6614                 { '\'', ' ' },
6615                 { '/',  ' ' },
6616         };
6617         chtype symbol = get_rev_graph_symbol(graph);
6618         struct rev_filler *filler;
6619         size_t i;
6621         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6622         filler = &fillers[DEFAULT];
6624         for (i = 0; i < graph->pos; i++) {
6625                 append_to_rev_graph(graph, filler->line);
6626                 if (graph_parent_is_merge(graph->prev) &&
6627                     graph->prev->pos == i)
6628                         filler = &fillers[RSHARP];
6630                 append_to_rev_graph(graph, filler->separator);
6631         }
6633         /* Place the symbol for this revision. */
6634         append_to_rev_graph(graph, symbol);
6636         if (graph->prev->size > graph->size)
6637                 filler = &fillers[RDIAG];
6638         else
6639                 filler = &fillers[DEFAULT];
6641         i++;
6643         for (; i < graph->size; i++) {
6644                 append_to_rev_graph(graph, filler->separator);
6645                 append_to_rev_graph(graph, filler->line);
6646                 if (graph_parent_is_merge(graph->prev) &&
6647                     i < graph->prev->pos + graph->parents->size)
6648                         filler = &fillers[RSHARP];
6649                 if (graph->prev->size > graph->size)
6650                         filler = &fillers[LDIAG];
6651         }
6653         if (graph->prev->size > graph->size) {
6654                 append_to_rev_graph(graph, filler->separator);
6655                 if (filler->line != ' ')
6656                         append_to_rev_graph(graph, filler->line);
6657         }
6660 /* Prepare the next rev graph */
6661 static void
6662 prepare_rev_graph(struct rev_graph *graph)
6664         size_t i;
6666         /* First, traverse all lines of revisions up to the active one. */
6667         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6668                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6669                         break;
6671                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6672         }
6674         /* Interleave the new revision parent(s). */
6675         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6676                 push_rev_graph(graph->next, graph->parents->rev[i]);
6678         /* Lastly, put any remaining revisions. */
6679         for (i = graph->pos + 1; i < graph->size; i++)
6680                 push_rev_graph(graph->next, graph->rev[i]);
6683 static void
6684 update_rev_graph(struct view *view, struct rev_graph *graph)
6686         /* If this is the finalizing update ... */
6687         if (graph->commit)
6688                 prepare_rev_graph(graph);
6690         /* Graph visualization needs a one rev look-ahead,
6691          * so the first update doesn't visualize anything. */
6692         if (!graph->prev->commit)
6693                 return;
6695         if (view->lines > 2)
6696                 view->line[view->lines - 3].dirty = 1;
6697         if (view->lines > 1)
6698                 view->line[view->lines - 2].dirty = 1;
6699         draw_rev_graph(graph->prev);
6700         done_rev_graph(graph->prev->prev);
6704 /*
6705  * Main view backend
6706  */
6708 static const char *main_argv[SIZEOF_ARG] = {
6709         "git", "log", "--no-color", "--pretty=raw", "--parents",
6710                       "--topo-order", "%(head)", NULL
6711 };
6713 static bool
6714 main_draw(struct view *view, struct line *line, unsigned int lineno)
6716         struct commit *commit = line->data;
6718         if (!commit->author)
6719                 return FALSE;
6721         if (opt_date && draw_date(view, &commit->time))
6722                 return TRUE;
6724         if (opt_author && draw_author(view, commit->author))
6725                 return TRUE;
6727         if (opt_rev_graph && commit->graph_size &&
6728             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6729                 return TRUE;
6731         if (opt_show_refs && commit->refs) {
6732                 size_t i;
6734                 for (i = 0; i < commit->refs->size; i++) {
6735                         struct ref *ref = commit->refs->refs[i];
6736                         enum line_type type;
6738                         if (ref->head)
6739                                 type = LINE_MAIN_HEAD;
6740                         else if (ref->ltag)
6741                                 type = LINE_MAIN_LOCAL_TAG;
6742                         else if (ref->tag)
6743                                 type = LINE_MAIN_TAG;
6744                         else if (ref->tracked)
6745                                 type = LINE_MAIN_TRACKED;
6746                         else if (ref->remote)
6747                                 type = LINE_MAIN_REMOTE;
6748                         else
6749                                 type = LINE_MAIN_REF;
6751                         if (draw_text(view, type, "[", TRUE) ||
6752                             draw_text(view, type, ref->name, TRUE) ||
6753                             draw_text(view, type, "]", TRUE))
6754                                 return TRUE;
6756                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6757                                 return TRUE;
6758                 }
6759         }
6761         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6762         return TRUE;
6765 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6766 static bool
6767 main_read(struct view *view, char *line)
6769         static struct rev_graph *graph = graph_stacks;
6770         enum line_type type;
6771         struct commit *commit;
6773         if (!line) {
6774                 int i;
6776                 if (!view->lines && !view->parent)
6777                         die("No revisions match the given arguments.");
6778                 if (view->lines > 0) {
6779                         commit = view->line[view->lines - 1].data;
6780                         view->line[view->lines - 1].dirty = 1;
6781                         if (!commit->author) {
6782                                 view->lines--;
6783                                 free(commit);
6784                                 graph->commit = NULL;
6785                         }
6786                 }
6787                 update_rev_graph(view, graph);
6789                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6790                         clear_rev_graph(&graph_stacks[i]);
6791                 return TRUE;
6792         }
6794         type = get_line_type(line);
6795         if (type == LINE_COMMIT) {
6796                 commit = calloc(1, sizeof(struct commit));
6797                 if (!commit)
6798                         return FALSE;
6800                 line += STRING_SIZE("commit ");
6801                 if (*line == '-') {
6802                         graph->boundary = 1;
6803                         line++;
6804                 }
6806                 string_copy_rev(commit->id, line);
6807                 commit->refs = get_ref_list(commit->id);
6808                 graph->commit = commit;
6809                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6811                 while ((line = strchr(line, ' '))) {
6812                         line++;
6813                         push_rev_graph(graph->parents, line);
6814                         commit->has_parents = TRUE;
6815                 }
6816                 return TRUE;
6817         }
6819         if (!view->lines)
6820                 return TRUE;
6821         commit = view->line[view->lines - 1].data;
6823         switch (type) {
6824         case LINE_PARENT:
6825                 if (commit->has_parents)
6826                         break;
6827                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6828                 break;
6830         case LINE_AUTHOR:
6831                 parse_author_line(line + STRING_SIZE("author "),
6832                                   &commit->author, &commit->time);
6833                 update_rev_graph(view, graph);
6834                 graph = graph->next;
6835                 break;
6837         default:
6838                 /* Fill in the commit title if it has not already been set. */
6839                 if (commit->title[0])
6840                         break;
6842                 /* Require titles to start with a non-space character at the
6843                  * offset used by git log. */
6844                 if (strncmp(line, "    ", 4))
6845                         break;
6846                 line += 4;
6847                 /* Well, if the title starts with a whitespace character,
6848                  * try to be forgiving.  Otherwise we end up with no title. */
6849                 while (isspace(*line))
6850                         line++;
6851                 if (*line == '\0')
6852                         break;
6853                 /* FIXME: More graceful handling of titles; append "..." to
6854                  * shortened titles, etc. */
6856                 string_expand(commit->title, sizeof(commit->title), line, 1);
6857                 view->line[view->lines - 1].dirty = 1;
6858         }
6860         return TRUE;
6863 static enum request
6864 main_request(struct view *view, enum request request, struct line *line)
6866         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6868         switch (request) {
6869         case REQ_ENTER:
6870                 open_view(view, REQ_VIEW_DIFF, flags);
6871                 break;
6872         case REQ_REFRESH:
6873                 load_refs();
6874                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6875                 break;
6876         default:
6877                 return request;
6878         }
6880         return REQ_NONE;
6883 static bool
6884 grep_refs(struct ref_list *list, regex_t *regex)
6886         regmatch_t pmatch;
6887         size_t i;
6889         if (!opt_show_refs || !list)
6890                 return FALSE;
6892         for (i = 0; i < list->size; i++) {
6893                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6894                         return TRUE;
6895         }
6897         return FALSE;
6900 static bool
6901 main_grep(struct view *view, struct line *line)
6903         struct commit *commit = line->data;
6904         const char *text[] = {
6905                 commit->title,
6906                 opt_author ? commit->author : "",
6907                 mkdate(&commit->time, opt_date),
6908                 NULL
6909         };
6911         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6914 static void
6915 main_select(struct view *view, struct line *line)
6917         struct commit *commit = line->data;
6919         string_copy_rev(view->ref, commit->id);
6920         string_copy_rev(ref_commit, view->ref);
6923 static struct view_ops main_ops = {
6924         "commit",
6925         main_argv,
6926         NULL,
6927         main_read,
6928         main_draw,
6929         main_request,
6930         main_grep,
6931         main_select,
6932 };
6935 /*
6936  * Status management
6937  */
6939 /* Whether or not the curses interface has been initialized. */
6940 static bool cursed = FALSE;
6942 /* Terminal hacks and workarounds. */
6943 static bool use_scroll_redrawwin;
6944 static bool use_scroll_status_wclear;
6946 /* The status window is used for polling keystrokes. */
6947 static WINDOW *status_win;
6949 /* Reading from the prompt? */
6950 static bool input_mode = FALSE;
6952 static bool status_empty = FALSE;
6954 /* Update status and title window. */
6955 static void
6956 report(const char *msg, ...)
6958         struct view *view = display[current_view];
6960         if (input_mode)
6961                 return;
6963         if (!view) {
6964                 char buf[SIZEOF_STR];
6965                 va_list args;
6967                 va_start(args, msg);
6968                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6969                         buf[sizeof(buf) - 1] = 0;
6970                         buf[sizeof(buf) - 2] = '.';
6971                         buf[sizeof(buf) - 3] = '.';
6972                         buf[sizeof(buf) - 4] = '.';
6973                 }
6974                 va_end(args);
6975                 die("%s", buf);
6976         }
6978         if (!status_empty || *msg) {
6979                 va_list args;
6981                 va_start(args, msg);
6983                 wmove(status_win, 0, 0);
6984                 if (view->has_scrolled && use_scroll_status_wclear)
6985                         wclear(status_win);
6986                 if (*msg) {
6987                         vwprintw(status_win, msg, args);
6988                         status_empty = FALSE;
6989                 } else {
6990                         status_empty = TRUE;
6991                 }
6992                 wclrtoeol(status_win);
6993                 wnoutrefresh(status_win);
6995                 va_end(args);
6996         }
6998         update_view_title(view);
7001 static void
7002 init_display(void)
7004         const char *term;
7005         int x, y;
7007         /* Initialize the curses library */
7008         if (isatty(STDIN_FILENO)) {
7009                 cursed = !!initscr();
7010                 opt_tty = stdin;
7011         } else {
7012                 /* Leave stdin and stdout alone when acting as a pager. */
7013                 opt_tty = fopen("/dev/tty", "r+");
7014                 if (!opt_tty)
7015                         die("Failed to open /dev/tty");
7016                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7017         }
7019         if (!cursed)
7020                 die("Failed to initialize curses");
7022         nonl();         /* Disable conversion and detect newlines from input. */
7023         cbreak();       /* Take input chars one at a time, no wait for \n */
7024         noecho();       /* Don't echo input */
7025         leaveok(stdscr, FALSE);
7027         if (has_colors())
7028                 init_colors();
7030         getmaxyx(stdscr, y, x);
7031         status_win = newwin(1, 0, y - 1, 0);
7032         if (!status_win)
7033                 die("Failed to create status window");
7035         /* Enable keyboard mapping */
7036         keypad(status_win, TRUE);
7037         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7039         TABSIZE = opt_tab_size;
7041         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7042         if (term && !strcmp(term, "gnome-terminal")) {
7043                 /* In the gnome-terminal-emulator, the message from
7044                  * scrolling up one line when impossible followed by
7045                  * scrolling down one line causes corruption of the
7046                  * status line. This is fixed by calling wclear. */
7047                 use_scroll_status_wclear = TRUE;
7048                 use_scroll_redrawwin = FALSE;
7050         } else if (term && !strcmp(term, "xrvt-xpm")) {
7051                 /* No problems with full optimizations in xrvt-(unicode)
7052                  * and aterm. */
7053                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7055         } else {
7056                 /* When scrolling in (u)xterm the last line in the
7057                  * scrolling direction will update slowly. */
7058                 use_scroll_redrawwin = TRUE;
7059                 use_scroll_status_wclear = FALSE;
7060         }
7063 static int
7064 get_input(int prompt_position)
7066         struct view *view;
7067         int i, key, cursor_y, cursor_x;
7068         bool loading = FALSE;
7070         if (prompt_position)
7071                 input_mode = TRUE;
7073         while (TRUE) {
7074                 foreach_view (view, i) {
7075                         update_view(view);
7076                         if (view_is_displayed(view) && view->has_scrolled &&
7077                             use_scroll_redrawwin)
7078                                 redrawwin(view->win);
7079                         view->has_scrolled = FALSE;
7080                         if (view->pipe)
7081                                 loading = TRUE;
7082                 }
7084                 /* Update the cursor position. */
7085                 if (prompt_position) {
7086                         getbegyx(status_win, cursor_y, cursor_x);
7087                         cursor_x = prompt_position;
7088                 } else {
7089                         view = display[current_view];
7090                         getbegyx(view->win, cursor_y, cursor_x);
7091                         cursor_x = view->width - 1;
7092                         cursor_y += view->lineno - view->offset;
7093                 }
7094                 setsyx(cursor_y, cursor_x);
7096                 /* Refresh, accept single keystroke of input */
7097                 doupdate();
7098                 nodelay(status_win, loading);
7099                 key = wgetch(status_win);
7101                 /* wgetch() with nodelay() enabled returns ERR when
7102                  * there's no input. */
7103                 if (key == ERR) {
7105                 } else if (key == KEY_RESIZE) {
7106                         int height, width;
7108                         getmaxyx(stdscr, height, width);
7110                         wresize(status_win, 1, width);
7111                         mvwin(status_win, height - 1, 0);
7112                         wnoutrefresh(status_win);
7113                         resize_display();
7114                         redraw_display(TRUE);
7116                 } else {
7117                         input_mode = FALSE;
7118                         return key;
7119                 }
7120         }
7123 static char *
7124 prompt_input(const char *prompt, input_handler handler, void *data)
7126         enum input_status status = INPUT_OK;
7127         static char buf[SIZEOF_STR];
7128         size_t pos = 0;
7130         buf[pos] = 0;
7132         while (status == INPUT_OK || status == INPUT_SKIP) {
7133                 int key;
7135                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7136                 wclrtoeol(status_win);
7138                 key = get_input(pos + 1);
7139                 switch (key) {
7140                 case KEY_RETURN:
7141                 case KEY_ENTER:
7142                 case '\n':
7143                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7144                         break;
7146                 case KEY_BACKSPACE:
7147                         if (pos > 0)
7148                                 buf[--pos] = 0;
7149                         else
7150                                 status = INPUT_CANCEL;
7151                         break;
7153                 case KEY_ESC:
7154                         status = INPUT_CANCEL;
7155                         break;
7157                 default:
7158                         if (pos >= sizeof(buf)) {
7159                                 report("Input string too long");
7160                                 return NULL;
7161                         }
7163                         status = handler(data, buf, key);
7164                         if (status == INPUT_OK)
7165                                 buf[pos++] = (char) key;
7166                 }
7167         }
7169         /* Clear the status window */
7170         status_empty = FALSE;
7171         report("");
7173         if (status == INPUT_CANCEL)
7174                 return NULL;
7176         buf[pos++] = 0;
7178         return buf;
7181 static enum input_status
7182 prompt_yesno_handler(void *data, char *buf, int c)
7184         if (c == 'y' || c == 'Y')
7185                 return INPUT_STOP;
7186         if (c == 'n' || c == 'N')
7187                 return INPUT_CANCEL;
7188         return INPUT_SKIP;
7191 static bool
7192 prompt_yesno(const char *prompt)
7194         char prompt2[SIZEOF_STR];
7196         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7197                 return FALSE;
7199         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7202 static enum input_status
7203 read_prompt_handler(void *data, char *buf, int c)
7205         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7208 static char *
7209 read_prompt(const char *prompt)
7211         return prompt_input(prompt, read_prompt_handler, NULL);
7214 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7216         enum input_status status = INPUT_OK;
7217         int size = 0;
7219         while (items[size].text)
7220                 size++;
7222         while (status == INPUT_OK) {
7223                 const struct menu_item *item = &items[*selected];
7224                 int key;
7225                 int i;
7227                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7228                           prompt, *selected + 1, size);
7229                 if (item->hotkey)
7230                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7231                 wprintw(status_win, "%s", item->text);
7232                 wclrtoeol(status_win);
7234                 key = get_input(COLS - 1);
7235                 switch (key) {
7236                 case KEY_RETURN:
7237                 case KEY_ENTER:
7238                 case '\n':
7239                         status = INPUT_STOP;
7240                         break;
7242                 case KEY_LEFT:
7243                 case KEY_UP:
7244                         *selected = *selected - 1;
7245                         if (*selected < 0)
7246                                 *selected = size - 1;
7247                         break;
7249                 case KEY_RIGHT:
7250                 case KEY_DOWN:
7251                         *selected = (*selected + 1) % size;
7252                         break;
7254                 case KEY_ESC:
7255                         status = INPUT_CANCEL;
7256                         break;
7258                 default:
7259                         for (i = 0; items[i].text; i++)
7260                                 if (items[i].hotkey == key) {
7261                                         *selected = i;
7262                                         status = INPUT_STOP;
7263                                         break;
7264                                 }
7265                 }
7266         }
7268         /* Clear the status window */
7269         status_empty = FALSE;
7270         report("");
7272         return status != INPUT_CANCEL;
7275 /*
7276  * Repository properties
7277  */
7279 static struct ref **refs = NULL;
7280 static size_t refs_size = 0;
7281 static struct ref *refs_head = NULL;
7283 static struct ref_list **ref_lists = NULL;
7284 static size_t ref_lists_size = 0;
7286 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7287 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7288 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7290 static int
7291 compare_refs(const void *ref1_, const void *ref2_)
7293         const struct ref *ref1 = *(const struct ref **)ref1_;
7294         const struct ref *ref2 = *(const struct ref **)ref2_;
7296         if (ref1->tag != ref2->tag)
7297                 return ref2->tag - ref1->tag;
7298         if (ref1->ltag != ref2->ltag)
7299                 return ref2->ltag - ref2->ltag;
7300         if (ref1->head != ref2->head)
7301                 return ref2->head - ref1->head;
7302         if (ref1->tracked != ref2->tracked)
7303                 return ref2->tracked - ref1->tracked;
7304         if (ref1->remote != ref2->remote)
7305                 return ref2->remote - ref1->remote;
7306         return strcmp(ref1->name, ref2->name);
7309 static void
7310 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7312         size_t i;
7314         for (i = 0; i < refs_size; i++)
7315                 if (!visitor(data, refs[i]))
7316                         break;
7319 static struct ref *
7320 get_ref_head()
7322         return refs_head;
7325 static struct ref_list *
7326 get_ref_list(const char *id)
7328         struct ref_list *list;
7329         size_t i;
7331         for (i = 0; i < ref_lists_size; i++)
7332                 if (!strcmp(id, ref_lists[i]->id))
7333                         return ref_lists[i];
7335         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7336                 return NULL;
7337         list = calloc(1, sizeof(*list));
7338         if (!list)
7339                 return NULL;
7341         for (i = 0; i < refs_size; i++) {
7342                 if (!strcmp(id, refs[i]->id) &&
7343                     realloc_refs_list(&list->refs, list->size, 1))
7344                         list->refs[list->size++] = refs[i];
7345         }
7347         if (!list->refs) {
7348                 free(list);
7349                 return NULL;
7350         }
7352         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7353         ref_lists[ref_lists_size++] = list;
7354         return list;
7357 static int
7358 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7360         struct ref *ref = NULL;
7361         bool tag = FALSE;
7362         bool ltag = FALSE;
7363         bool remote = FALSE;
7364         bool tracked = FALSE;
7365         bool head = FALSE;
7366         int from = 0, to = refs_size - 1;
7368         if (!prefixcmp(name, "refs/tags/")) {
7369                 if (!suffixcmp(name, namelen, "^{}")) {
7370                         namelen -= 3;
7371                         name[namelen] = 0;
7372                 } else {
7373                         ltag = TRUE;
7374                 }
7376                 tag = TRUE;
7377                 namelen -= STRING_SIZE("refs/tags/");
7378                 name    += STRING_SIZE("refs/tags/");
7380         } else if (!prefixcmp(name, "refs/remotes/")) {
7381                 remote = TRUE;
7382                 namelen -= STRING_SIZE("refs/remotes/");
7383                 name    += STRING_SIZE("refs/remotes/");
7384                 tracked  = !strcmp(opt_remote, name);
7386         } else if (!prefixcmp(name, "refs/heads/")) {
7387                 namelen -= STRING_SIZE("refs/heads/");
7388                 name    += STRING_SIZE("refs/heads/");
7389                 if (!strncmp(opt_head, name, namelen))
7390                         return OK;
7392         } else if (!strcmp(name, "HEAD")) {
7393                 head     = TRUE;
7394                 if (*opt_head) {
7395                         namelen  = strlen(opt_head);
7396                         name     = opt_head;
7397                 }
7398         }
7400         /* If we are reloading or it's an annotated tag, replace the
7401          * previous SHA1 with the resolved commit id; relies on the fact
7402          * git-ls-remote lists the commit id of an annotated tag right
7403          * before the commit id it points to. */
7404         while (from <= to) {
7405                 size_t pos = (to + from) / 2;
7406                 int cmp = strcmp(name, refs[pos]->name);
7408                 if (!cmp) {
7409                         ref = refs[pos];
7410                         break;
7411                 }
7413                 if (cmp < 0)
7414                         to = pos - 1;
7415                 else
7416                         from = pos + 1;
7417         }
7419         if (!ref) {
7420                 if (!realloc_refs(&refs, refs_size, 1))
7421                         return ERR;
7422                 ref = calloc(1, sizeof(*ref) + namelen);
7423                 if (!ref)
7424                         return ERR;
7425                 memmove(refs + from + 1, refs + from,
7426                         (refs_size - from) * sizeof(*refs));
7427                 refs[from] = ref;
7428                 strncpy(ref->name, name, namelen);
7429                 refs_size++;
7430         }
7432         ref->head = head;
7433         ref->tag = tag;
7434         ref->ltag = ltag;
7435         ref->remote = remote;
7436         ref->tracked = tracked;
7437         string_copy_rev(ref->id, id);
7439         if (head)
7440                 refs_head = ref;
7441         return OK;
7444 static int
7445 load_refs(void)
7447         const char *head_argv[] = {
7448                 "git", "symbolic-ref", "HEAD", NULL
7449         };
7450         static const char *ls_remote_argv[SIZEOF_ARG] = {
7451                 "git", "ls-remote", opt_git_dir, NULL
7452         };
7453         static bool init = FALSE;
7454         size_t i;
7456         if (!init) {
7457                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7458                         die("TIG_LS_REMOTE contains too many arguments");
7459                 init = TRUE;
7460         }
7462         if (!*opt_git_dir)
7463                 return OK;
7465         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7466             !prefixcmp(opt_head, "refs/heads/")) {
7467                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7469                 memmove(opt_head, offset, strlen(offset) + 1);
7470         }
7472         refs_head = NULL;
7473         for (i = 0; i < refs_size; i++)
7474                 refs[i]->id[0] = 0;
7476         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7477                 return ERR;
7479         /* Update the ref lists to reflect changes. */
7480         for (i = 0; i < ref_lists_size; i++) {
7481                 struct ref_list *list = ref_lists[i];
7482                 size_t old, new;
7484                 for (old = new = 0; old < list->size; old++)
7485                         if (!strcmp(list->id, list->refs[old]->id))
7486                                 list->refs[new++] = list->refs[old];
7487                 list->size = new;
7488         }
7490         return OK;
7493 static void
7494 set_remote_branch(const char *name, const char *value, size_t valuelen)
7496         if (!strcmp(name, ".remote")) {
7497                 string_ncopy(opt_remote, value, valuelen);
7499         } else if (*opt_remote && !strcmp(name, ".merge")) {
7500                 size_t from = strlen(opt_remote);
7502                 if (!prefixcmp(value, "refs/heads/"))
7503                         value += STRING_SIZE("refs/heads/");
7505                 if (!string_format_from(opt_remote, &from, "/%s", value))
7506                         opt_remote[0] = 0;
7507         }
7510 static void
7511 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7513         const char *argv[SIZEOF_ARG] = { name, "=" };
7514         int argc = 1 + (cmd == option_set_command);
7515         int error = ERR;
7517         if (!argv_from_string(argv, &argc, value))
7518                 config_msg = "Too many option arguments";
7519         else
7520                 error = cmd(argc, argv);
7522         if (error == ERR)
7523                 warn("Option 'tig.%s': %s", name, config_msg);
7526 static bool
7527 set_environment_variable(const char *name, const char *value)
7529         size_t len = strlen(name) + 1 + strlen(value) + 1;
7530         char *env = malloc(len);
7532         if (env &&
7533             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7534             putenv(env) == 0)
7535                 return TRUE;
7536         free(env);
7537         return FALSE;
7540 static void
7541 set_work_tree(const char *value)
7543         char cwd[SIZEOF_STR];
7545         if (!getcwd(cwd, sizeof(cwd)))
7546                 die("Failed to get cwd path: %s", strerror(errno));
7547         if (chdir(opt_git_dir) < 0)
7548                 die("Failed to chdir(%s): %s", strerror(errno));
7549         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7550                 die("Failed to get git path: %s", strerror(errno));
7551         if (chdir(cwd) < 0)
7552                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7553         if (chdir(value) < 0)
7554                 die("Failed to chdir(%s): %s", value, strerror(errno));
7555         if (!getcwd(cwd, sizeof(cwd)))
7556                 die("Failed to get cwd path: %s", strerror(errno));
7557         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7558                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7559         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7560                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7561         opt_is_inside_work_tree = TRUE;
7564 static int
7565 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7567         if (!strcmp(name, "i18n.commitencoding"))
7568                 string_ncopy(opt_encoding, value, valuelen);
7570         else if (!strcmp(name, "core.editor"))
7571                 string_ncopy(opt_editor, value, valuelen);
7573         else if (!strcmp(name, "core.worktree"))
7574                 set_work_tree(value);
7576         else if (!prefixcmp(name, "tig.color."))
7577                 set_repo_config_option(name + 10, value, option_color_command);
7579         else if (!prefixcmp(name, "tig.bind."))
7580                 set_repo_config_option(name + 9, value, option_bind_command);
7582         else if (!prefixcmp(name, "tig."))
7583                 set_repo_config_option(name + 4, value, option_set_command);
7585         else if (*opt_head && !prefixcmp(name, "branch.") &&
7586                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7587                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7589         return OK;
7592 static int
7593 load_git_config(void)
7595         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7597         return io_run_load(config_list_argv, "=", read_repo_config_option);
7600 static int
7601 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7603         if (!opt_git_dir[0]) {
7604                 string_ncopy(opt_git_dir, name, namelen);
7606         } else if (opt_is_inside_work_tree == -1) {
7607                 /* This can be 3 different values depending on the
7608                  * version of git being used. If git-rev-parse does not
7609                  * understand --is-inside-work-tree it will simply echo
7610                  * the option else either "true" or "false" is printed.
7611                  * Default to true for the unknown case. */
7612                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7614         } else if (*name == '.') {
7615                 string_ncopy(opt_cdup, name, namelen);
7617         } else {
7618                 string_ncopy(opt_prefix, name, namelen);
7619         }
7621         return OK;
7624 static int
7625 load_repo_info(void)
7627         const char *rev_parse_argv[] = {
7628                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7629                         "--show-cdup", "--show-prefix", NULL
7630         };
7632         return io_run_load(rev_parse_argv, "=", read_repo_info);
7636 /*
7637  * Main
7638  */
7640 static const char usage[] =
7641 "tig " TIG_VERSION " (" __DATE__ ")\n"
7642 "\n"
7643 "Usage: tig        [options] [revs] [--] [paths]\n"
7644 "   or: tig show   [options] [revs] [--] [paths]\n"
7645 "   or: tig blame  [rev] path\n"
7646 "   or: tig status\n"
7647 "   or: tig <      [git command output]\n"
7648 "\n"
7649 "Options:\n"
7650 "  -v, --version   Show version and exit\n"
7651 "  -h, --help      Show help message and exit";
7653 static void __NORETURN
7654 quit(int sig)
7656         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7657         if (cursed)
7658                 endwin();
7659         exit(0);
7662 static void __NORETURN
7663 die(const char *err, ...)
7665         va_list args;
7667         endwin();
7669         va_start(args, err);
7670         fputs("tig: ", stderr);
7671         vfprintf(stderr, err, args);
7672         fputs("\n", stderr);
7673         va_end(args);
7675         exit(1);
7678 static void
7679 warn(const char *msg, ...)
7681         va_list args;
7683         va_start(args, msg);
7684         fputs("tig warning: ", stderr);
7685         vfprintf(stderr, msg, args);
7686         fputs("\n", stderr);
7687         va_end(args);
7690 static enum request
7691 parse_options(int argc, const char *argv[])
7693         enum request request = REQ_VIEW_MAIN;
7694         const char *subcommand;
7695         bool seen_dashdash = FALSE;
7696         /* XXX: This is vulnerable to the user overriding options
7697          * required for the main view parser. */
7698         const char *custom_argv[SIZEOF_ARG] = {
7699                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7700                         "--topo-order", NULL
7701         };
7702         int i, j = 6;
7704         if (!isatty(STDIN_FILENO)) {
7705                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7706                 return REQ_VIEW_PAGER;
7707         }
7709         if (argc <= 1)
7710                 return REQ_NONE;
7712         subcommand = argv[1];
7713         if (!strcmp(subcommand, "status")) {
7714                 if (argc > 2)
7715                         warn("ignoring arguments after `%s'", subcommand);
7716                 return REQ_VIEW_STATUS;
7718         } else if (!strcmp(subcommand, "blame")) {
7719                 if (argc <= 2 || argc > 4)
7720                         die("invalid number of options to blame\n\n%s", usage);
7722                 i = 2;
7723                 if (argc == 4) {
7724                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7725                         i++;
7726                 }
7728                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7729                 return REQ_VIEW_BLAME;
7731         } else if (!strcmp(subcommand, "show")) {
7732                 request = REQ_VIEW_DIFF;
7734         } else {
7735                 subcommand = NULL;
7736         }
7738         if (subcommand) {
7739                 custom_argv[1] = subcommand;
7740                 j = 2;
7741         }
7743         for (i = 1 + !!subcommand; i < argc; i++) {
7744                 const char *opt = argv[i];
7746                 if (seen_dashdash || !strcmp(opt, "--")) {
7747                         seen_dashdash = TRUE;
7749                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7750                         printf("tig version %s\n", TIG_VERSION);
7751                         quit(0);
7753                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7754                         printf("%s\n", usage);
7755                         quit(0);
7756                 }
7758                 custom_argv[j++] = opt;
7759                 if (j >= ARRAY_SIZE(custom_argv))
7760                         die("command too long");
7761         }
7763         if (!prepare_update(VIEW(request), custom_argv, NULL))
7764                 die("Failed to format arguments");
7766         return request;
7769 int
7770 main(int argc, const char *argv[])
7772         const char *codeset = "UTF-8";
7773         enum request request = parse_options(argc, argv);
7774         struct view *view;
7775         size_t i;
7777         signal(SIGINT, quit);
7778         signal(SIGPIPE, SIG_IGN);
7780         if (setlocale(LC_ALL, "")) {
7781                 codeset = nl_langinfo(CODESET);
7782         }
7784         if (load_repo_info() == ERR)
7785                 die("Failed to load repo info.");
7787         if (load_options() == ERR)
7788                 die("Failed to load user config.");
7790         if (load_git_config() == ERR)
7791                 die("Failed to load repo config.");
7793         /* Require a git repository unless when running in pager mode. */
7794         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7795                 die("Not a git repository");
7797         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7798                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7799                 if (opt_iconv_in == ICONV_NONE)
7800                         die("Failed to initialize character set conversion");
7801         }
7803         if (codeset && strcmp(codeset, "UTF-8")) {
7804                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7805                 if (opt_iconv_out == ICONV_NONE)
7806                         die("Failed to initialize character set conversion");
7807         }
7809         if (load_refs() == ERR)
7810                 die("Failed to load refs.");
7812         foreach_view (view, i)
7813                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7814                         die("Too many arguments in the `%s` environment variable",
7815                             view->cmd_env);
7817         init_display();
7819         if (request != REQ_NONE)
7820                 open_view(NULL, request, OPEN_PREPARED);
7821         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7823         while (view_driver(display[current_view], request)) {
7824                 int key = get_input(0);
7826                 view = display[current_view];
7827                 request = get_keybinding(view->keymap, key);
7829                 /* Some low-level request handling. This keeps access to
7830                  * status_win restricted. */
7831                 switch (request) {
7832                 case REQ_NONE:
7833                         report("Unknown key, press %s for help",
7834                                get_key(view->keymap, REQ_VIEW_HELP));
7835                         break;
7836                 case REQ_PROMPT:
7837                 {
7838                         char *cmd = read_prompt(":");
7840                         if (cmd && isdigit(*cmd)) {
7841                                 int lineno = view->lineno + 1;
7843                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7844                                         select_view_line(view, lineno - 1);
7845                                         report("");
7846                                 } else {
7847                                         report("Unable to parse '%s' as a line number", cmd);
7848                                 }
7850                         } else if (cmd) {
7851                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7852                                 const char *argv[SIZEOF_ARG] = { "git" };
7853                                 int argc = 1;
7855                                 /* When running random commands, initially show the
7856                                  * command in the title. However, it maybe later be
7857                                  * overwritten if a commit line is selected. */
7858                                 string_ncopy(next->ref, cmd, strlen(cmd));
7860                                 if (!argv_from_string(argv, &argc, cmd)) {
7861                                         report("Too many arguments");
7862                                 } else if (!prepare_update(next, argv, NULL)) {
7863                                         report("Failed to format command");
7864                                 } else {
7865                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7866                                 }
7867                         }
7869                         request = REQ_NONE;
7870                         break;
7871                 }
7872                 case REQ_SEARCH:
7873                 case REQ_SEARCH_BACK:
7874                 {
7875                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7876                         char *search = read_prompt(prompt);
7878                         if (search)
7879                                 string_ncopy(opt_search, search, strlen(search));
7880                         else if (*opt_search)
7881                                 request = request == REQ_SEARCH ?
7882                                         REQ_FIND_NEXT :
7883                                         REQ_FIND_PREV;
7884                         else
7885                                 request = REQ_NONE;
7886                         break;
7887                 }
7888                 default:
7889                         break;
7890                 }
7891         }
7893         quit(0);
7895         return 0;