Code

When adding a keybinding check if the key is already bound
[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];
1525         size_t i;
1527         for (i = 0; i < keybindings[keymap].size; i++) {
1528                 if (keybindings[keymap].data[i].alias == key) {
1529                         keybindings[keymap].data[i].request = request;
1530                         return;
1531                 }
1532         }
1534         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1535         if (!table->data)
1536                 die("Failed to allocate keybinding");
1537         table->data[table->size].alias = key;
1538         table->data[table->size++].request = request;
1540         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1541                 int i;
1543                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1544                         if (default_keybindings[i].alias == key)
1545                                 default_keybindings[i].request = REQ_NONE;
1546         }
1549 /* Looks for a key binding first in the given map, then in the generic map, and
1550  * lastly in the default keybindings. */
1551 static enum request
1552 get_keybinding(enum keymap keymap, int key)
1554         size_t i;
1556         for (i = 0; i < keybindings[keymap].size; i++)
1557                 if (keybindings[keymap].data[i].alias == key)
1558                         return keybindings[keymap].data[i].request;
1560         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1561                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1562                         return keybindings[KEYMAP_GENERIC].data[i].request;
1564         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1565                 if (default_keybindings[i].alias == key)
1566                         return default_keybindings[i].request;
1568         return (enum request) key;
1572 struct key {
1573         const char *name;
1574         int value;
1575 };
1577 static const struct key key_table[] = {
1578         { "Enter",      KEY_RETURN },
1579         { "Space",      ' ' },
1580         { "Backspace",  KEY_BACKSPACE },
1581         { "Tab",        KEY_TAB },
1582         { "Escape",     KEY_ESC },
1583         { "Left",       KEY_LEFT },
1584         { "Right",      KEY_RIGHT },
1585         { "Up",         KEY_UP },
1586         { "Down",       KEY_DOWN },
1587         { "Insert",     KEY_IC },
1588         { "Delete",     KEY_DC },
1589         { "Hash",       '#' },
1590         { "Home",       KEY_HOME },
1591         { "End",        KEY_END },
1592         { "PageUp",     KEY_PPAGE },
1593         { "PageDown",   KEY_NPAGE },
1594         { "F1",         KEY_F(1) },
1595         { "F2",         KEY_F(2) },
1596         { "F3",         KEY_F(3) },
1597         { "F4",         KEY_F(4) },
1598         { "F5",         KEY_F(5) },
1599         { "F6",         KEY_F(6) },
1600         { "F7",         KEY_F(7) },
1601         { "F8",         KEY_F(8) },
1602         { "F9",         KEY_F(9) },
1603         { "F10",        KEY_F(10) },
1604         { "F11",        KEY_F(11) },
1605         { "F12",        KEY_F(12) },
1606 };
1608 static int
1609 get_key_value(const char *name)
1611         int i;
1613         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1614                 if (!strcasecmp(key_table[i].name, name))
1615                         return key_table[i].value;
1617         if (strlen(name) == 1 && isprint(*name))
1618                 return (int) *name;
1620         return ERR;
1623 static const char *
1624 get_key_name(int key_value)
1626         static char key_char[] = "'X'";
1627         const char *seq = NULL;
1628         int key;
1630         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1631                 if (key_table[key].value == key_value)
1632                         seq = key_table[key].name;
1634         if (seq == NULL &&
1635             key_value < 127 &&
1636             isprint(key_value)) {
1637                 key_char[1] = (char) key_value;
1638                 seq = key_char;
1639         }
1641         return seq ? seq : "(no key)";
1644 static bool
1645 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1647         const char *sep = *pos > 0 ? ", " : "";
1648         const char *keyname = get_key_name(keybinding->alias);
1650         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1653 static bool
1654 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1655                            enum keymap keymap, bool all)
1657         int i;
1659         for (i = 0; i < keybindings[keymap].size; i++) {
1660                 if (keybindings[keymap].data[i].request == request) {
1661                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1662                                 return FALSE;
1663                         if (!all)
1664                                 break;
1665                 }
1666         }
1668         return TRUE;
1671 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1673 static const char *
1674 get_keys(enum keymap keymap, enum request request, bool all)
1676         static char buf[BUFSIZ];
1677         size_t pos = 0;
1678         int i;
1680         buf[pos] = 0;
1682         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1683                 return "Too many keybindings!";
1684         if (pos > 0 && !all)
1685                 return buf;
1687         if (keymap != KEYMAP_GENERIC) {
1688                 /* Only the generic keymap includes the default keybindings when
1689                  * listing all keys. */
1690                 if (all)
1691                         return buf;
1693                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1694                         return "Too many keybindings!";
1695                 if (pos)
1696                         return buf;
1697         }
1699         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1700                 if (default_keybindings[i].request == request) {
1701                         if (!append_key(buf, &pos, &default_keybindings[i]))
1702                                 return "Too many keybindings!";
1703                         if (!all)
1704                                 return buf;
1705                 }
1706         }
1708         return buf;
1711 struct run_request {
1712         enum keymap keymap;
1713         int key;
1714         const char *argv[SIZEOF_ARG];
1715 };
1717 static struct run_request *run_request;
1718 static size_t run_requests;
1720 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1722 static enum request
1723 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1725         struct run_request *req;
1727         if (argc >= ARRAY_SIZE(req->argv) - 1)
1728                 return REQ_NONE;
1730         if (!realloc_run_requests(&run_request, run_requests, 1))
1731                 return REQ_NONE;
1733         req = &run_request[run_requests];
1734         req->keymap = keymap;
1735         req->key = key;
1736         req->argv[0] = NULL;
1738         if (!format_argv(req->argv, argv, FORMAT_NONE))
1739                 return REQ_NONE;
1741         return REQ_NONE + ++run_requests;
1744 static struct run_request *
1745 get_run_request(enum request request)
1747         if (request <= REQ_NONE)
1748                 return NULL;
1749         return &run_request[request - REQ_NONE - 1];
1752 static void
1753 add_builtin_run_requests(void)
1755         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1756         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1757         const char *commit[] = { "git", "commit", NULL };
1758         const char *gc[] = { "git", "gc", NULL };
1759         struct {
1760                 enum keymap keymap;
1761                 int key;
1762                 int argc;
1763                 const char **argv;
1764         } reqs[] = {
1765                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1766                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1767                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1768                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1769         };
1770         int i;
1772         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1773                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1775                 if (req != reqs[i].key)
1776                         continue;
1777                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1778                 if (req != REQ_NONE)
1779                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1780         }
1783 /*
1784  * User config file handling.
1785  */
1787 static int   config_lineno;
1788 static bool  config_errors;
1789 static const char *config_msg;
1791 static const struct enum_map color_map[] = {
1792 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1793         COLOR_MAP(DEFAULT),
1794         COLOR_MAP(BLACK),
1795         COLOR_MAP(BLUE),
1796         COLOR_MAP(CYAN),
1797         COLOR_MAP(GREEN),
1798         COLOR_MAP(MAGENTA),
1799         COLOR_MAP(RED),
1800         COLOR_MAP(WHITE),
1801         COLOR_MAP(YELLOW),
1802 };
1804 static const struct enum_map attr_map[] = {
1805 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1806         ATTR_MAP(NORMAL),
1807         ATTR_MAP(BLINK),
1808         ATTR_MAP(BOLD),
1809         ATTR_MAP(DIM),
1810         ATTR_MAP(REVERSE),
1811         ATTR_MAP(STANDOUT),
1812         ATTR_MAP(UNDERLINE),
1813 };
1815 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1817 static int parse_step(double *opt, const char *arg)
1819         *opt = atoi(arg);
1820         if (!strchr(arg, '%'))
1821                 return OK;
1823         /* "Shift down" so 100% and 1 does not conflict. */
1824         *opt = (*opt - 1) / 100;
1825         if (*opt >= 1.0) {
1826                 *opt = 0.99;
1827                 config_msg = "Step value larger than 100%";
1828                 return ERR;
1829         }
1830         if (*opt < 0.0) {
1831                 *opt = 1;
1832                 config_msg = "Invalid step value";
1833                 return ERR;
1834         }
1835         return OK;
1838 static int
1839 parse_int(int *opt, const char *arg, int min, int max)
1841         int value = atoi(arg);
1843         if (min <= value && value <= max) {
1844                 *opt = value;
1845                 return OK;
1846         }
1848         config_msg = "Integer value out of bound";
1849         return ERR;
1852 static bool
1853 set_color(int *color, const char *name)
1855         if (map_enum(color, color_map, name))
1856                 return TRUE;
1857         if (!prefixcmp(name, "color"))
1858                 return parse_int(color, name + 5, 0, 255) == OK;
1859         return FALSE;
1862 /* Wants: object fgcolor bgcolor [attribute] */
1863 static int
1864 option_color_command(int argc, const char *argv[])
1866         struct line_info *info;
1868         if (argc < 3) {
1869                 config_msg = "Wrong number of arguments given to color command";
1870                 return ERR;
1871         }
1873         info = get_line_info(argv[0]);
1874         if (!info) {
1875                 static const struct enum_map obsolete[] = {
1876                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1877                         ENUM_MAP("main-date",   LINE_DATE),
1878                         ENUM_MAP("main-author", LINE_AUTHOR),
1879                 };
1880                 int index;
1882                 if (!map_enum(&index, obsolete, argv[0])) {
1883                         config_msg = "Unknown color name";
1884                         return ERR;
1885                 }
1886                 info = &line_info[index];
1887         }
1889         if (!set_color(&info->fg, argv[1]) ||
1890             !set_color(&info->bg, argv[2])) {
1891                 config_msg = "Unknown color";
1892                 return ERR;
1893         }
1895         info->attr = 0;
1896         while (argc-- > 3) {
1897                 int attr;
1899                 if (!set_attribute(&attr, argv[argc])) {
1900                         config_msg = "Unknown attribute";
1901                         return ERR;
1902                 }
1903                 info->attr |= attr;
1904         }
1906         return OK;
1909 static int parse_bool(bool *opt, const char *arg)
1911         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1912                 ? TRUE : FALSE;
1913         return OK;
1916 static int parse_enum_do(unsigned int *opt, const char *arg,
1917                          const struct enum_map *map, size_t map_size)
1919         bool is_true;
1921         assert(map_size > 1);
1923         if (map_enum_do(map, map_size, (int *) opt, arg))
1924                 return OK;
1926         if (parse_bool(&is_true, arg) != OK)
1927                 return ERR;
1929         *opt = is_true ? map[1].value : map[0].value;
1930         return OK;
1933 #define parse_enum(opt, arg, map) \
1934         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1936 static int
1937 parse_string(char *opt, const char *arg, size_t optsize)
1939         int arglen = strlen(arg);
1941         switch (arg[0]) {
1942         case '\"':
1943         case '\'':
1944                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1945                         config_msg = "Unmatched quotation";
1946                         return ERR;
1947                 }
1948                 arg += 1; arglen -= 2;
1949         default:
1950                 string_ncopy_do(opt, optsize, arg, arglen);
1951                 return OK;
1952         }
1955 /* Wants: name = value */
1956 static int
1957 option_set_command(int argc, const char *argv[])
1959         if (argc != 3) {
1960                 config_msg = "Wrong number of arguments given to set command";
1961                 return ERR;
1962         }
1964         if (strcmp(argv[1], "=")) {
1965                 config_msg = "No value assigned";
1966                 return ERR;
1967         }
1969         if (!strcmp(argv[0], "show-author"))
1970                 return parse_enum(&opt_author, argv[2], author_map);
1972         if (!strcmp(argv[0], "show-date"))
1973                 return parse_enum(&opt_date, argv[2], date_map);
1975         if (!strcmp(argv[0], "show-rev-graph"))
1976                 return parse_bool(&opt_rev_graph, argv[2]);
1978         if (!strcmp(argv[0], "show-refs"))
1979                 return parse_bool(&opt_show_refs, argv[2]);
1981         if (!strcmp(argv[0], "show-line-numbers"))
1982                 return parse_bool(&opt_line_number, argv[2]);
1984         if (!strcmp(argv[0], "line-graphics"))
1985                 return parse_bool(&opt_line_graphics, argv[2]);
1987         if (!strcmp(argv[0], "line-number-interval"))
1988                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1990         if (!strcmp(argv[0], "author-width"))
1991                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1993         if (!strcmp(argv[0], "horizontal-scroll"))
1994                 return parse_step(&opt_hscroll, argv[2]);
1996         if (!strcmp(argv[0], "split-view-height"))
1997                 return parse_step(&opt_scale_split_view, argv[2]);
1999         if (!strcmp(argv[0], "tab-size"))
2000                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2002         if (!strcmp(argv[0], "commit-encoding"))
2003                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2005         config_msg = "Unknown variable name";
2006         return ERR;
2009 /* Wants: mode request key */
2010 static int
2011 option_bind_command(int argc, const char *argv[])
2013         enum request request;
2014         int keymap = -1;
2015         int key;
2017         if (argc < 3) {
2018                 config_msg = "Wrong number of arguments given to bind command";
2019                 return ERR;
2020         }
2022         if (!set_keymap(&keymap, argv[0])) {
2023                 config_msg = "Unknown key map";
2024                 return ERR;
2025         }
2027         key = get_key_value(argv[1]);
2028         if (key == ERR) {
2029                 config_msg = "Unknown key";
2030                 return ERR;
2031         }
2033         request = get_request(argv[2]);
2034         if (request == REQ_UNKNOWN) {
2035                 static const struct enum_map obsolete[] = {
2036                         ENUM_MAP("cherry-pick",         REQ_NONE),
2037                         ENUM_MAP("screen-resize",       REQ_NONE),
2038                         ENUM_MAP("tree-parent",         REQ_PARENT),
2039                 };
2040                 int alias;
2042                 if (map_enum(&alias, obsolete, argv[2])) {
2043                         if (alias != REQ_NONE)
2044                                 add_keybinding(keymap, alias, key);
2045                         config_msg = "Obsolete request name";
2046                         return ERR;
2047                 }
2048         }
2049         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2050                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2051         if (request == REQ_UNKNOWN) {
2052                 config_msg = "Unknown request name";
2053                 return ERR;
2054         }
2056         add_keybinding(keymap, request, key);
2058         return OK;
2061 static int
2062 set_option(const char *opt, char *value)
2064         const char *argv[SIZEOF_ARG];
2065         int argc = 0;
2067         if (!argv_from_string(argv, &argc, value)) {
2068                 config_msg = "Too many option arguments";
2069                 return ERR;
2070         }
2072         if (!strcmp(opt, "color"))
2073                 return option_color_command(argc, argv);
2075         if (!strcmp(opt, "set"))
2076                 return option_set_command(argc, argv);
2078         if (!strcmp(opt, "bind"))
2079                 return option_bind_command(argc, argv);
2081         config_msg = "Unknown option command";
2082         return ERR;
2085 static int
2086 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2088         int status = OK;
2090         config_lineno++;
2091         config_msg = "Internal error";
2093         /* Check for comment markers, since read_properties() will
2094          * only ensure opt and value are split at first " \t". */
2095         optlen = strcspn(opt, "#");
2096         if (optlen == 0)
2097                 return OK;
2099         if (opt[optlen] != 0) {
2100                 config_msg = "No option value";
2101                 status = ERR;
2103         }  else {
2104                 /* Look for comment endings in the value. */
2105                 size_t len = strcspn(value, "#");
2107                 if (len < valuelen) {
2108                         valuelen = len;
2109                         value[valuelen] = 0;
2110                 }
2112                 status = set_option(opt, value);
2113         }
2115         if (status == ERR) {
2116                 warn("Error on line %d, near '%.*s': %s",
2117                      config_lineno, (int) optlen, opt, config_msg);
2118                 config_errors = TRUE;
2119         }
2121         /* Always keep going if errors are encountered. */
2122         return OK;
2125 static void
2126 load_option_file(const char *path)
2128         struct io io = {};
2130         /* It's OK that the file doesn't exist. */
2131         if (!io_open(&io, "%s", path))
2132                 return;
2134         config_lineno = 0;
2135         config_errors = FALSE;
2137         if (io_load(&io, " \t", read_option) == ERR ||
2138             config_errors == TRUE)
2139                 warn("Errors while loading %s.", path);
2142 static int
2143 load_options(void)
2145         const char *home = getenv("HOME");
2146         const char *tigrc_user = getenv("TIGRC_USER");
2147         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2148         char buf[SIZEOF_STR];
2150         if (!tigrc_system)
2151                 tigrc_system = SYSCONFDIR "/tigrc";
2152         load_option_file(tigrc_system);
2154         if (!tigrc_user) {
2155                 if (!home || !string_format(buf, "%s/.tigrc", home))
2156                         return ERR;
2157                 tigrc_user = buf;
2158         }
2159         load_option_file(tigrc_user);
2161         /* Add _after_ loading config files to avoid adding run requests
2162          * that conflict with keybindings. */
2163         add_builtin_run_requests();
2165         return OK;
2169 /*
2170  * The viewer
2171  */
2173 struct view;
2174 struct view_ops;
2176 /* The display array of active views and the index of the current view. */
2177 static struct view *display[2];
2178 static unsigned int current_view;
2180 #define foreach_displayed_view(view, i) \
2181         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2183 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2185 /* Current head and commit ID */
2186 static char ref_blob[SIZEOF_REF]        = "";
2187 static char ref_commit[SIZEOF_REF]      = "HEAD";
2188 static char ref_head[SIZEOF_REF]        = "HEAD";
2189 static char ref_branch[SIZEOF_REF]      = "";
2191 enum view_type {
2192         VIEW_MAIN,
2193         VIEW_DIFF,
2194         VIEW_LOG,
2195         VIEW_TREE,
2196         VIEW_BLOB,
2197         VIEW_BLAME,
2198         VIEW_BRANCH,
2199         VIEW_HELP,
2200         VIEW_PAGER,
2201         VIEW_STATUS,
2202         VIEW_STAGE,
2203 };
2205 struct view {
2206         enum view_type type;    /* View type */
2207         const char *name;       /* View name */
2208         const char *cmd_env;    /* Command line set via environment */
2209         const char *id;         /* Points to either of ref_{head,commit,blob} */
2211         struct view_ops *ops;   /* View operations */
2213         enum keymap keymap;     /* What keymap does this view have */
2214         bool git_dir;           /* Whether the view requires a git directory. */
2215         bool refresh;           /* Whether the view supports refreshing. */
2217         char ref[SIZEOF_REF];   /* Hovered commit reference */
2218         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2220         int height, width;      /* The width and height of the main window */
2221         WINDOW *win;            /* The main window */
2222         WINDOW *title;          /* The title window living below the main window */
2224         /* Navigation */
2225         unsigned long offset;   /* Offset of the window top */
2226         unsigned long yoffset;  /* Offset from the window side. */
2227         unsigned long lineno;   /* Current line number */
2228         unsigned long p_offset; /* Previous offset of the window top */
2229         unsigned long p_yoffset;/* Previous offset from the window side */
2230         unsigned long p_lineno; /* Previous current line number */
2231         bool p_restore;         /* Should the previous position be restored. */
2233         /* Searching */
2234         char grep[SIZEOF_STR];  /* Search string */
2235         regex_t *regex;         /* Pre-compiled regexp */
2237         /* If non-NULL, points to the view that opened this view. If this view
2238          * is closed tig will switch back to the parent view. */
2239         struct view *parent;
2241         /* Buffering */
2242         size_t lines;           /* Total number of lines */
2243         struct line *line;      /* Line index */
2244         unsigned int digits;    /* Number of digits in the lines member. */
2246         /* Drawing */
2247         struct line *curline;   /* Line currently being drawn. */
2248         enum line_type curtype; /* Attribute currently used for drawing. */
2249         unsigned long col;      /* Column when drawing. */
2250         bool has_scrolled;      /* View was scrolled. */
2252         /* Loading */
2253         struct io io;
2254         struct io *pipe;
2255         time_t start_time;
2256         time_t update_secs;
2257 };
2259 struct view_ops {
2260         /* What type of content being displayed. Used in the title bar. */
2261         const char *type;
2262         /* Default command arguments. */
2263         const char **argv;
2264         /* Open and reads in all view content. */
2265         bool (*open)(struct view *view);
2266         /* Read one line; updates view->line. */
2267         bool (*read)(struct view *view, char *data);
2268         /* Draw one line; @lineno must be < view->height. */
2269         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2270         /* Depending on view handle a special requests. */
2271         enum request (*request)(struct view *view, enum request request, struct line *line);
2272         /* Search for regexp in a line. */
2273         bool (*grep)(struct view *view, struct line *line);
2274         /* Select line */
2275         void (*select)(struct view *view, struct line *line);
2276         /* Prepare view for loading */
2277         bool (*prepare)(struct view *view);
2278 };
2280 static struct view_ops blame_ops;
2281 static struct view_ops blob_ops;
2282 static struct view_ops diff_ops;
2283 static struct view_ops help_ops;
2284 static struct view_ops log_ops;
2285 static struct view_ops main_ops;
2286 static struct view_ops pager_ops;
2287 static struct view_ops stage_ops;
2288 static struct view_ops status_ops;
2289 static struct view_ops tree_ops;
2290 static struct view_ops branch_ops;
2292 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2293         { type, name, #env, ref, ops, map, git, refresh }
2295 #define VIEW_(id, name, ops, git, refresh, ref) \
2296         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2298 static struct view views[] = {
2299         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  TRUE,  ref_head),
2300         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  FALSE, ref_commit),
2301         VIEW_(LOG,    "log",    &log_ops,    TRUE,  TRUE,  ref_head),
2302         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  FALSE, ref_commit),
2303         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  FALSE, ref_blob),
2304         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  FALSE, ref_commit),
2305         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  TRUE,  ref_head),
2306         VIEW_(HELP,   "help",   &help_ops,   FALSE, FALSE, ""),
2307         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, FALSE, "stdin"),
2308         VIEW_(STATUS, "status", &status_ops, TRUE,  TRUE,  ""),
2309         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  TRUE,  ""),
2310 };
2312 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2314 #define foreach_view(view, i) \
2315         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2317 #define view_is_displayed(view) \
2318         (view == display[0] || view == display[1])
2320 #define view_has_parent(view, child_type, parent_type) \
2321         (view->type == child_type && view->parent && view->parent->type == parent_type)
2323 static inline void
2324 set_view_attr(struct view *view, enum line_type type)
2326         if (!view->curline->selected && view->curtype != type) {
2327                 (void) wattrset(view->win, get_line_attr(type));
2328                 wchgat(view->win, -1, 0, type, NULL);
2329                 view->curtype = type;
2330         }
2333 static int
2334 draw_chars(struct view *view, enum line_type type, const char *string,
2335            int max_len, bool use_tilde)
2337         static char out_buffer[BUFSIZ * 2];
2338         int len = 0;
2339         int col = 0;
2340         int trimmed = FALSE;
2341         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2343         if (max_len <= 0)
2344                 return 0;
2346         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2348         set_view_attr(view, type);
2349         if (len > 0) {
2350                 if (opt_iconv_out != ICONV_NONE) {
2351                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2352                         size_t inlen = len + 1;
2354                         char *outbuf = out_buffer;
2355                         size_t outlen = sizeof(out_buffer);
2357                         size_t ret;
2359                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2360                         if (ret != (size_t) -1) {
2361                                 string = out_buffer;
2362                                 len = sizeof(out_buffer) - outlen;
2363                         }
2364                 }
2366                 waddnstr(view->win, string, len);
2367         }
2368         if (trimmed && use_tilde) {
2369                 set_view_attr(view, LINE_DELIMITER);
2370                 waddch(view->win, '~');
2371                 col++;
2372         }
2374         return col;
2377 static int
2378 draw_space(struct view *view, enum line_type type, int max, int spaces)
2380         static char space[] = "                    ";
2381         int col = 0;
2383         spaces = MIN(max, spaces);
2385         while (spaces > 0) {
2386                 int len = MIN(spaces, sizeof(space) - 1);
2388                 col += draw_chars(view, type, space, len, FALSE);
2389                 spaces -= len;
2390         }
2392         return col;
2395 static bool
2396 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2398         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2399         return view->width + view->yoffset <= view->col;
2402 static bool
2403 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2405         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2406         int max = view->width + view->yoffset - view->col;
2407         int i;
2409         if (max < size)
2410                 size = max;
2412         set_view_attr(view, type);
2413         /* Using waddch() instead of waddnstr() ensures that
2414          * they'll be rendered correctly for the cursor line. */
2415         for (i = skip; i < size; i++)
2416                 waddch(view->win, graphic[i]);
2418         view->col += size;
2419         if (size < max && skip <= size)
2420                 waddch(view->win, ' ');
2421         view->col++;
2423         return view->width + view->yoffset <= view->col;
2426 static bool
2427 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2429         int max = MIN(view->width + view->yoffset - view->col, len);
2430         int col;
2432         if (text)
2433                 col = draw_chars(view, type, text, max - 1, trim);
2434         else
2435                 col = draw_space(view, type, max - 1, max - 1);
2437         view->col += col;
2438         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2439         return view->width + view->yoffset <= view->col;
2442 static bool
2443 draw_date(struct view *view, struct time *time)
2445         const char *date = mkdate(time, opt_date);
2446         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2448         return draw_field(view, LINE_DATE, date, cols, FALSE);
2451 static bool
2452 draw_author(struct view *view, const char *author)
2454         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2455         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2457         if (abbreviate && author)
2458                 author = get_author_initials(author);
2460         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2463 static bool
2464 draw_mode(struct view *view, mode_t mode)
2466         const char *str;
2468         if (S_ISDIR(mode))
2469                 str = "drwxr-xr-x";
2470         else if (S_ISLNK(mode))
2471                 str = "lrwxrwxrwx";
2472         else if (S_ISGITLINK(mode))
2473                 str = "m---------";
2474         else if (S_ISREG(mode) && mode & S_IXUSR)
2475                 str = "-rwxr-xr-x";
2476         else if (S_ISREG(mode))
2477                 str = "-rw-r--r--";
2478         else
2479                 str = "----------";
2481         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2484 static bool
2485 draw_lineno(struct view *view, unsigned int lineno)
2487         char number[10];
2488         int digits3 = view->digits < 3 ? 3 : view->digits;
2489         int max = MIN(view->width + view->yoffset - view->col, digits3);
2490         char *text = NULL;
2491         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2493         lineno += view->offset + 1;
2494         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2495                 static char fmt[] = "%1ld";
2497                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2498                 if (string_format(number, fmt, lineno))
2499                         text = number;
2500         }
2501         if (text)
2502                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2503         else
2504                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2505         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2508 static bool
2509 draw_view_line(struct view *view, unsigned int lineno)
2511         struct line *line;
2512         bool selected = (view->offset + lineno == view->lineno);
2514         assert(view_is_displayed(view));
2516         if (view->offset + lineno >= view->lines)
2517                 return FALSE;
2519         line = &view->line[view->offset + lineno];
2521         wmove(view->win, lineno, 0);
2522         if (line->cleareol)
2523                 wclrtoeol(view->win);
2524         view->col = 0;
2525         view->curline = line;
2526         view->curtype = LINE_NONE;
2527         line->selected = FALSE;
2528         line->dirty = line->cleareol = 0;
2530         if (selected) {
2531                 set_view_attr(view, LINE_CURSOR);
2532                 line->selected = TRUE;
2533                 view->ops->select(view, line);
2534         }
2536         return view->ops->draw(view, line, lineno);
2539 static void
2540 redraw_view_dirty(struct view *view)
2542         bool dirty = FALSE;
2543         int lineno;
2545         for (lineno = 0; lineno < view->height; lineno++) {
2546                 if (view->offset + lineno >= view->lines)
2547                         break;
2548                 if (!view->line[view->offset + lineno].dirty)
2549                         continue;
2550                 dirty = TRUE;
2551                 if (!draw_view_line(view, lineno))
2552                         break;
2553         }
2555         if (!dirty)
2556                 return;
2557         wnoutrefresh(view->win);
2560 static void
2561 redraw_view_from(struct view *view, int lineno)
2563         assert(0 <= lineno && lineno < view->height);
2565         for (; lineno < view->height; lineno++) {
2566                 if (!draw_view_line(view, lineno))
2567                         break;
2568         }
2570         wnoutrefresh(view->win);
2573 static void
2574 redraw_view(struct view *view)
2576         werase(view->win);
2577         redraw_view_from(view, 0);
2581 static void
2582 update_view_title(struct view *view)
2584         char buf[SIZEOF_STR];
2585         char state[SIZEOF_STR];
2586         size_t bufpos = 0, statelen = 0;
2588         assert(view_is_displayed(view));
2590         if (view->type != VIEW_STATUS && view->lines) {
2591                 unsigned int view_lines = view->offset + view->height;
2592                 unsigned int lines = view->lines
2593                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2594                                    : 0;
2596                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2597                                    view->ops->type,
2598                                    view->lineno + 1,
2599                                    view->lines,
2600                                    lines);
2602         }
2604         if (view->pipe) {
2605                 time_t secs = time(NULL) - view->start_time;
2607                 /* Three git seconds are a long time ... */
2608                 if (secs > 2)
2609                         string_format_from(state, &statelen, " loading %lds", secs);
2610         }
2612         string_format_from(buf, &bufpos, "[%s]", view->name);
2613         if (*view->ref && bufpos < view->width) {
2614                 size_t refsize = strlen(view->ref);
2615                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2617                 if (minsize < view->width)
2618                         refsize = view->width - minsize + 7;
2619                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2620         }
2622         if (statelen && bufpos < view->width) {
2623                 string_format_from(buf, &bufpos, "%s", state);
2624         }
2626         if (view == display[current_view])
2627                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2628         else
2629                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2631         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2632         wclrtoeol(view->title);
2633         wnoutrefresh(view->title);
2636 static int
2637 apply_step(double step, int value)
2639         if (step >= 1)
2640                 return (int) step;
2641         value *= step + 0.01;
2642         return value ? value : 1;
2645 static void
2646 resize_display(void)
2648         int offset, i;
2649         struct view *base = display[0];
2650         struct view *view = display[1] ? display[1] : display[0];
2652         /* Setup window dimensions */
2654         getmaxyx(stdscr, base->height, base->width);
2656         /* Make room for the status window. */
2657         base->height -= 1;
2659         if (view != base) {
2660                 /* Horizontal split. */
2661                 view->width   = base->width;
2662                 view->height  = apply_step(opt_scale_split_view, base->height);
2663                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2664                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2665                 base->height -= view->height;
2667                 /* Make room for the title bar. */
2668                 view->height -= 1;
2669         }
2671         /* Make room for the title bar. */
2672         base->height -= 1;
2674         offset = 0;
2676         foreach_displayed_view (view, i) {
2677                 if (!view->win) {
2678                         view->win = newwin(view->height, 0, offset, 0);
2679                         if (!view->win)
2680                                 die("Failed to create %s view", view->name);
2682                         scrollok(view->win, FALSE);
2684                         view->title = newwin(1, 0, offset + view->height, 0);
2685                         if (!view->title)
2686                                 die("Failed to create title window");
2688                 } else {
2689                         wresize(view->win, view->height, view->width);
2690                         mvwin(view->win,   offset, 0);
2691                         mvwin(view->title, offset + view->height, 0);
2692                 }
2694                 offset += view->height + 1;
2695         }
2698 static void
2699 redraw_display(bool clear)
2701         struct view *view;
2702         int i;
2704         foreach_displayed_view (view, i) {
2705                 if (clear)
2706                         wclear(view->win);
2707                 redraw_view(view);
2708                 update_view_title(view);
2709         }
2712 static void
2713 toggle_enum_option_do(unsigned int *opt, const char *help,
2714                       const struct enum_map *map, size_t size)
2716         *opt = (*opt + 1) % size;
2717         redraw_display(FALSE);
2718         report("Displaying %s %s", enum_name(map[*opt]), help);
2721 #define toggle_enum_option(opt, help, map) \
2722         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2724 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2725 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2727 static void
2728 toggle_view_option(bool *option, const char *help)
2730         *option = !*option;
2731         redraw_display(FALSE);
2732         report("%sabling %s", *option ? "En" : "Dis", help);
2735 static void
2736 open_option_menu(void)
2738         const struct menu_item menu[] = {
2739                 { '.', "line numbers", &opt_line_number },
2740                 { 'D', "date display", &opt_date },
2741                 { 'A', "author display", &opt_author },
2742                 { 'g', "revision graph display", &opt_rev_graph },
2743                 { 'F', "reference display", &opt_show_refs },
2744                 { 0 }
2745         };
2746         int selected = 0;
2748         if (prompt_menu("Toggle option", menu, &selected)) {
2749                 if (menu[selected].data == &opt_date)
2750                         toggle_date();
2751                 else if (menu[selected].data == &opt_author)
2752                         toggle_author();
2753                 else
2754                         toggle_view_option(menu[selected].data, menu[selected].text);
2755         }
2758 static void
2759 maximize_view(struct view *view)
2761         memset(display, 0, sizeof(display));
2762         current_view = 0;
2763         display[current_view] = view;
2764         resize_display();
2765         redraw_display(FALSE);
2766         report("");
2770 /*
2771  * Navigation
2772  */
2774 static bool
2775 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2777         if (lineno >= view->lines)
2778                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2780         if (offset > lineno || offset + view->height <= lineno) {
2781                 unsigned long half = view->height / 2;
2783                 if (lineno > half)
2784                         offset = lineno - half;
2785                 else
2786                         offset = 0;
2787         }
2789         if (offset != view->offset || lineno != view->lineno) {
2790                 view->offset = offset;
2791                 view->lineno = lineno;
2792                 return TRUE;
2793         }
2795         return FALSE;
2798 /* Scrolling backend */
2799 static void
2800 do_scroll_view(struct view *view, int lines)
2802         bool redraw_current_line = FALSE;
2804         /* The rendering expects the new offset. */
2805         view->offset += lines;
2807         assert(0 <= view->offset && view->offset < view->lines);
2808         assert(lines);
2810         /* Move current line into the view. */
2811         if (view->lineno < view->offset) {
2812                 view->lineno = view->offset;
2813                 redraw_current_line = TRUE;
2814         } else if (view->lineno >= view->offset + view->height) {
2815                 view->lineno = view->offset + view->height - 1;
2816                 redraw_current_line = TRUE;
2817         }
2819         assert(view->offset <= view->lineno && view->lineno < view->lines);
2821         /* Redraw the whole screen if scrolling is pointless. */
2822         if (view->height < ABS(lines)) {
2823                 redraw_view(view);
2825         } else {
2826                 int line = lines > 0 ? view->height - lines : 0;
2827                 int end = line + ABS(lines);
2829                 scrollok(view->win, TRUE);
2830                 wscrl(view->win, lines);
2831                 scrollok(view->win, FALSE);
2833                 while (line < end && draw_view_line(view, line))
2834                         line++;
2836                 if (redraw_current_line)
2837                         draw_view_line(view, view->lineno - view->offset);
2838                 wnoutrefresh(view->win);
2839         }
2841         view->has_scrolled = TRUE;
2842         report("");
2845 /* Scroll frontend */
2846 static void
2847 scroll_view(struct view *view, enum request request)
2849         int lines = 1;
2851         assert(view_is_displayed(view));
2853         switch (request) {
2854         case REQ_SCROLL_LEFT:
2855                 if (view->yoffset == 0) {
2856                         report("Cannot scroll beyond the first column");
2857                         return;
2858                 }
2859                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2860                         view->yoffset = 0;
2861                 else
2862                         view->yoffset -= apply_step(opt_hscroll, view->width);
2863                 redraw_view_from(view, 0);
2864                 report("");
2865                 return;
2866         case REQ_SCROLL_RIGHT:
2867                 view->yoffset += apply_step(opt_hscroll, view->width);
2868                 redraw_view(view);
2869                 report("");
2870                 return;
2871         case REQ_SCROLL_PAGE_DOWN:
2872                 lines = view->height;
2873         case REQ_SCROLL_LINE_DOWN:
2874                 if (view->offset + lines > view->lines)
2875                         lines = view->lines - view->offset;
2877                 if (lines == 0 || view->offset + view->height >= view->lines) {
2878                         report("Cannot scroll beyond the last line");
2879                         return;
2880                 }
2881                 break;
2883         case REQ_SCROLL_PAGE_UP:
2884                 lines = view->height;
2885         case REQ_SCROLL_LINE_UP:
2886                 if (lines > view->offset)
2887                         lines = view->offset;
2889                 if (lines == 0) {
2890                         report("Cannot scroll beyond the first line");
2891                         return;
2892                 }
2894                 lines = -lines;
2895                 break;
2897         default:
2898                 die("request %d not handled in switch", request);
2899         }
2901         do_scroll_view(view, lines);
2904 /* Cursor moving */
2905 static void
2906 move_view(struct view *view, enum request request)
2908         int scroll_steps = 0;
2909         int steps;
2911         switch (request) {
2912         case REQ_MOVE_FIRST_LINE:
2913                 steps = -view->lineno;
2914                 break;
2916         case REQ_MOVE_LAST_LINE:
2917                 steps = view->lines - view->lineno - 1;
2918                 break;
2920         case REQ_MOVE_PAGE_UP:
2921                 steps = view->height > view->lineno
2922                       ? -view->lineno : -view->height;
2923                 break;
2925         case REQ_MOVE_PAGE_DOWN:
2926                 steps = view->lineno + view->height >= view->lines
2927                       ? view->lines - view->lineno - 1 : view->height;
2928                 break;
2930         case REQ_MOVE_UP:
2931                 steps = -1;
2932                 break;
2934         case REQ_MOVE_DOWN:
2935                 steps = 1;
2936                 break;
2938         default:
2939                 die("request %d not handled in switch", request);
2940         }
2942         if (steps <= 0 && view->lineno == 0) {
2943                 report("Cannot move beyond the first line");
2944                 return;
2946         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2947                 report("Cannot move beyond the last line");
2948                 return;
2949         }
2951         /* Move the current line */
2952         view->lineno += steps;
2953         assert(0 <= view->lineno && view->lineno < view->lines);
2955         /* Check whether the view needs to be scrolled */
2956         if (view->lineno < view->offset ||
2957             view->lineno >= view->offset + view->height) {
2958                 scroll_steps = steps;
2959                 if (steps < 0 && -steps > view->offset) {
2960                         scroll_steps = -view->offset;
2962                 } else if (steps > 0) {
2963                         if (view->lineno == view->lines - 1 &&
2964                             view->lines > view->height) {
2965                                 scroll_steps = view->lines - view->offset - 1;
2966                                 if (scroll_steps >= view->height)
2967                                         scroll_steps -= view->height - 1;
2968                         }
2969                 }
2970         }
2972         if (!view_is_displayed(view)) {
2973                 view->offset += scroll_steps;
2974                 assert(0 <= view->offset && view->offset < view->lines);
2975                 view->ops->select(view, &view->line[view->lineno]);
2976                 return;
2977         }
2979         /* Repaint the old "current" line if we be scrolling */
2980         if (ABS(steps) < view->height)
2981                 draw_view_line(view, view->lineno - steps - view->offset);
2983         if (scroll_steps) {
2984                 do_scroll_view(view, scroll_steps);
2985                 return;
2986         }
2988         /* Draw the current line */
2989         draw_view_line(view, view->lineno - view->offset);
2991         wnoutrefresh(view->win);
2992         report("");
2996 /*
2997  * Searching
2998  */
3000 static void search_view(struct view *view, enum request request);
3002 static bool
3003 grep_text(struct view *view, const char *text[])
3005         regmatch_t pmatch;
3006         size_t i;
3008         for (i = 0; text[i]; i++)
3009                 if (*text[i] &&
3010                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3011                         return TRUE;
3012         return FALSE;
3015 static void
3016 select_view_line(struct view *view, unsigned long lineno)
3018         unsigned long old_lineno = view->lineno;
3019         unsigned long old_offset = view->offset;
3021         if (goto_view_line(view, view->offset, lineno)) {
3022                 if (view_is_displayed(view)) {
3023                         if (old_offset != view->offset) {
3024                                 redraw_view(view);
3025                         } else {
3026                                 draw_view_line(view, old_lineno - view->offset);
3027                                 draw_view_line(view, view->lineno - view->offset);
3028                                 wnoutrefresh(view->win);
3029                         }
3030                 } else {
3031                         view->ops->select(view, &view->line[view->lineno]);
3032                 }
3033         }
3036 static void
3037 find_next(struct view *view, enum request request)
3039         unsigned long lineno = view->lineno;
3040         int direction;
3042         if (!*view->grep) {
3043                 if (!*opt_search)
3044                         report("No previous search");
3045                 else
3046                         search_view(view, request);
3047                 return;
3048         }
3050         switch (request) {
3051         case REQ_SEARCH:
3052         case REQ_FIND_NEXT:
3053                 direction = 1;
3054                 break;
3056         case REQ_SEARCH_BACK:
3057         case REQ_FIND_PREV:
3058                 direction = -1;
3059                 break;
3061         default:
3062                 return;
3063         }
3065         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3066                 lineno += direction;
3068         /* Note, lineno is unsigned long so will wrap around in which case it
3069          * will become bigger than view->lines. */
3070         for (; lineno < view->lines; lineno += direction) {
3071                 if (view->ops->grep(view, &view->line[lineno])) {
3072                         select_view_line(view, lineno);
3073                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3074                         return;
3075                 }
3076         }
3078         report("No match found for '%s'", view->grep);
3081 static void
3082 search_view(struct view *view, enum request request)
3084         int regex_err;
3086         if (view->regex) {
3087                 regfree(view->regex);
3088                 *view->grep = 0;
3089         } else {
3090                 view->regex = calloc(1, sizeof(*view->regex));
3091                 if (!view->regex)
3092                         return;
3093         }
3095         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3096         if (regex_err != 0) {
3097                 char buf[SIZEOF_STR] = "unknown error";
3099                 regerror(regex_err, view->regex, buf, sizeof(buf));
3100                 report("Search failed: %s", buf);
3101                 return;
3102         }
3104         string_copy(view->grep, opt_search);
3106         find_next(view, request);
3109 /*
3110  * Incremental updating
3111  */
3113 static void
3114 reset_view(struct view *view)
3116         int i;
3118         for (i = 0; i < view->lines; i++)
3119                 free(view->line[i].data);
3120         free(view->line);
3122         view->p_offset = view->offset;
3123         view->p_yoffset = view->yoffset;
3124         view->p_lineno = view->lineno;
3126         view->line = NULL;
3127         view->offset = 0;
3128         view->yoffset = 0;
3129         view->lines  = 0;
3130         view->lineno = 0;
3131         view->vid[0] = 0;
3132         view->update_secs = 0;
3135 static void
3136 free_argv(const char *argv[])
3138         int argc;
3140         for (argc = 0; argv[argc]; argc++)
3141                 free((void *) argv[argc]);
3144 static const char *
3145 format_arg(const char *name)
3147         static struct {
3148                 const char *name;
3149                 size_t namelen;
3150                 const char *value;
3151                 const char *value_if_empty;
3152         } vars[] = {
3153 #define FORMAT_VAR(name, value, value_if_empty) \
3154         { name, STRING_SIZE(name), value, value_if_empty }
3155                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3156                 FORMAT_VAR("%(file)",           opt_file,       ""),
3157                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3158                 FORMAT_VAR("%(head)",           ref_head,       ""),
3159                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3160                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3161                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3162         };
3163         int i;
3165         for (i = 0; i < ARRAY_SIZE(vars); i++)
3166                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3167                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3169         report("Unknown replacement: `%s`", name);
3170         return NULL;
3173 static bool
3174 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3176         char buf[SIZEOF_STR];
3177         int argc;
3178         bool noreplace = flags == FORMAT_NONE;
3180         free_argv(dst_argv);
3182         for (argc = 0; src_argv[argc]; argc++) {
3183                 const char *arg = src_argv[argc];
3184                 size_t bufpos = 0;
3186                 while (arg) {
3187                         char *next = strstr(arg, "%(");
3188                         int len = next - arg;
3189                         const char *value;
3191                         if (!next || noreplace) {
3192                                 len = strlen(arg);
3193                                 value = "";
3195                         } else {
3196                                 value = format_arg(next);
3198                                 if (!value) {
3199                                         return FALSE;
3200                                 }
3201                         }
3203                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3204                                 return FALSE;
3206                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3207                 }
3209                 dst_argv[argc] = strdup(buf);
3210                 if (!dst_argv[argc])
3211                         break;
3212         }
3214         dst_argv[argc] = NULL;
3216         return src_argv[argc] == NULL;
3219 static bool
3220 restore_view_position(struct view *view)
3222         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3223                 return FALSE;
3225         /* Changing the view position cancels the restoring. */
3226         /* FIXME: Changing back to the first line is not detected. */
3227         if (view->offset != 0 || view->lineno != 0) {
3228                 view->p_restore = FALSE;
3229                 return FALSE;
3230         }
3232         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3233             view_is_displayed(view))
3234                 werase(view->win);
3236         view->yoffset = view->p_yoffset;
3237         view->p_restore = FALSE;
3239         return TRUE;
3242 static void
3243 end_update(struct view *view, bool force)
3245         if (!view->pipe)
3246                 return;
3247         while (!view->ops->read(view, NULL))
3248                 if (!force)
3249                         return;
3250         if (force)
3251                 io_kill(view->pipe);
3252         io_done(view->pipe);
3253         view->pipe = NULL;
3256 static void
3257 setup_update(struct view *view, const char *vid)
3259         reset_view(view);
3260         string_copy_rev(view->vid, vid);
3261         view->pipe = &view->io;
3262         view->start_time = time(NULL);
3265 static bool
3266 prepare_update(struct view *view, const char *argv[], const char *dir)
3268         if (view->pipe)
3269                 end_update(view, TRUE);
3270         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3273 static bool
3274 prepare_update_file(struct view *view, const char *name)
3276         if (view->pipe)
3277                 end_update(view, TRUE);
3278         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3281 static bool
3282 begin_update(struct view *view, bool refresh)
3284         if (view->pipe)
3285                 end_update(view, TRUE);
3287         if (!refresh) {
3288                 if (view->ops->prepare) {
3289                         if (!view->ops->prepare(view))
3290                                 return FALSE;
3291                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3292                         return FALSE;
3293                 }
3295                 /* Put the current ref_* value to the view title ref
3296                  * member. This is needed by the blob view. Most other
3297                  * views sets it automatically after loading because the
3298                  * first line is a commit line. */
3299                 string_copy_rev(view->ref, view->id);
3300         }
3302         if (!io_start(&view->io))
3303                 return FALSE;
3305         setup_update(view, view->id);
3307         return TRUE;
3310 static bool
3311 update_view(struct view *view)
3313         char out_buffer[BUFSIZ * 2];
3314         char *line;
3315         /* Clear the view and redraw everything since the tree sorting
3316          * might have rearranged things. */
3317         bool redraw = view->lines == 0;
3318         bool can_read = TRUE;
3320         if (!view->pipe)
3321                 return TRUE;
3323         if (!io_can_read(view->pipe)) {
3324                 if (view->lines == 0 && view_is_displayed(view)) {
3325                         time_t secs = time(NULL) - view->start_time;
3327                         if (secs > 1 && secs > view->update_secs) {
3328                                 if (view->update_secs == 0)
3329                                         redraw_view(view);
3330                                 update_view_title(view);
3331                                 view->update_secs = secs;
3332                         }
3333                 }
3334                 return TRUE;
3335         }
3337         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3338                 if (opt_iconv_in != ICONV_NONE) {
3339                         ICONV_CONST char *inbuf = line;
3340                         size_t inlen = strlen(line) + 1;
3342                         char *outbuf = out_buffer;
3343                         size_t outlen = sizeof(out_buffer);
3345                         size_t ret;
3347                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3348                         if (ret != (size_t) -1)
3349                                 line = out_buffer;
3350                 }
3352                 if (!view->ops->read(view, line)) {
3353                         report("Allocation failure");
3354                         end_update(view, TRUE);
3355                         return FALSE;
3356                 }
3357         }
3359         {
3360                 unsigned long lines = view->lines;
3361                 int digits;
3363                 for (digits = 0; lines; digits++)
3364                         lines /= 10;
3366                 /* Keep the displayed view in sync with line number scaling. */
3367                 if (digits != view->digits) {
3368                         view->digits = digits;
3369                         if (opt_line_number || view->type == VIEW_BLAME)
3370                                 redraw = TRUE;
3371                 }
3372         }
3374         if (io_error(view->pipe)) {
3375                 report("Failed to read: %s", io_strerror(view->pipe));
3376                 end_update(view, TRUE);
3378         } else if (io_eof(view->pipe)) {
3379                 report("");
3380                 end_update(view, FALSE);
3381         }
3383         if (restore_view_position(view))
3384                 redraw = TRUE;
3386         if (!view_is_displayed(view))
3387                 return TRUE;
3389         if (redraw)
3390                 redraw_view_from(view, 0);
3391         else
3392                 redraw_view_dirty(view);
3394         /* Update the title _after_ the redraw so that if the redraw picks up a
3395          * commit reference in view->ref it'll be available here. */
3396         update_view_title(view);
3397         return TRUE;
3400 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3402 static struct line *
3403 add_line_data(struct view *view, void *data, enum line_type type)
3405         struct line *line;
3407         if (!realloc_lines(&view->line, view->lines, 1))
3408                 return NULL;
3410         line = &view->line[view->lines++];
3411         memset(line, 0, sizeof(*line));
3412         line->type = type;
3413         line->data = data;
3414         line->dirty = 1;
3416         return line;
3419 static struct line *
3420 add_line_text(struct view *view, const char *text, enum line_type type)
3422         char *data = text ? strdup(text) : NULL;
3424         return data ? add_line_data(view, data, type) : NULL;
3427 static struct line *
3428 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3430         char buf[SIZEOF_STR];
3431         va_list args;
3433         va_start(args, fmt);
3434         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3435                 buf[0] = 0;
3436         va_end(args);
3438         return buf[0] ? add_line_text(view, buf, type) : NULL;
3441 /*
3442  * View opening
3443  */
3445 enum open_flags {
3446         OPEN_DEFAULT = 0,       /* Use default view switching. */
3447         OPEN_SPLIT = 1,         /* Split current view. */
3448         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3449         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3450         OPEN_PREPARED = 32,     /* Open already prepared command. */
3451 };
3453 static void
3454 open_view(struct view *prev, enum request request, enum open_flags flags)
3456         bool split = !!(flags & OPEN_SPLIT);
3457         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3458         bool nomaximize = !!(flags & OPEN_REFRESH);
3459         struct view *view = VIEW(request);
3460         int nviews = displayed_views();
3461         struct view *base_view = display[0];
3463         if (view == prev && nviews == 1 && !reload) {
3464                 report("Already in %s view", view->name);
3465                 return;
3466         }
3468         if (view->git_dir && !opt_git_dir[0]) {
3469                 report("The %s view is disabled in pager view", view->name);
3470                 return;
3471         }
3473         if (split) {
3474                 display[1] = view;
3475                 current_view = 1;
3476         } else if (!nomaximize) {
3477                 /* Maximize the current view. */
3478                 memset(display, 0, sizeof(display));
3479                 current_view = 0;
3480                 display[current_view] = view;
3481         }
3483         /* No parent signals that this is the first loaded view. */
3484         if (prev && view != prev) {
3485                 view->parent = prev;
3486         }
3488         /* Resize the view when switching between split- and full-screen,
3489          * or when switching between two different full-screen views. */
3490         if (nviews != displayed_views() ||
3491             (nviews == 1 && base_view != display[0]))
3492                 resize_display();
3494         if (view->ops->open) {
3495                 if (view->pipe)
3496                         end_update(view, TRUE);
3497                 if (!view->ops->open(view)) {
3498                         report("Failed to load %s view", view->name);
3499                         return;
3500                 }
3501                 restore_view_position(view);
3503         } else if ((reload || strcmp(view->vid, view->id)) &&
3504                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3505                 report("Failed to load %s view", view->name);
3506                 return;
3507         }
3509         if (split && prev->lineno - prev->offset >= prev->height) {
3510                 /* Take the title line into account. */
3511                 int lines = prev->lineno - prev->offset - prev->height + 1;
3513                 /* Scroll the view that was split if the current line is
3514                  * outside the new limited view. */
3515                 do_scroll_view(prev, lines);
3516         }
3518         if (prev && view != prev && split && view_is_displayed(prev)) {
3519                 /* "Blur" the previous view. */
3520                 update_view_title(prev);
3521         }
3523         if (view->pipe && view->lines == 0) {
3524                 /* Clear the old view and let the incremental updating refill
3525                  * the screen. */
3526                 werase(view->win);
3527                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3528                 report("");
3529         } else if (view_is_displayed(view)) {
3530                 redraw_view(view);
3531                 report("");
3532         }
3535 static void
3536 open_external_viewer(const char *argv[], const char *dir)
3538         def_prog_mode();           /* save current tty modes */
3539         endwin();                  /* restore original tty modes */
3540         io_run_fg(argv, dir);
3541         fprintf(stderr, "Press Enter to continue");
3542         getc(opt_tty);
3543         reset_prog_mode();
3544         redraw_display(TRUE);
3547 static void
3548 open_mergetool(const char *file)
3550         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3552         open_external_viewer(mergetool_argv, opt_cdup);
3555 static void
3556 open_editor(const char *file)
3558         const char *editor_argv[] = { "vi", file, NULL };
3559         const char *editor;
3561         editor = getenv("GIT_EDITOR");
3562         if (!editor && *opt_editor)
3563                 editor = opt_editor;
3564         if (!editor)
3565                 editor = getenv("VISUAL");
3566         if (!editor)
3567                 editor = getenv("EDITOR");
3568         if (!editor)
3569                 editor = "vi";
3571         editor_argv[0] = editor;
3572         open_external_viewer(editor_argv, opt_cdup);
3575 static void
3576 open_run_request(enum request request)
3578         struct run_request *req = get_run_request(request);
3579         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3581         if (!req) {
3582                 report("Unknown run request");
3583                 return;
3584         }
3586         if (format_argv(argv, req->argv, FORMAT_ALL))
3587                 open_external_viewer(argv, NULL);
3588         free_argv(argv);
3591 /*
3592  * User request switch noodle
3593  */
3595 static int
3596 view_driver(struct view *view, enum request request)
3598         int i;
3600         if (request == REQ_NONE)
3601                 return TRUE;
3603         if (request > REQ_NONE) {
3604                 open_run_request(request);
3605                 /* FIXME: When all views can refresh always do this. */
3606                 if (view->refresh)
3607                         request = REQ_REFRESH;
3608                 else
3609                         return TRUE;
3610         }
3612         if (view && view->lines) {
3613                 request = view->ops->request(view, request, &view->line[view->lineno]);
3614                 if (request == REQ_NONE)
3615                         return TRUE;
3616         }
3618         switch (request) {
3619         case REQ_MOVE_UP:
3620         case REQ_MOVE_DOWN:
3621         case REQ_MOVE_PAGE_UP:
3622         case REQ_MOVE_PAGE_DOWN:
3623         case REQ_MOVE_FIRST_LINE:
3624         case REQ_MOVE_LAST_LINE:
3625                 move_view(view, request);
3626                 break;
3628         case REQ_SCROLL_LEFT:
3629         case REQ_SCROLL_RIGHT:
3630         case REQ_SCROLL_LINE_DOWN:
3631         case REQ_SCROLL_LINE_UP:
3632         case REQ_SCROLL_PAGE_DOWN:
3633         case REQ_SCROLL_PAGE_UP:
3634                 scroll_view(view, request);
3635                 break;
3637         case REQ_VIEW_BLAME:
3638                 if (!opt_file[0]) {
3639                         report("No file chosen, press %s to open tree view",
3640                                get_key(view->keymap, REQ_VIEW_TREE));
3641                         break;
3642                 }
3643                 open_view(view, request, OPEN_DEFAULT);
3644                 break;
3646         case REQ_VIEW_BLOB:
3647                 if (!ref_blob[0]) {
3648                         report("No file chosen, press %s to open tree view",
3649                                get_key(view->keymap, REQ_VIEW_TREE));
3650                         break;
3651                 }
3652                 open_view(view, request, OPEN_DEFAULT);
3653                 break;
3655         case REQ_VIEW_PAGER:
3656                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3657                         report("No pager content, press %s to run command from prompt",
3658                                get_key(view->keymap, REQ_PROMPT));
3659                         break;
3660                 }
3661                 open_view(view, request, OPEN_DEFAULT);
3662                 break;
3664         case REQ_VIEW_STAGE:
3665                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3666                         report("No stage content, press %s to open the status view and choose file",
3667                                get_key(view->keymap, REQ_VIEW_STATUS));
3668                         break;
3669                 }
3670                 open_view(view, request, OPEN_DEFAULT);
3671                 break;
3673         case REQ_VIEW_STATUS:
3674                 if (opt_is_inside_work_tree == FALSE) {
3675                         report("The status view requires a working tree");
3676                         break;
3677                 }
3678                 open_view(view, request, OPEN_DEFAULT);
3679                 break;
3681         case REQ_VIEW_MAIN:
3682         case REQ_VIEW_DIFF:
3683         case REQ_VIEW_LOG:
3684         case REQ_VIEW_TREE:
3685         case REQ_VIEW_HELP:
3686         case REQ_VIEW_BRANCH:
3687                 open_view(view, request, OPEN_DEFAULT);
3688                 break;
3690         case REQ_NEXT:
3691         case REQ_PREVIOUS:
3692                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3694                 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3695                     view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3696                     view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3697                     view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3698                     view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3699                         int line;
3701                         view = view->parent;
3702                         line = view->lineno;
3703                         move_view(view, request);
3704                         if (view_is_displayed(view))
3705                                 update_view_title(view);
3706                         if (line != view->lineno)
3707                                 view->ops->request(view, REQ_ENTER,
3708                                                    &view->line[view->lineno]);
3710                 } else {
3711                         move_view(view, request);
3712                 }
3713                 break;
3715         case REQ_VIEW_NEXT:
3716         {
3717                 int nviews = displayed_views();
3718                 int next_view = (current_view + 1) % nviews;
3720                 if (next_view == current_view) {
3721                         report("Only one view is displayed");
3722                         break;
3723                 }
3725                 current_view = next_view;
3726                 /* Blur out the title of the previous view. */
3727                 update_view_title(view);
3728                 report("");
3729                 break;
3730         }
3731         case REQ_REFRESH:
3732                 report("Refreshing is not yet supported for the %s view", view->name);
3733                 break;
3735         case REQ_MAXIMIZE:
3736                 if (displayed_views() == 2)
3737                         maximize_view(view);
3738                 break;
3740         case REQ_OPTIONS:
3741                 open_option_menu();
3742                 break;
3744         case REQ_TOGGLE_LINENO:
3745                 toggle_view_option(&opt_line_number, "line numbers");
3746                 break;
3748         case REQ_TOGGLE_DATE:
3749                 toggle_date();
3750                 break;
3752         case REQ_TOGGLE_AUTHOR:
3753                 toggle_author();
3754                 break;
3756         case REQ_TOGGLE_REV_GRAPH:
3757                 toggle_view_option(&opt_rev_graph, "revision graph display");
3758                 break;
3760         case REQ_TOGGLE_REFS:
3761                 toggle_view_option(&opt_show_refs, "reference display");
3762                 break;
3764         case REQ_TOGGLE_SORT_FIELD:
3765         case REQ_TOGGLE_SORT_ORDER:
3766                 report("Sorting is not yet supported for the %s view", view->name);
3767                 break;
3769         case REQ_SEARCH:
3770         case REQ_SEARCH_BACK:
3771                 search_view(view, request);
3772                 break;
3774         case REQ_FIND_NEXT:
3775         case REQ_FIND_PREV:
3776                 find_next(view, request);
3777                 break;
3779         case REQ_STOP_LOADING:
3780                 foreach_view(view, i) {
3781                         if (view->pipe)
3782                                 report("Stopped loading the %s view", view->name),
3783                         end_update(view, TRUE);
3784                 }
3785                 break;
3787         case REQ_SHOW_VERSION:
3788                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3789                 return TRUE;
3791         case REQ_SCREEN_REDRAW:
3792                 redraw_display(TRUE);
3793                 break;
3795         case REQ_EDIT:
3796                 report("Nothing to edit");
3797                 break;
3799         case REQ_ENTER:
3800                 report("Nothing to enter");
3801                 break;
3803         case REQ_VIEW_CLOSE:
3804                 /* XXX: Mark closed views by letting view->parent point to the
3805                  * view itself. Parents to closed view should never be
3806                  * followed. */
3807                 if (view->parent &&
3808                     view->parent->parent != view->parent) {
3809                         maximize_view(view->parent);
3810                         view->parent = view;
3811                         break;
3812                 }
3813                 /* Fall-through */
3814         case REQ_QUIT:
3815                 return FALSE;
3817         default:
3818                 report("Unknown key, press %s for help",
3819                        get_key(view->keymap, REQ_VIEW_HELP));
3820                 return TRUE;
3821         }
3823         return TRUE;
3827 /*
3828  * View backend utilities
3829  */
3831 enum sort_field {
3832         ORDERBY_NAME,
3833         ORDERBY_DATE,
3834         ORDERBY_AUTHOR,
3835 };
3837 struct sort_state {
3838         const enum sort_field *fields;
3839         size_t size, current;
3840         bool reverse;
3841 };
3843 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3844 #define get_sort_field(state) ((state).fields[(state).current])
3845 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3847 static void
3848 sort_view(struct view *view, enum request request, struct sort_state *state,
3849           int (*compare)(const void *, const void *))
3851         switch (request) {
3852         case REQ_TOGGLE_SORT_FIELD:
3853                 state->current = (state->current + 1) % state->size;
3854                 break;
3856         case REQ_TOGGLE_SORT_ORDER:
3857                 state->reverse = !state->reverse;
3858                 break;
3859         default:
3860                 die("Not a sort request");
3861         }
3863         qsort(view->line, view->lines, sizeof(*view->line), compare);
3864         redraw_view(view);
3867 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3869 /* Small author cache to reduce memory consumption. It uses binary
3870  * search to lookup or find place to position new entries. No entries
3871  * are ever freed. */
3872 static const char *
3873 get_author(const char *name)
3875         static const char **authors;
3876         static size_t authors_size;
3877         int from = 0, to = authors_size - 1;
3879         while (from <= to) {
3880                 size_t pos = (to + from) / 2;
3881                 int cmp = strcmp(name, authors[pos]);
3883                 if (!cmp)
3884                         return authors[pos];
3886                 if (cmp < 0)
3887                         to = pos - 1;
3888                 else
3889                         from = pos + 1;
3890         }
3892         if (!realloc_authors(&authors, authors_size, 1))
3893                 return NULL;
3894         name = strdup(name);
3895         if (!name)
3896                 return NULL;
3898         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3899         authors[from] = name;
3900         authors_size++;
3902         return name;
3905 static void
3906 parse_timesec(struct time *time, const char *sec)
3908         time->sec = (time_t) atol(sec);
3911 static void
3912 parse_timezone(struct time *time, const char *zone)
3914         long tz;
3916         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3917         tz += ('0' - zone[2]) * 60 * 60;
3918         tz += ('0' - zone[3]) * 60 * 10;
3919         tz += ('0' - zone[4]) * 60;
3921         if (zone[0] == '-')
3922                 tz = -tz;
3924         time->tz = tz;
3925         time->sec -= tz;
3928 /* Parse author lines where the name may be empty:
3929  *      author  <email@address.tld> 1138474660 +0100
3930  */
3931 static void
3932 parse_author_line(char *ident, const char **author, struct time *time)
3934         char *nameend = strchr(ident, '<');
3935         char *emailend = strchr(ident, '>');
3937         if (nameend && emailend)
3938                 *nameend = *emailend = 0;
3939         ident = chomp_string(ident);
3940         if (!*ident) {
3941                 if (nameend)
3942                         ident = chomp_string(nameend + 1);
3943                 if (!*ident)
3944                         ident = "Unknown";
3945         }
3947         *author = get_author(ident);
3949         /* Parse epoch and timezone */
3950         if (emailend && emailend[1] == ' ') {
3951                 char *secs = emailend + 2;
3952                 char *zone = strchr(secs, ' ');
3954                 parse_timesec(time, secs);
3956                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3957                         parse_timezone(time, zone + 1);
3958         }
3961 static bool
3962 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3964         char rev[SIZEOF_REV];
3965         const char *revlist_argv[] = {
3966                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3967         };
3968         struct menu_item *items;
3969         char text[SIZEOF_STR];
3970         bool ok = TRUE;
3971         int i;
3973         items = calloc(*parents + 1, sizeof(*items));
3974         if (!items)
3975                 return FALSE;
3977         for (i = 0; i < *parents; i++) {
3978                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3979                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3980                     !(items[i].text = strdup(text))) {
3981                         ok = FALSE;
3982                         break;
3983                 }
3984         }
3986         if (ok) {
3987                 *parents = 0;
3988                 ok = prompt_menu("Select parent", items, parents);
3989         }
3990         for (i = 0; items[i].text; i++)
3991                 free((char *) items[i].text);
3992         free(items);
3993         return ok;
3996 static bool
3997 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3999         char buf[SIZEOF_STR * 4];
4000         const char *revlist_argv[] = {
4001                 "git", "log", "--no-color", "-1",
4002                         "--pretty=format:%P", id, "--", path, NULL
4003         };
4004         int parents;
4006         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4007             (parents = strlen(buf) / 40) < 0) {
4008                 report("Failed to get parent information");
4009                 return FALSE;
4011         } else if (parents == 0) {
4012                 if (path)
4013                         report("Path '%s' does not exist in the parent", path);
4014                 else
4015                         report("The selected commit has no parents");
4016                 return FALSE;
4017         }
4019         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
4020                 return FALSE;
4022         string_copy_rev(rev, &buf[41 * parents]);
4023         return TRUE;
4026 /*
4027  * Pager backend
4028  */
4030 static bool
4031 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4033         char text[SIZEOF_STR];
4035         if (opt_line_number && draw_lineno(view, lineno))
4036                 return TRUE;
4038         string_expand(text, sizeof(text), line->data, opt_tab_size);
4039         draw_text(view, line->type, text, TRUE);
4040         return TRUE;
4043 static bool
4044 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4046         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4047         char ref[SIZEOF_STR];
4049         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4050                 return TRUE;
4052         /* This is the only fatal call, since it can "corrupt" the buffer. */
4053         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4054                 return FALSE;
4056         return TRUE;
4059 static void
4060 add_pager_refs(struct view *view, struct line *line)
4062         char buf[SIZEOF_STR];
4063         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4064         struct ref_list *list;
4065         size_t bufpos = 0, i;
4066         const char *sep = "Refs: ";
4067         bool is_tag = FALSE;
4069         assert(line->type == LINE_COMMIT);
4071         list = get_ref_list(commit_id);
4072         if (!list) {
4073                 if (view->type == VIEW_DIFF)
4074                         goto try_add_describe_ref;
4075                 return;
4076         }
4078         for (i = 0; i < list->size; i++) {
4079                 struct ref *ref = list->refs[i];
4080                 const char *fmt = ref->tag    ? "%s[%s]" :
4081                                   ref->remote ? "%s<%s>" : "%s%s";
4083                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4084                         return;
4085                 sep = ", ";
4086                 if (ref->tag)
4087                         is_tag = TRUE;
4088         }
4090         if (!is_tag && view->type == VIEW_DIFF) {
4091 try_add_describe_ref:
4092                 /* Add <tag>-g<commit_id> "fake" reference. */
4093                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4094                         return;
4095         }
4097         if (bufpos == 0)
4098                 return;
4100         add_line_text(view, buf, LINE_PP_REFS);
4103 static bool
4104 pager_read(struct view *view, char *data)
4106         struct line *line;
4108         if (!data)
4109                 return TRUE;
4111         line = add_line_text(view, data, get_line_type(data));
4112         if (!line)
4113                 return FALSE;
4115         if (line->type == LINE_COMMIT &&
4116             (view->type == VIEW_DIFF ||
4117              view->type == VIEW_LOG))
4118                 add_pager_refs(view, line);
4120         return TRUE;
4123 static enum request
4124 pager_request(struct view *view, enum request request, struct line *line)
4126         int split = 0;
4128         if (request != REQ_ENTER)
4129                 return request;
4131         if (line->type == LINE_COMMIT &&
4132            (view->type == VIEW_LOG ||
4133             view->type == VIEW_PAGER)) {
4134                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4135                 split = 1;
4136         }
4138         /* Always scroll the view even if it was split. That way
4139          * you can use Enter to scroll through the log view and
4140          * split open each commit diff. */
4141         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4143         /* FIXME: A minor workaround. Scrolling the view will call report("")
4144          * but if we are scrolling a non-current view this won't properly
4145          * update the view title. */
4146         if (split)
4147                 update_view_title(view);
4149         return REQ_NONE;
4152 static bool
4153 pager_grep(struct view *view, struct line *line)
4155         const char *text[] = { line->data, NULL };
4157         return grep_text(view, text);
4160 static void
4161 pager_select(struct view *view, struct line *line)
4163         if (line->type == LINE_COMMIT) {
4164                 char *text = (char *)line->data + STRING_SIZE("commit ");
4166                 if (view->type != VIEW_PAGER)
4167                         string_copy_rev(view->ref, text);
4168                 string_copy_rev(ref_commit, text);
4169         }
4172 static struct view_ops pager_ops = {
4173         "line",
4174         NULL,
4175         NULL,
4176         pager_read,
4177         pager_draw,
4178         pager_request,
4179         pager_grep,
4180         pager_select,
4181 };
4183 static const char *log_argv[SIZEOF_ARG] = {
4184         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4185 };
4187 static enum request
4188 log_request(struct view *view, enum request request, struct line *line)
4190         switch (request) {
4191         case REQ_REFRESH:
4192                 load_refs();
4193                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4194                 return REQ_NONE;
4195         default:
4196                 return pager_request(view, request, line);
4197         }
4200 static struct view_ops log_ops = {
4201         "line",
4202         log_argv,
4203         NULL,
4204         pager_read,
4205         pager_draw,
4206         log_request,
4207         pager_grep,
4208         pager_select,
4209 };
4211 static const char *diff_argv[SIZEOF_ARG] = {
4212         "git", "show", "--pretty=fuller", "--no-color", "--root",
4213                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4214 };
4216 static struct view_ops diff_ops = {
4217         "line",
4218         diff_argv,
4219         NULL,
4220         pager_read,
4221         pager_draw,
4222         pager_request,
4223         pager_grep,
4224         pager_select,
4225 };
4227 /*
4228  * Help backend
4229  */
4231 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4233 static bool
4234 help_open_keymap_title(struct view *view, enum keymap keymap)
4236         struct line *line;
4238         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4239                                help_keymap_hidden[keymap] ? '+' : '-',
4240                                enum_name(keymap_table[keymap]));
4241         if (line)
4242                 line->other = keymap;
4244         return help_keymap_hidden[keymap];
4247 static void
4248 help_open_keymap(struct view *view, enum keymap keymap)
4250         const char *group = NULL;
4251         char buf[SIZEOF_STR];
4252         size_t bufpos;
4253         bool add_title = TRUE;
4254         int i;
4256         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4257                 const char *key = NULL;
4259                 if (req_info[i].request == REQ_NONE)
4260                         continue;
4262                 if (!req_info[i].request) {
4263                         group = req_info[i].help;
4264                         continue;
4265                 }
4267                 key = get_keys(keymap, req_info[i].request, TRUE);
4268                 if (!key || !*key)
4269                         continue;
4271                 if (add_title && help_open_keymap_title(view, keymap))
4272                         return;
4273                 add_title = FALSE;
4275                 if (group) {
4276                         add_line_text(view, group, LINE_HELP_GROUP);
4277                         group = NULL;
4278                 }
4280                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4281                                 enum_name(req_info[i]), req_info[i].help);
4282         }
4284         group = "External commands:";
4286         for (i = 0; i < run_requests; i++) {
4287                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4288                 const char *key;
4289                 int argc;
4291                 if (!req || req->keymap != keymap)
4292                         continue;
4294                 key = get_key_name(req->key);
4295                 if (!*key)
4296                         key = "(no key defined)";
4298                 if (add_title && help_open_keymap_title(view, keymap))
4299                         return;
4300                 if (group) {
4301                         add_line_text(view, group, LINE_HELP_GROUP);
4302                         group = NULL;
4303                 }
4305                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4306                         if (!string_format_from(buf, &bufpos, "%s%s",
4307                                                 argc ? " " : "", req->argv[argc]))
4308                                 return;
4310                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4311         }
4314 static bool
4315 help_open(struct view *view)
4317         enum keymap keymap;
4319         reset_view(view);
4320         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4321         add_line_text(view, "", LINE_DEFAULT);
4323         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4324                 help_open_keymap(view, keymap);
4326         return TRUE;
4329 static enum request
4330 help_request(struct view *view, enum request request, struct line *line)
4332         switch (request) {
4333         case REQ_ENTER:
4334                 if (line->type == LINE_HELP_KEYMAP) {
4335                         help_keymap_hidden[line->other] =
4336                                 !help_keymap_hidden[line->other];
4337                         view->p_restore = TRUE;
4338                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4339                 }
4341                 return REQ_NONE;
4342         default:
4343                 return pager_request(view, request, line);
4344         }
4347 static struct view_ops help_ops = {
4348         "line",
4349         NULL,
4350         help_open,
4351         NULL,
4352         pager_draw,
4353         help_request,
4354         pager_grep,
4355         pager_select,
4356 };
4359 /*
4360  * Tree backend
4361  */
4363 struct tree_stack_entry {
4364         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4365         unsigned long lineno;           /* Line number to restore */
4366         char *name;                     /* Position of name in opt_path */
4367 };
4369 /* The top of the path stack. */
4370 static struct tree_stack_entry *tree_stack = NULL;
4371 unsigned long tree_lineno = 0;
4373 static void
4374 pop_tree_stack_entry(void)
4376         struct tree_stack_entry *entry = tree_stack;
4378         tree_lineno = entry->lineno;
4379         entry->name[0] = 0;
4380         tree_stack = entry->prev;
4381         free(entry);
4384 static void
4385 push_tree_stack_entry(const char *name, unsigned long lineno)
4387         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4388         size_t pathlen = strlen(opt_path);
4390         if (!entry)
4391                 return;
4393         entry->prev = tree_stack;
4394         entry->name = opt_path + pathlen;
4395         tree_stack = entry;
4397         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4398                 pop_tree_stack_entry();
4399                 return;
4400         }
4402         /* Move the current line to the first tree entry. */
4403         tree_lineno = 1;
4404         entry->lineno = lineno;
4407 /* Parse output from git-ls-tree(1):
4408  *
4409  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4410  */
4412 #define SIZEOF_TREE_ATTR \
4413         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4415 #define SIZEOF_TREE_MODE \
4416         STRING_SIZE("100644 ")
4418 #define TREE_ID_OFFSET \
4419         STRING_SIZE("100644 blob ")
4421 struct tree_entry {
4422         char id[SIZEOF_REV];
4423         mode_t mode;
4424         struct time time;               /* Date from the author ident. */
4425         const char *author;             /* Author of the commit. */
4426         char name[1];
4427 };
4429 static const char *
4430 tree_path(const struct line *line)
4432         return ((struct tree_entry *) line->data)->name;
4435 static int
4436 tree_compare_entry(const struct line *line1, const struct line *line2)
4438         if (line1->type != line2->type)
4439                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4440         return strcmp(tree_path(line1), tree_path(line2));
4443 static const enum sort_field tree_sort_fields[] = {
4444         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4445 };
4446 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4448 static int
4449 tree_compare(const void *l1, const void *l2)
4451         const struct line *line1 = (const struct line *) l1;
4452         const struct line *line2 = (const struct line *) l2;
4453         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4454         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4456         if (line1->type == LINE_TREE_HEAD)
4457                 return -1;
4458         if (line2->type == LINE_TREE_HEAD)
4459                 return 1;
4461         switch (get_sort_field(tree_sort_state)) {
4462         case ORDERBY_DATE:
4463                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4465         case ORDERBY_AUTHOR:
4466                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4468         case ORDERBY_NAME:
4469         default:
4470                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4471         }
4475 static struct line *
4476 tree_entry(struct view *view, enum line_type type, const char *path,
4477            const char *mode, const char *id)
4479         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4480         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4482         if (!entry || !line) {
4483                 free(entry);
4484                 return NULL;
4485         }
4487         strncpy(entry->name, path, strlen(path));
4488         if (mode)
4489                 entry->mode = strtoul(mode, NULL, 8);
4490         if (id)
4491                 string_copy_rev(entry->id, id);
4493         return line;
4496 static bool
4497 tree_read_date(struct view *view, char *text, bool *read_date)
4499         static const char *author_name;
4500         static struct time author_time;
4502         if (!text && *read_date) {
4503                 *read_date = FALSE;
4504                 return TRUE;
4506         } else if (!text) {
4507                 char *path = *opt_path ? opt_path : ".";
4508                 /* Find next entry to process */
4509                 const char *log_file[] = {
4510                         "git", "log", "--no-color", "--pretty=raw",
4511                                 "--cc", "--raw", view->id, "--", path, NULL
4512                 };
4513                 struct io io = {};
4515                 if (!view->lines) {
4516                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4517                         report("Tree is empty");
4518                         return TRUE;
4519                 }
4521                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4522                         report("Failed to load tree data");
4523                         return TRUE;
4524                 }
4526                 io_done(view->pipe);
4527                 view->io = io;
4528                 *read_date = TRUE;
4529                 return FALSE;
4531         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4532                 parse_author_line(text + STRING_SIZE("author "),
4533                                   &author_name, &author_time);
4535         } else if (*text == ':') {
4536                 char *pos;
4537                 size_t annotated = 1;
4538                 size_t i;
4540                 pos = strchr(text, '\t');
4541                 if (!pos)
4542                         return TRUE;
4543                 text = pos + 1;
4544                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4545                         text += strlen(opt_path);
4546                 pos = strchr(text, '/');
4547                 if (pos)
4548                         *pos = 0;
4550                 for (i = 1; i < view->lines; i++) {
4551                         struct line *line = &view->line[i];
4552                         struct tree_entry *entry = line->data;
4554                         annotated += !!entry->author;
4555                         if (entry->author || strcmp(entry->name, text))
4556                                 continue;
4558                         entry->author = author_name;
4559                         entry->time = author_time;
4560                         line->dirty = 1;
4561                         break;
4562                 }
4564                 if (annotated == view->lines)
4565                         io_kill(view->pipe);
4566         }
4567         return TRUE;
4570 static bool
4571 tree_read(struct view *view, char *text)
4573         static bool read_date = FALSE;
4574         struct tree_entry *data;
4575         struct line *entry, *line;
4576         enum line_type type;
4577         size_t textlen = text ? strlen(text) : 0;
4578         char *path = text + SIZEOF_TREE_ATTR;
4580         if (read_date || !text)
4581                 return tree_read_date(view, text, &read_date);
4583         if (textlen <= SIZEOF_TREE_ATTR)
4584                 return FALSE;
4585         if (view->lines == 0 &&
4586             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4587                 return FALSE;
4589         /* Strip the path part ... */
4590         if (*opt_path) {
4591                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4592                 size_t striplen = strlen(opt_path);
4594                 if (pathlen > striplen)
4595                         memmove(path, path + striplen,
4596                                 pathlen - striplen + 1);
4598                 /* Insert "link" to parent directory. */
4599                 if (view->lines == 1 &&
4600                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4601                         return FALSE;
4602         }
4604         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4605         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4606         if (!entry)
4607                 return FALSE;
4608         data = entry->data;
4610         /* Skip "Directory ..." and ".." line. */
4611         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4612                 if (tree_compare_entry(line, entry) <= 0)
4613                         continue;
4615                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4617                 line->data = data;
4618                 line->type = type;
4619                 for (; line <= entry; line++)
4620                         line->dirty = line->cleareol = 1;
4621                 return TRUE;
4622         }
4624         if (tree_lineno > view->lineno) {
4625                 view->lineno = tree_lineno;
4626                 tree_lineno = 0;
4627         }
4629         return TRUE;
4632 static bool
4633 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4635         struct tree_entry *entry = line->data;
4637         if (line->type == LINE_TREE_HEAD) {
4638                 if (draw_text(view, line->type, "Directory path /", TRUE))
4639                         return TRUE;
4640         } else {
4641                 if (draw_mode(view, entry->mode))
4642                         return TRUE;
4644                 if (opt_author && draw_author(view, entry->author))
4645                         return TRUE;
4647                 if (opt_date && draw_date(view, &entry->time))
4648                         return TRUE;
4649         }
4650         if (draw_text(view, line->type, entry->name, TRUE))
4651                 return TRUE;
4652         return TRUE;
4655 static void
4656 open_blob_editor()
4658         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4659         int fd = mkstemp(file);
4661         if (fd == -1)
4662                 report("Failed to create temporary file");
4663         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4664                 report("Failed to save blob data to file");
4665         else
4666                 open_editor(file);
4667         if (fd != -1)
4668                 unlink(file);
4671 static enum request
4672 tree_request(struct view *view, enum request request, struct line *line)
4674         enum open_flags flags;
4676         switch (request) {
4677         case REQ_VIEW_BLAME:
4678                 if (line->type != LINE_TREE_FILE) {
4679                         report("Blame only supported for files");
4680                         return REQ_NONE;
4681                 }
4683                 string_copy(opt_ref, view->vid);
4684                 return request;
4686         case REQ_EDIT:
4687                 if (line->type != LINE_TREE_FILE) {
4688                         report("Edit only supported for files");
4689                 } else if (!is_head_commit(view->vid)) {
4690                         open_blob_editor();
4691                 } else {
4692                         open_editor(opt_file);
4693                 }
4694                 return REQ_NONE;
4696         case REQ_TOGGLE_SORT_FIELD:
4697         case REQ_TOGGLE_SORT_ORDER:
4698                 sort_view(view, request, &tree_sort_state, tree_compare);
4699                 return REQ_NONE;
4701         case REQ_PARENT:
4702                 if (!*opt_path) {
4703                         /* quit view if at top of tree */
4704                         return REQ_VIEW_CLOSE;
4705                 }
4706                 /* fake 'cd  ..' */
4707                 line = &view->line[1];
4708                 break;
4710         case REQ_ENTER:
4711                 break;
4713         default:
4714                 return request;
4715         }
4717         /* Cleanup the stack if the tree view is at a different tree. */
4718         while (!*opt_path && tree_stack)
4719                 pop_tree_stack_entry();
4721         switch (line->type) {
4722         case LINE_TREE_DIR:
4723                 /* Depending on whether it is a subdirectory or parent link
4724                  * mangle the path buffer. */
4725                 if (line == &view->line[1] && *opt_path) {
4726                         pop_tree_stack_entry();
4728                 } else {
4729                         const char *basename = tree_path(line);
4731                         push_tree_stack_entry(basename, view->lineno);
4732                 }
4734                 /* Trees and subtrees share the same ID, so they are not not
4735                  * unique like blobs. */
4736                 flags = OPEN_RELOAD;
4737                 request = REQ_VIEW_TREE;
4738                 break;
4740         case LINE_TREE_FILE:
4741                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4742                 request = REQ_VIEW_BLOB;
4743                 break;
4745         default:
4746                 return REQ_NONE;
4747         }
4749         open_view(view, request, flags);
4750         if (request == REQ_VIEW_TREE)
4751                 view->lineno = tree_lineno;
4753         return REQ_NONE;
4756 static bool
4757 tree_grep(struct view *view, struct line *line)
4759         struct tree_entry *entry = line->data;
4760         const char *text[] = {
4761                 entry->name,
4762                 opt_author ? entry->author : "",
4763                 mkdate(&entry->time, opt_date),
4764                 NULL
4765         };
4767         return grep_text(view, text);
4770 static void
4771 tree_select(struct view *view, struct line *line)
4773         struct tree_entry *entry = line->data;
4775         if (line->type == LINE_TREE_FILE) {
4776                 string_copy_rev(ref_blob, entry->id);
4777                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4779         } else if (line->type != LINE_TREE_DIR) {
4780                 return;
4781         }
4783         string_copy_rev(view->ref, entry->id);
4786 static bool
4787 tree_prepare(struct view *view)
4789         if (view->lines == 0 && opt_prefix[0]) {
4790                 char *pos = opt_prefix;
4792                 while (pos && *pos) {
4793                         char *end = strchr(pos, '/');
4795                         if (end)
4796                                 *end = 0;
4797                         push_tree_stack_entry(pos, 0);
4798                         pos = end;
4799                         if (end) {
4800                                 *end = '/';
4801                                 pos++;
4802                         }
4803                 }
4805         } else if (strcmp(view->vid, view->id)) {
4806                 opt_path[0] = 0;
4807         }
4809         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4812 static const char *tree_argv[SIZEOF_ARG] = {
4813         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4814 };
4816 static struct view_ops tree_ops = {
4817         "file",
4818         tree_argv,
4819         NULL,
4820         tree_read,
4821         tree_draw,
4822         tree_request,
4823         tree_grep,
4824         tree_select,
4825         tree_prepare,
4826 };
4828 static bool
4829 blob_read(struct view *view, char *line)
4831         if (!line)
4832                 return TRUE;
4833         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4836 static enum request
4837 blob_request(struct view *view, enum request request, struct line *line)
4839         switch (request) {
4840         case REQ_EDIT:
4841                 open_blob_editor();
4842                 return REQ_NONE;
4843         default:
4844                 return pager_request(view, request, line);
4845         }
4848 static const char *blob_argv[SIZEOF_ARG] = {
4849         "git", "cat-file", "blob", "%(blob)", NULL
4850 };
4852 static struct view_ops blob_ops = {
4853         "line",
4854         blob_argv,
4855         NULL,
4856         blob_read,
4857         pager_draw,
4858         blob_request,
4859         pager_grep,
4860         pager_select,
4861 };
4863 /*
4864  * Blame backend
4865  *
4866  * Loading the blame view is a two phase job:
4867  *
4868  *  1. File content is read either using opt_file from the
4869  *     filesystem or using git-cat-file.
4870  *  2. Then blame information is incrementally added by
4871  *     reading output from git-blame.
4872  */
4874 static const char *blame_head_argv[] = {
4875         "git", "blame", "--incremental", "--", "%(file)", NULL
4876 };
4878 static const char *blame_ref_argv[] = {
4879         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4880 };
4882 static const char *blame_cat_file_argv[] = {
4883         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4884 };
4886 struct blame_commit {
4887         char id[SIZEOF_REV];            /* SHA1 ID. */
4888         char title[128];                /* First line of the commit message. */
4889         const char *author;             /* Author of the commit. */
4890         struct time time;               /* Date from the author ident. */
4891         char filename[128];             /* Name of file. */
4892         bool has_previous;              /* Was a "previous" line detected. */
4893 };
4895 struct blame {
4896         struct blame_commit *commit;
4897         unsigned long lineno;
4898         char text[1];
4899 };
4901 static bool
4902 blame_open(struct view *view)
4904         char path[SIZEOF_STR];
4906         if (!view->parent && *opt_prefix) {
4907                 string_copy(path, opt_file);
4908                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4909                         return FALSE;
4910         }
4912         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4913                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4914                         return FALSE;
4915         }
4917         setup_update(view, opt_file);
4918         string_format(view->ref, "%s ...", opt_file);
4920         return TRUE;
4923 static struct blame_commit *
4924 get_blame_commit(struct view *view, const char *id)
4926         size_t i;
4928         for (i = 0; i < view->lines; i++) {
4929                 struct blame *blame = view->line[i].data;
4931                 if (!blame->commit)
4932                         continue;
4934                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4935                         return blame->commit;
4936         }
4938         {
4939                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4941                 if (commit)
4942                         string_ncopy(commit->id, id, SIZEOF_REV);
4943                 return commit;
4944         }
4947 static bool
4948 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4950         const char *pos = *posref;
4952         *posref = NULL;
4953         pos = strchr(pos + 1, ' ');
4954         if (!pos || !isdigit(pos[1]))
4955                 return FALSE;
4956         *number = atoi(pos + 1);
4957         if (*number < min || *number > max)
4958                 return FALSE;
4960         *posref = pos;
4961         return TRUE;
4964 static struct blame_commit *
4965 parse_blame_commit(struct view *view, const char *text, int *blamed)
4967         struct blame_commit *commit;
4968         struct blame *blame;
4969         const char *pos = text + SIZEOF_REV - 2;
4970         size_t orig_lineno = 0;
4971         size_t lineno;
4972         size_t group;
4974         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4975                 return NULL;
4977         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4978             !parse_number(&pos, &lineno, 1, view->lines) ||
4979             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4980                 return NULL;
4982         commit = get_blame_commit(view, text);
4983         if (!commit)
4984                 return NULL;
4986         *blamed += group;
4987         while (group--) {
4988                 struct line *line = &view->line[lineno + group - 1];
4990                 blame = line->data;
4991                 blame->commit = commit;
4992                 blame->lineno = orig_lineno + group - 1;
4993                 line->dirty = 1;
4994         }
4996         return commit;
4999 static bool
5000 blame_read_file(struct view *view, const char *line, bool *read_file)
5002         if (!line) {
5003                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
5004                 struct io io = {};
5006                 if (view->lines == 0 && !view->parent)
5007                         die("No blame exist for %s", view->vid);
5009                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
5010                         report("Failed to load blame data");
5011                         return TRUE;
5012                 }
5014                 io_done(view->pipe);
5015                 view->io = io;
5016                 *read_file = FALSE;
5017                 return FALSE;
5019         } else {
5020                 size_t linelen = strlen(line);
5021                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5023                 if (!blame)
5024                         return FALSE;
5026                 blame->commit = NULL;
5027                 strncpy(blame->text, line, linelen);
5028                 blame->text[linelen] = 0;
5029                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5030         }
5033 static bool
5034 match_blame_header(const char *name, char **line)
5036         size_t namelen = strlen(name);
5037         bool matched = !strncmp(name, *line, namelen);
5039         if (matched)
5040                 *line += namelen;
5042         return matched;
5045 static bool
5046 blame_read(struct view *view, char *line)
5048         static struct blame_commit *commit = NULL;
5049         static int blamed = 0;
5050         static bool read_file = TRUE;
5052         if (read_file)
5053                 return blame_read_file(view, line, &read_file);
5055         if (!line) {
5056                 /* Reset all! */
5057                 commit = NULL;
5058                 blamed = 0;
5059                 read_file = TRUE;
5060                 string_format(view->ref, "%s", view->vid);
5061                 if (view_is_displayed(view)) {
5062                         update_view_title(view);
5063                         redraw_view_from(view, 0);
5064                 }
5065                 return TRUE;
5066         }
5068         if (!commit) {
5069                 commit = parse_blame_commit(view, line, &blamed);
5070                 string_format(view->ref, "%s %2d%%", view->vid,
5071                               view->lines ? blamed * 100 / view->lines : 0);
5073         } else if (match_blame_header("author ", &line)) {
5074                 commit->author = get_author(line);
5076         } else if (match_blame_header("author-time ", &line)) {
5077                 parse_timesec(&commit->time, line);
5079         } else if (match_blame_header("author-tz ", &line)) {
5080                 parse_timezone(&commit->time, line);
5082         } else if (match_blame_header("summary ", &line)) {
5083                 string_ncopy(commit->title, line, strlen(line));
5085         } else if (match_blame_header("previous ", &line)) {
5086                 commit->has_previous = TRUE;
5088         } else if (match_blame_header("filename ", &line)) {
5089                 string_ncopy(commit->filename, line, strlen(line));
5090                 commit = NULL;
5091         }
5093         return TRUE;
5096 static bool
5097 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5099         struct blame *blame = line->data;
5100         struct time *time = NULL;
5101         const char *id = NULL, *author = NULL;
5102         char text[SIZEOF_STR];
5104         if (blame->commit && *blame->commit->filename) {
5105                 id = blame->commit->id;
5106                 author = blame->commit->author;
5107                 time = &blame->commit->time;
5108         }
5110         if (opt_date && draw_date(view, time))
5111                 return TRUE;
5113         if (opt_author && draw_author(view, author))
5114                 return TRUE;
5116         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5117                 return TRUE;
5119         if (draw_lineno(view, lineno))
5120                 return TRUE;
5122         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5123         draw_text(view, LINE_DEFAULT, text, TRUE);
5124         return TRUE;
5127 static bool
5128 check_blame_commit(struct blame *blame, bool check_null_id)
5130         if (!blame->commit)
5131                 report("Commit data not loaded yet");
5132         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5133                 report("No commit exist for the selected line");
5134         else
5135                 return TRUE;
5136         return FALSE;
5139 static void
5140 setup_blame_parent_line(struct view *view, struct blame *blame)
5142         const char *diff_tree_argv[] = {
5143                 "git", "diff-tree", "-U0", blame->commit->id,
5144                         "--", blame->commit->filename, NULL
5145         };
5146         struct io io = {};
5147         int parent_lineno = -1;
5148         int blamed_lineno = -1;
5149         char *line;
5151         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5152                 return;
5154         while ((line = io_get(&io, '\n', TRUE))) {
5155                 if (*line == '@') {
5156                         char *pos = strchr(line, '+');
5158                         parent_lineno = atoi(line + 4);
5159                         if (pos)
5160                                 blamed_lineno = atoi(pos + 1);
5162                 } else if (*line == '+' && parent_lineno != -1) {
5163                         if (blame->lineno == blamed_lineno - 1 &&
5164                             !strcmp(blame->text, line + 1)) {
5165                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5166                                 break;
5167                         }
5168                         blamed_lineno++;
5169                 }
5170         }
5172         io_done(&io);
5175 static enum request
5176 blame_request(struct view *view, enum request request, struct line *line)
5178         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5179         struct blame *blame = line->data;
5181         switch (request) {
5182         case REQ_VIEW_BLAME:
5183                 if (check_blame_commit(blame, TRUE)) {
5184                         string_copy(opt_ref, blame->commit->id);
5185                         string_copy(opt_file, blame->commit->filename);
5186                         if (blame->lineno)
5187                                 view->lineno = blame->lineno;
5188                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5189                 }
5190                 break;
5192         case REQ_PARENT:
5193                 if (check_blame_commit(blame, TRUE) &&
5194                     select_commit_parent(blame->commit->id, opt_ref,
5195                                          blame->commit->filename)) {
5196                         string_copy(opt_file, blame->commit->filename);
5197                         setup_blame_parent_line(view, blame);
5198                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5199                 }
5200                 break;
5202         case REQ_ENTER:
5203                 if (!check_blame_commit(blame, FALSE))
5204                         break;
5206                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5207                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5208                         break;
5210                 if (!strcmp(blame->commit->id, NULL_ID)) {
5211                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5212                         const char *diff_index_argv[] = {
5213                                 "git", "diff-index", "--root", "--patch-with-stat",
5214                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5215                         };
5217                         if (!blame->commit->has_previous) {
5218                                 diff_index_argv[1] = "diff";
5219                                 diff_index_argv[2] = "--no-color";
5220                                 diff_index_argv[6] = "--";
5221                                 diff_index_argv[7] = "/dev/null";
5222                         }
5224                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5225                                 report("Failed to allocate diff command");
5226                                 break;
5227                         }
5228                         flags |= OPEN_PREPARED;
5229                 }
5231                 open_view(view, REQ_VIEW_DIFF, flags);
5232                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5233                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5234                 break;
5236         default:
5237                 return request;
5238         }
5240         return REQ_NONE;
5243 static bool
5244 blame_grep(struct view *view, struct line *line)
5246         struct blame *blame = line->data;
5247         struct blame_commit *commit = blame->commit;
5248         const char *text[] = {
5249                 blame->text,
5250                 commit ? commit->title : "",
5251                 commit ? commit->id : "",
5252                 commit && opt_author ? commit->author : "",
5253                 commit ? mkdate(&commit->time, opt_date) : "",
5254                 NULL
5255         };
5257         return grep_text(view, text);
5260 static void
5261 blame_select(struct view *view, struct line *line)
5263         struct blame *blame = line->data;
5264         struct blame_commit *commit = blame->commit;
5266         if (!commit)
5267                 return;
5269         if (!strcmp(commit->id, NULL_ID))
5270                 string_ncopy(ref_commit, "HEAD", 4);
5271         else
5272                 string_copy_rev(ref_commit, commit->id);
5275 static struct view_ops blame_ops = {
5276         "line",
5277         NULL,
5278         blame_open,
5279         blame_read,
5280         blame_draw,
5281         blame_request,
5282         blame_grep,
5283         blame_select,
5284 };
5286 /*
5287  * Branch backend
5288  */
5290 struct branch {
5291         const char *author;             /* Author of the last commit. */
5292         struct time time;               /* Date of the last activity. */
5293         const struct ref *ref;          /* Name and commit ID information. */
5294 };
5296 static const struct ref branch_all;
5298 static const enum sort_field branch_sort_fields[] = {
5299         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5300 };
5301 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5303 static int
5304 branch_compare(const void *l1, const void *l2)
5306         const struct branch *branch1 = ((const struct line *) l1)->data;
5307         const struct branch *branch2 = ((const struct line *) l2)->data;
5309         switch (get_sort_field(branch_sort_state)) {
5310         case ORDERBY_DATE:
5311                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5313         case ORDERBY_AUTHOR:
5314                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5316         case ORDERBY_NAME:
5317         default:
5318                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5319         }
5322 static bool
5323 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5325         struct branch *branch = line->data;
5326         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5328         if (opt_date && draw_date(view, &branch->time))
5329                 return TRUE;
5331         if (opt_author && draw_author(view, branch->author))
5332                 return TRUE;
5334         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5335         return TRUE;
5338 static enum request
5339 branch_request(struct view *view, enum request request, struct line *line)
5341         struct branch *branch = line->data;
5343         switch (request) {
5344         case REQ_REFRESH:
5345                 load_refs();
5346                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5347                 return REQ_NONE;
5349         case REQ_TOGGLE_SORT_FIELD:
5350         case REQ_TOGGLE_SORT_ORDER:
5351                 sort_view(view, request, &branch_sort_state, branch_compare);
5352                 return REQ_NONE;
5354         case REQ_ENTER:
5355                 if (branch->ref == &branch_all) {
5356                         const char *all_branches_argv[] = {
5357                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5358                                       "--topo-order", "--all", NULL
5359                         };
5360                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5362                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5363                                 report("Failed to load view of all branches");
5364                                 return REQ_NONE;
5365                         }
5366                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5367                 } else {
5368                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5369                 }
5370                 return REQ_NONE;
5372         default:
5373                 return request;
5374         }
5377 static bool
5378 branch_read(struct view *view, char *line)
5380         static char id[SIZEOF_REV];
5381         struct branch *reference;
5382         size_t i;
5384         if (!line)
5385                 return TRUE;
5387         switch (get_line_type(line)) {
5388         case LINE_COMMIT:
5389                 string_copy_rev(id, line + STRING_SIZE("commit "));
5390                 return TRUE;
5392         case LINE_AUTHOR:
5393                 for (i = 0, reference = NULL; i < view->lines; i++) {
5394                         struct branch *branch = view->line[i].data;
5396                         if (strcmp(branch->ref->id, id))
5397                                 continue;
5399                         view->line[i].dirty = TRUE;
5400                         if (reference) {
5401                                 branch->author = reference->author;
5402                                 branch->time = reference->time;
5403                                 continue;
5404                         }
5406                         parse_author_line(line + STRING_SIZE("author "),
5407                                           &branch->author, &branch->time);
5408                         reference = branch;
5409                 }
5410                 return TRUE;
5412         default:
5413                 return TRUE;
5414         }
5418 static bool
5419 branch_open_visitor(void *data, const struct ref *ref)
5421         struct view *view = data;
5422         struct branch *branch;
5424         if (ref->tag || ref->ltag || ref->remote)
5425                 return TRUE;
5427         branch = calloc(1, sizeof(*branch));
5428         if (!branch)
5429                 return FALSE;
5431         branch->ref = ref;
5432         return !!add_line_data(view, branch, LINE_DEFAULT);
5435 static bool
5436 branch_open(struct view *view)
5438         const char *branch_log[] = {
5439                 "git", "log", "--no-color", "--pretty=raw",
5440                         "--simplify-by-decoration", "--all", NULL
5441         };
5443         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5444                 report("Failed to load branch data");
5445                 return TRUE;
5446         }
5448         setup_update(view, view->id);
5449         branch_open_visitor(view, &branch_all);
5450         foreach_ref(branch_open_visitor, view);
5451         view->p_restore = TRUE;
5453         return TRUE;
5456 static bool
5457 branch_grep(struct view *view, struct line *line)
5459         struct branch *branch = line->data;
5460         const char *text[] = {
5461                 branch->ref->name,
5462                 branch->author,
5463                 NULL
5464         };
5466         return grep_text(view, text);
5469 static void
5470 branch_select(struct view *view, struct line *line)
5472         struct branch *branch = line->data;
5474         string_copy_rev(view->ref, branch->ref->id);
5475         string_copy_rev(ref_commit, branch->ref->id);
5476         string_copy_rev(ref_head, branch->ref->id);
5477         string_copy_rev(ref_branch, branch->ref->name);
5480 static struct view_ops branch_ops = {
5481         "branch",
5482         NULL,
5483         branch_open,
5484         branch_read,
5485         branch_draw,
5486         branch_request,
5487         branch_grep,
5488         branch_select,
5489 };
5491 /*
5492  * Status backend
5493  */
5495 struct status {
5496         char status;
5497         struct {
5498                 mode_t mode;
5499                 char rev[SIZEOF_REV];
5500                 char name[SIZEOF_STR];
5501         } old;
5502         struct {
5503                 mode_t mode;
5504                 char rev[SIZEOF_REV];
5505                 char name[SIZEOF_STR];
5506         } new;
5507 };
5509 static char status_onbranch[SIZEOF_STR];
5510 static struct status stage_status;
5511 static enum line_type stage_line_type;
5512 static size_t stage_chunks;
5513 static int *stage_chunk;
5515 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5517 /* This should work even for the "On branch" line. */
5518 static inline bool
5519 status_has_none(struct view *view, struct line *line)
5521         return line < view->line + view->lines && !line[1].data;
5524 /* Get fields from the diff line:
5525  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5526  */
5527 static inline bool
5528 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5530         const char *old_mode = buf +  1;
5531         const char *new_mode = buf +  8;
5532         const char *old_rev  = buf + 15;
5533         const char *new_rev  = buf + 56;
5534         const char *status   = buf + 97;
5536         if (bufsize < 98 ||
5537             old_mode[-1] != ':' ||
5538             new_mode[-1] != ' ' ||
5539             old_rev[-1]  != ' ' ||
5540             new_rev[-1]  != ' ' ||
5541             status[-1]   != ' ')
5542                 return FALSE;
5544         file->status = *status;
5546         string_copy_rev(file->old.rev, old_rev);
5547         string_copy_rev(file->new.rev, new_rev);
5549         file->old.mode = strtoul(old_mode, NULL, 8);
5550         file->new.mode = strtoul(new_mode, NULL, 8);
5552         file->old.name[0] = file->new.name[0] = 0;
5554         return TRUE;
5557 static bool
5558 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5560         struct status *unmerged = NULL;
5561         char *buf;
5562         struct io io = {};
5564         if (!io_run(&io, argv, opt_cdup, IO_RD))
5565                 return FALSE;
5567         add_line_data(view, NULL, type);
5569         while ((buf = io_get(&io, 0, TRUE))) {
5570                 struct status *file = unmerged;
5572                 if (!file) {
5573                         file = calloc(1, sizeof(*file));
5574                         if (!file || !add_line_data(view, file, type))
5575                                 goto error_out;
5576                 }
5578                 /* Parse diff info part. */
5579                 if (status) {
5580                         file->status = status;
5581                         if (status == 'A')
5582                                 string_copy(file->old.rev, NULL_ID);
5584                 } else if (!file->status || file == unmerged) {
5585                         if (!status_get_diff(file, buf, strlen(buf)))
5586                                 goto error_out;
5588                         buf = io_get(&io, 0, TRUE);
5589                         if (!buf)
5590                                 break;
5592                         /* Collapse all modified entries that follow an
5593                          * associated unmerged entry. */
5594                         if (unmerged == file) {
5595                                 unmerged->status = 'U';
5596                                 unmerged = NULL;
5597                         } else if (file->status == 'U') {
5598                                 unmerged = file;
5599                         }
5600                 }
5602                 /* Grab the old name for rename/copy. */
5603                 if (!*file->old.name &&
5604                     (file->status == 'R' || file->status == 'C')) {
5605                         string_ncopy(file->old.name, buf, strlen(buf));
5607                         buf = io_get(&io, 0, TRUE);
5608                         if (!buf)
5609                                 break;
5610                 }
5612                 /* git-ls-files just delivers a NUL separated list of
5613                  * file names similar to the second half of the
5614                  * git-diff-* output. */
5615                 string_ncopy(file->new.name, buf, strlen(buf));
5616                 if (!*file->old.name)
5617                         string_copy(file->old.name, file->new.name);
5618                 file = NULL;
5619         }
5621         if (io_error(&io)) {
5622 error_out:
5623                 io_done(&io);
5624                 return FALSE;
5625         }
5627         if (!view->line[view->lines - 1].data)
5628                 add_line_data(view, NULL, LINE_STAT_NONE);
5630         io_done(&io);
5631         return TRUE;
5634 /* Don't show unmerged entries in the staged section. */
5635 static const char *status_diff_index_argv[] = {
5636         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5637                              "--cached", "-M", "HEAD", NULL
5638 };
5640 static const char *status_diff_files_argv[] = {
5641         "git", "diff-files", "-z", NULL
5642 };
5644 static const char *status_list_other_argv[] = {
5645         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5646 };
5648 static const char *status_list_no_head_argv[] = {
5649         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5650 };
5652 static const char *update_index_argv[] = {
5653         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5654 };
5656 /* Restore the previous line number to stay in the context or select a
5657  * line with something that can be updated. */
5658 static void
5659 status_restore(struct view *view)
5661         if (view->p_lineno >= view->lines)
5662                 view->p_lineno = view->lines - 1;
5663         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5664                 view->p_lineno++;
5665         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5666                 view->p_lineno--;
5668         /* If the above fails, always skip the "On branch" line. */
5669         if (view->p_lineno < view->lines)
5670                 view->lineno = view->p_lineno;
5671         else
5672                 view->lineno = 1;
5674         if (view->lineno < view->offset)
5675                 view->offset = view->lineno;
5676         else if (view->offset + view->height <= view->lineno)
5677                 view->offset = view->lineno - view->height + 1;
5679         view->p_restore = FALSE;
5682 static void
5683 status_update_onbranch(void)
5685         static const char *paths[][2] = {
5686                 { "rebase-apply/rebasing",      "Rebasing" },
5687                 { "rebase-apply/applying",      "Applying mailbox" },
5688                 { "rebase-apply/",              "Rebasing mailbox" },
5689                 { "rebase-merge/interactive",   "Interactive rebase" },
5690                 { "rebase-merge/",              "Rebase merge" },
5691                 { "MERGE_HEAD",                 "Merging" },
5692                 { "BISECT_LOG",                 "Bisecting" },
5693                 { "HEAD",                       "On branch" },
5694         };
5695         char buf[SIZEOF_STR];
5696         struct stat stat;
5697         int i;
5699         if (is_initial_commit()) {
5700                 string_copy(status_onbranch, "Initial commit");
5701                 return;
5702         }
5704         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5705                 char *head = opt_head;
5707                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5708                     lstat(buf, &stat) < 0)
5709                         continue;
5711                 if (!*opt_head) {
5712                         struct io io = {};
5714                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5715                             io_read_buf(&io, buf, sizeof(buf))) {
5716                                 head = buf;
5717                                 if (!prefixcmp(head, "refs/heads/"))
5718                                         head += STRING_SIZE("refs/heads/");
5719                         }
5720                 }
5722                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5723                         string_copy(status_onbranch, opt_head);
5724                 return;
5725         }
5727         string_copy(status_onbranch, "Not currently on any branch");
5730 /* First parse staged info using git-diff-index(1), then parse unstaged
5731  * info using git-diff-files(1), and finally untracked files using
5732  * git-ls-files(1). */
5733 static bool
5734 status_open(struct view *view)
5736         reset_view(view);
5738         add_line_data(view, NULL, LINE_STAT_HEAD);
5739         status_update_onbranch();
5741         io_run_bg(update_index_argv);
5743         if (is_initial_commit()) {
5744                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5745                         return FALSE;
5746         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5747                 return FALSE;
5748         }
5750         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5751             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5752                 return FALSE;
5754         /* Restore the exact position or use the specialized restore
5755          * mode? */
5756         if (!view->p_restore)
5757                 status_restore(view);
5758         return TRUE;
5761 static bool
5762 status_draw(struct view *view, struct line *line, unsigned int lineno)
5764         struct status *status = line->data;
5765         enum line_type type;
5766         const char *text;
5768         if (!status) {
5769                 switch (line->type) {
5770                 case LINE_STAT_STAGED:
5771                         type = LINE_STAT_SECTION;
5772                         text = "Changes to be committed:";
5773                         break;
5775                 case LINE_STAT_UNSTAGED:
5776                         type = LINE_STAT_SECTION;
5777                         text = "Changed but not updated:";
5778                         break;
5780                 case LINE_STAT_UNTRACKED:
5781                         type = LINE_STAT_SECTION;
5782                         text = "Untracked files:";
5783                         break;
5785                 case LINE_STAT_NONE:
5786                         type = LINE_DEFAULT;
5787                         text = "  (no files)";
5788                         break;
5790                 case LINE_STAT_HEAD:
5791                         type = LINE_STAT_HEAD;
5792                         text = status_onbranch;
5793                         break;
5795                 default:
5796                         return FALSE;
5797                 }
5798         } else {
5799                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5801                 buf[0] = status->status;
5802                 if (draw_text(view, line->type, buf, TRUE))
5803                         return TRUE;
5804                 type = LINE_DEFAULT;
5805                 text = status->new.name;
5806         }
5808         draw_text(view, type, text, TRUE);
5809         return TRUE;
5812 static enum request
5813 status_load_error(struct view *view, struct view *stage, const char *path)
5815         if (displayed_views() == 2 || display[current_view] != view)
5816                 maximize_view(view);
5817         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5818         return REQ_NONE;
5821 static enum request
5822 status_enter(struct view *view, struct line *line)
5824         struct status *status = line->data;
5825         const char *oldpath = status ? status->old.name : NULL;
5826         /* Diffs for unmerged entries are empty when passing the new
5827          * path, so leave it empty. */
5828         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5829         const char *info;
5830         enum open_flags split;
5831         struct view *stage = VIEW(REQ_VIEW_STAGE);
5833         if (line->type == LINE_STAT_NONE ||
5834             (!status && line[1].type == LINE_STAT_NONE)) {
5835                 report("No file to diff");
5836                 return REQ_NONE;
5837         }
5839         switch (line->type) {
5840         case LINE_STAT_STAGED:
5841                 if (is_initial_commit()) {
5842                         const char *no_head_diff_argv[] = {
5843                                 "git", "diff", "--no-color", "--patch-with-stat",
5844                                         "--", "/dev/null", newpath, NULL
5845                         };
5847                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5848                                 return status_load_error(view, stage, newpath);
5849                 } else {
5850                         const char *index_show_argv[] = {
5851                                 "git", "diff-index", "--root", "--patch-with-stat",
5852                                         "-C", "-M", "--cached", "HEAD", "--",
5853                                         oldpath, newpath, NULL
5854                         };
5856                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5857                                 return status_load_error(view, stage, newpath);
5858                 }
5860                 if (status)
5861                         info = "Staged changes to %s";
5862                 else
5863                         info = "Staged changes";
5864                 break;
5866         case LINE_STAT_UNSTAGED:
5867         {
5868                 const char *files_show_argv[] = {
5869                         "git", "diff-files", "--root", "--patch-with-stat",
5870                                 "-C", "-M", "--", oldpath, newpath, NULL
5871                 };
5873                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5874                         return status_load_error(view, stage, newpath);
5875                 if (status)
5876                         info = "Unstaged changes to %s";
5877                 else
5878                         info = "Unstaged changes";
5879                 break;
5880         }
5881         case LINE_STAT_UNTRACKED:
5882                 if (!newpath) {
5883                         report("No file to show");
5884                         return REQ_NONE;
5885                 }
5887                 if (!suffixcmp(status->new.name, -1, "/")) {
5888                         report("Cannot display a directory");
5889                         return REQ_NONE;
5890                 }
5892                 if (!prepare_update_file(stage, newpath))
5893                         return status_load_error(view, stage, newpath);
5894                 info = "Untracked file %s";
5895                 break;
5897         case LINE_STAT_HEAD:
5898                 return REQ_NONE;
5900         default:
5901                 die("line type %d not handled in switch", line->type);
5902         }
5904         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5905         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5906         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5907                 if (status) {
5908                         stage_status = *status;
5909                 } else {
5910                         memset(&stage_status, 0, sizeof(stage_status));
5911                 }
5913                 stage_line_type = line->type;
5914                 stage_chunks = 0;
5915                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5916         }
5918         return REQ_NONE;
5921 static bool
5922 status_exists(struct status *status, enum line_type type)
5924         struct view *view = VIEW(REQ_VIEW_STATUS);
5925         unsigned long lineno;
5927         for (lineno = 0; lineno < view->lines; lineno++) {
5928                 struct line *line = &view->line[lineno];
5929                 struct status *pos = line->data;
5931                 if (line->type != type)
5932                         continue;
5933                 if (!pos && (!status || !status->status) && line[1].data) {
5934                         select_view_line(view, lineno);
5935                         return TRUE;
5936                 }
5937                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5938                         select_view_line(view, lineno);
5939                         return TRUE;
5940                 }
5941         }
5943         return FALSE;
5947 static bool
5948 status_update_prepare(struct io *io, enum line_type type)
5950         const char *staged_argv[] = {
5951                 "git", "update-index", "-z", "--index-info", NULL
5952         };
5953         const char *others_argv[] = {
5954                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5955         };
5957         switch (type) {
5958         case LINE_STAT_STAGED:
5959                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5961         case LINE_STAT_UNSTAGED:
5962         case LINE_STAT_UNTRACKED:
5963                 return io_run(io, others_argv, opt_cdup, IO_WR);
5965         default:
5966                 die("line type %d not handled in switch", type);
5967                 return FALSE;
5968         }
5971 static bool
5972 status_update_write(struct io *io, struct status *status, enum line_type type)
5974         char buf[SIZEOF_STR];
5975         size_t bufsize = 0;
5977         switch (type) {
5978         case LINE_STAT_STAGED:
5979                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5980                                         status->old.mode,
5981                                         status->old.rev,
5982                                         status->old.name, 0))
5983                         return FALSE;
5984                 break;
5986         case LINE_STAT_UNSTAGED:
5987         case LINE_STAT_UNTRACKED:
5988                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5989                         return FALSE;
5990                 break;
5992         default:
5993                 die("line type %d not handled in switch", type);
5994         }
5996         return io_write(io, buf, bufsize);
5999 static bool
6000 status_update_file(struct status *status, enum line_type type)
6002         struct io io = {};
6003         bool result;
6005         if (!status_update_prepare(&io, type))
6006                 return FALSE;
6008         result = status_update_write(&io, status, type);
6009         return io_done(&io) && result;
6012 static bool
6013 status_update_files(struct view *view, struct line *line)
6015         char buf[sizeof(view->ref)];
6016         struct io io = {};
6017         bool result = TRUE;
6018         struct line *pos = view->line + view->lines;
6019         int files = 0;
6020         int file, done;
6021         int cursor_y = -1, cursor_x = -1;
6023         if (!status_update_prepare(&io, line->type))
6024                 return FALSE;
6026         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6027                 files++;
6029         string_copy(buf, view->ref);
6030         getsyx(cursor_y, cursor_x);
6031         for (file = 0, done = 5; result && file < files; line++, file++) {
6032                 int almost_done = file * 100 / files;
6034                 if (almost_done > done) {
6035                         done = almost_done;
6036                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6037                                       file, files, done);
6038                         update_view_title(view);
6039                         setsyx(cursor_y, cursor_x);
6040                         doupdate();
6041                 }
6042                 result = status_update_write(&io, line->data, line->type);
6043         }
6044         string_copy(view->ref, buf);
6046         return io_done(&io) && result;
6049 static bool
6050 status_update(struct view *view)
6052         struct line *line = &view->line[view->lineno];
6054         assert(view->lines);
6056         if (!line->data) {
6057                 /* This should work even for the "On branch" line. */
6058                 if (line < view->line + view->lines && !line[1].data) {
6059                         report("Nothing to update");
6060                         return FALSE;
6061                 }
6063                 if (!status_update_files(view, line + 1)) {
6064                         report("Failed to update file status");
6065                         return FALSE;
6066                 }
6068         } else if (!status_update_file(line->data, line->type)) {
6069                 report("Failed to update file status");
6070                 return FALSE;
6071         }
6073         return TRUE;
6076 static bool
6077 status_revert(struct status *status, enum line_type type, bool has_none)
6079         if (!status || type != LINE_STAT_UNSTAGED) {
6080                 if (type == LINE_STAT_STAGED) {
6081                         report("Cannot revert changes to staged files");
6082                 } else if (type == LINE_STAT_UNTRACKED) {
6083                         report("Cannot revert changes to untracked files");
6084                 } else if (has_none) {
6085                         report("Nothing to revert");
6086                 } else {
6087                         report("Cannot revert changes to multiple files");
6088                 }
6090         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6091                 char mode[10] = "100644";
6092                 const char *reset_argv[] = {
6093                         "git", "update-index", "--cacheinfo", mode,
6094                                 status->old.rev, status->old.name, NULL
6095                 };
6096                 const char *checkout_argv[] = {
6097                         "git", "checkout", "--", status->old.name, NULL
6098                 };
6100                 if (status->status == 'U') {
6101                         string_format(mode, "%5o", status->old.mode);
6103                         if (status->old.mode == 0 && status->new.mode == 0) {
6104                                 reset_argv[2] = "--force-remove";
6105                                 reset_argv[3] = status->old.name;
6106                                 reset_argv[4] = NULL;
6107                         }
6109                         if (!io_run_fg(reset_argv, opt_cdup))
6110                                 return FALSE;
6111                         if (status->old.mode == 0 && status->new.mode == 0)
6112                                 return TRUE;
6113                 }
6115                 return io_run_fg(checkout_argv, opt_cdup);
6116         }
6118         return FALSE;
6121 static enum request
6122 status_request(struct view *view, enum request request, struct line *line)
6124         struct status *status = line->data;
6126         switch (request) {
6127         case REQ_STATUS_UPDATE:
6128                 if (!status_update(view))
6129                         return REQ_NONE;
6130                 break;
6132         case REQ_STATUS_REVERT:
6133                 if (!status_revert(status, line->type, status_has_none(view, line)))
6134                         return REQ_NONE;
6135                 break;
6137         case REQ_STATUS_MERGE:
6138                 if (!status || status->status != 'U') {
6139                         report("Merging only possible for files with unmerged status ('U').");
6140                         return REQ_NONE;
6141                 }
6142                 open_mergetool(status->new.name);
6143                 break;
6145         case REQ_EDIT:
6146                 if (!status)
6147                         return request;
6148                 if (status->status == 'D') {
6149                         report("File has been deleted.");
6150                         return REQ_NONE;
6151                 }
6153                 open_editor(status->new.name);
6154                 break;
6156         case REQ_VIEW_BLAME:
6157                 if (status)
6158                         opt_ref[0] = 0;
6159                 return request;
6161         case REQ_ENTER:
6162                 /* After returning the status view has been split to
6163                  * show the stage view. No further reloading is
6164                  * necessary. */
6165                 return status_enter(view, line);
6167         case REQ_REFRESH:
6168                 /* Simply reload the view. */
6169                 break;
6171         default:
6172                 return request;
6173         }
6175         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6177         return REQ_NONE;
6180 static void
6181 status_select(struct view *view, struct line *line)
6183         struct status *status = line->data;
6184         char file[SIZEOF_STR] = "all files";
6185         const char *text;
6186         const char *key;
6188         if (status && !string_format(file, "'%s'", status->new.name))
6189                 return;
6191         if (!status && line[1].type == LINE_STAT_NONE)
6192                 line++;
6194         switch (line->type) {
6195         case LINE_STAT_STAGED:
6196                 text = "Press %s to unstage %s for commit";
6197                 break;
6199         case LINE_STAT_UNSTAGED:
6200                 text = "Press %s to stage %s for commit";
6201                 break;
6203         case LINE_STAT_UNTRACKED:
6204                 text = "Press %s to stage %s for addition";
6205                 break;
6207         case LINE_STAT_HEAD:
6208         case LINE_STAT_NONE:
6209                 text = "Nothing to update";
6210                 break;
6212         default:
6213                 die("line type %d not handled in switch", line->type);
6214         }
6216         if (status && status->status == 'U') {
6217                 text = "Press %s to resolve conflict in %s";
6218                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6220         } else {
6221                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6222         }
6224         string_format(view->ref, text, key, file);
6225         if (status)
6226                 string_copy(opt_file, status->new.name);
6229 static bool
6230 status_grep(struct view *view, struct line *line)
6232         struct status *status = line->data;
6234         if (status) {
6235                 const char buf[2] = { status->status, 0 };
6236                 const char *text[] = { status->new.name, buf, NULL };
6238                 return grep_text(view, text);
6239         }
6241         return FALSE;
6244 static struct view_ops status_ops = {
6245         "file",
6246         NULL,
6247         status_open,
6248         NULL,
6249         status_draw,
6250         status_request,
6251         status_grep,
6252         status_select,
6253 };
6256 static bool
6257 stage_diff_write(struct io *io, struct line *line, struct line *end)
6259         while (line < end) {
6260                 if (!io_write(io, line->data, strlen(line->data)) ||
6261                     !io_write(io, "\n", 1))
6262                         return FALSE;
6263                 line++;
6264                 if (line->type == LINE_DIFF_CHUNK ||
6265                     line->type == LINE_DIFF_HEADER)
6266                         break;
6267         }
6269         return TRUE;
6272 static struct line *
6273 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6275         for (; view->line < line; line--)
6276                 if (line->type == type)
6277                         return line;
6279         return NULL;
6282 static bool
6283 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6285         const char *apply_argv[SIZEOF_ARG] = {
6286                 "git", "apply", "--whitespace=nowarn", NULL
6287         };
6288         struct line *diff_hdr;
6289         struct io io = {};
6290         int argc = 3;
6292         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6293         if (!diff_hdr)
6294                 return FALSE;
6296         if (!revert)
6297                 apply_argv[argc++] = "--cached";
6298         if (revert || stage_line_type == LINE_STAT_STAGED)
6299                 apply_argv[argc++] = "-R";
6300         apply_argv[argc++] = "-";
6301         apply_argv[argc++] = NULL;
6302         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6303                 return FALSE;
6305         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6306             !stage_diff_write(&io, chunk, view->line + view->lines))
6307                 chunk = NULL;
6309         io_done(&io);
6310         io_run_bg(update_index_argv);
6312         return chunk ? TRUE : FALSE;
6315 static bool
6316 stage_update(struct view *view, struct line *line)
6318         struct line *chunk = NULL;
6320         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6321                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6323         if (chunk) {
6324                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6325                         report("Failed to apply chunk");
6326                         return FALSE;
6327                 }
6329         } else if (!stage_status.status) {
6330                 view = VIEW(REQ_VIEW_STATUS);
6332                 for (line = view->line; line < view->line + view->lines; line++)
6333                         if (line->type == stage_line_type)
6334                                 break;
6336                 if (!status_update_files(view, line + 1)) {
6337                         report("Failed to update files");
6338                         return FALSE;
6339                 }
6341         } else if (!status_update_file(&stage_status, stage_line_type)) {
6342                 report("Failed to update file");
6343                 return FALSE;
6344         }
6346         return TRUE;
6349 static bool
6350 stage_revert(struct view *view, struct line *line)
6352         struct line *chunk = NULL;
6354         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6355                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6357         if (chunk) {
6358                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6359                         return FALSE;
6361                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6362                         report("Failed to revert chunk");
6363                         return FALSE;
6364                 }
6365                 return TRUE;
6367         } else {
6368                 return status_revert(stage_status.status ? &stage_status : NULL,
6369                                      stage_line_type, FALSE);
6370         }
6374 static void
6375 stage_next(struct view *view, struct line *line)
6377         int i;
6379         if (!stage_chunks) {
6380                 for (line = view->line; line < view->line + view->lines; line++) {
6381                         if (line->type != LINE_DIFF_CHUNK)
6382                                 continue;
6384                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6385                                 report("Allocation failure");
6386                                 return;
6387                         }
6389                         stage_chunk[stage_chunks++] = line - view->line;
6390                 }
6391         }
6393         for (i = 0; i < stage_chunks; i++) {
6394                 if (stage_chunk[i] > view->lineno) {
6395                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6396                         report("Chunk %d of %d", i + 1, stage_chunks);
6397                         return;
6398                 }
6399         }
6401         report("No next chunk found");
6404 static enum request
6405 stage_request(struct view *view, enum request request, struct line *line)
6407         switch (request) {
6408         case REQ_STATUS_UPDATE:
6409                 if (!stage_update(view, line))
6410                         return REQ_NONE;
6411                 break;
6413         case REQ_STATUS_REVERT:
6414                 if (!stage_revert(view, line))
6415                         return REQ_NONE;
6416                 break;
6418         case REQ_STAGE_NEXT:
6419                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6420                         report("File is untracked; press %s to add",
6421                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6422                         return REQ_NONE;
6423                 }
6424                 stage_next(view, line);
6425                 return REQ_NONE;
6427         case REQ_EDIT:
6428                 if (!stage_status.new.name[0])
6429                         return request;
6430                 if (stage_status.status == 'D') {
6431                         report("File has been deleted.");
6432                         return REQ_NONE;
6433                 }
6435                 open_editor(stage_status.new.name);
6436                 break;
6438         case REQ_REFRESH:
6439                 /* Reload everything ... */
6440                 break;
6442         case REQ_VIEW_BLAME:
6443                 if (stage_status.new.name[0]) {
6444                         string_copy(opt_file, stage_status.new.name);
6445                         opt_ref[0] = 0;
6446                 }
6447                 return request;
6449         case REQ_ENTER:
6450                 return pager_request(view, request, line);
6452         default:
6453                 return request;
6454         }
6456         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6457         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6459         /* Check whether the staged entry still exists, and close the
6460          * stage view if it doesn't. */
6461         if (!status_exists(&stage_status, stage_line_type)) {
6462                 status_restore(VIEW(REQ_VIEW_STATUS));
6463                 return REQ_VIEW_CLOSE;
6464         }
6466         if (stage_line_type == LINE_STAT_UNTRACKED) {
6467                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6468                         report("Cannot display a directory");
6469                         return REQ_NONE;
6470                 }
6472                 if (!prepare_update_file(view, stage_status.new.name)) {
6473                         report("Failed to open file: %s", strerror(errno));
6474                         return REQ_NONE;
6475                 }
6476         }
6477         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6479         return REQ_NONE;
6482 static struct view_ops stage_ops = {
6483         "line",
6484         NULL,
6485         NULL,
6486         pager_read,
6487         pager_draw,
6488         stage_request,
6489         pager_grep,
6490         pager_select,
6491 };
6494 /*
6495  * Revision graph
6496  */
6498 struct commit {
6499         char id[SIZEOF_REV];            /* SHA1 ID. */
6500         char title[128];                /* First line of the commit message. */
6501         const char *author;             /* Author of the commit. */
6502         struct time time;               /* Date from the author ident. */
6503         struct ref_list *refs;          /* Repository references. */
6504         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6505         size_t graph_size;              /* The width of the graph array. */
6506         bool has_parents;               /* Rewritten --parents seen. */
6507 };
6509 /* Size of rev graph with no  "padding" columns */
6510 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6512 struct rev_graph {
6513         struct rev_graph *prev, *next, *parents;
6514         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6515         size_t size;
6516         struct commit *commit;
6517         size_t pos;
6518         unsigned int boundary:1;
6519 };
6521 /* Parents of the commit being visualized. */
6522 static struct rev_graph graph_parents[4];
6524 /* The current stack of revisions on the graph. */
6525 static struct rev_graph graph_stacks[4] = {
6526         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6527         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6528         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6529         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6530 };
6532 static inline bool
6533 graph_parent_is_merge(struct rev_graph *graph)
6535         return graph->parents->size > 1;
6538 static inline void
6539 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6541         struct commit *commit = graph->commit;
6543         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6544                 commit->graph[commit->graph_size++] = symbol;
6547 static void
6548 clear_rev_graph(struct rev_graph *graph)
6550         graph->boundary = 0;
6551         graph->size = graph->pos = 0;
6552         graph->commit = NULL;
6553         memset(graph->parents, 0, sizeof(*graph->parents));
6556 static void
6557 done_rev_graph(struct rev_graph *graph)
6559         if (graph_parent_is_merge(graph) &&
6560             graph->pos < graph->size - 1 &&
6561             graph->next->size == graph->size + graph->parents->size - 1) {
6562                 size_t i = graph->pos + graph->parents->size - 1;
6564                 graph->commit->graph_size = i * 2;
6565                 while (i < graph->next->size - 1) {
6566                         append_to_rev_graph(graph, ' ');
6567                         append_to_rev_graph(graph, '\\');
6568                         i++;
6569                 }
6570         }
6572         clear_rev_graph(graph);
6575 static void
6576 push_rev_graph(struct rev_graph *graph, const char *parent)
6578         int i;
6580         /* "Collapse" duplicate parents lines.
6581          *
6582          * FIXME: This needs to also update update the drawn graph but
6583          * for now it just serves as a method for pruning graph lines. */
6584         for (i = 0; i < graph->size; i++)
6585                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6586                         return;
6588         if (graph->size < SIZEOF_REVITEMS) {
6589                 string_copy_rev(graph->rev[graph->size++], parent);
6590         }
6593 static chtype
6594 get_rev_graph_symbol(struct rev_graph *graph)
6596         chtype symbol;
6598         if (graph->boundary)
6599                 symbol = REVGRAPH_BOUND;
6600         else if (graph->parents->size == 0)
6601                 symbol = REVGRAPH_INIT;
6602         else if (graph_parent_is_merge(graph))
6603                 symbol = REVGRAPH_MERGE;
6604         else if (graph->pos >= graph->size)
6605                 symbol = REVGRAPH_BRANCH;
6606         else
6607                 symbol = REVGRAPH_COMMIT;
6609         return symbol;
6612 static void
6613 draw_rev_graph(struct rev_graph *graph)
6615         struct rev_filler {
6616                 chtype separator, line;
6617         };
6618         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6619         static struct rev_filler fillers[] = {
6620                 { ' ',  '|' },
6621                 { '`',  '.' },
6622                 { '\'', ' ' },
6623                 { '/',  ' ' },
6624         };
6625         chtype symbol = get_rev_graph_symbol(graph);
6626         struct rev_filler *filler;
6627         size_t i;
6629         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6630         filler = &fillers[DEFAULT];
6632         for (i = 0; i < graph->pos; i++) {
6633                 append_to_rev_graph(graph, filler->line);
6634                 if (graph_parent_is_merge(graph->prev) &&
6635                     graph->prev->pos == i)
6636                         filler = &fillers[RSHARP];
6638                 append_to_rev_graph(graph, filler->separator);
6639         }
6641         /* Place the symbol for this revision. */
6642         append_to_rev_graph(graph, symbol);
6644         if (graph->prev->size > graph->size)
6645                 filler = &fillers[RDIAG];
6646         else
6647                 filler = &fillers[DEFAULT];
6649         i++;
6651         for (; i < graph->size; i++) {
6652                 append_to_rev_graph(graph, filler->separator);
6653                 append_to_rev_graph(graph, filler->line);
6654                 if (graph_parent_is_merge(graph->prev) &&
6655                     i < graph->prev->pos + graph->parents->size)
6656                         filler = &fillers[RSHARP];
6657                 if (graph->prev->size > graph->size)
6658                         filler = &fillers[LDIAG];
6659         }
6661         if (graph->prev->size > graph->size) {
6662                 append_to_rev_graph(graph, filler->separator);
6663                 if (filler->line != ' ')
6664                         append_to_rev_graph(graph, filler->line);
6665         }
6668 /* Prepare the next rev graph */
6669 static void
6670 prepare_rev_graph(struct rev_graph *graph)
6672         size_t i;
6674         /* First, traverse all lines of revisions up to the active one. */
6675         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6676                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6677                         break;
6679                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6680         }
6682         /* Interleave the new revision parent(s). */
6683         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6684                 push_rev_graph(graph->next, graph->parents->rev[i]);
6686         /* Lastly, put any remaining revisions. */
6687         for (i = graph->pos + 1; i < graph->size; i++)
6688                 push_rev_graph(graph->next, graph->rev[i]);
6691 static void
6692 update_rev_graph(struct view *view, struct rev_graph *graph)
6694         /* If this is the finalizing update ... */
6695         if (graph->commit)
6696                 prepare_rev_graph(graph);
6698         /* Graph visualization needs a one rev look-ahead,
6699          * so the first update doesn't visualize anything. */
6700         if (!graph->prev->commit)
6701                 return;
6703         if (view->lines > 2)
6704                 view->line[view->lines - 3].dirty = 1;
6705         if (view->lines > 1)
6706                 view->line[view->lines - 2].dirty = 1;
6707         draw_rev_graph(graph->prev);
6708         done_rev_graph(graph->prev->prev);
6712 /*
6713  * Main view backend
6714  */
6716 static const char *main_argv[SIZEOF_ARG] = {
6717         "git", "log", "--no-color", "--pretty=raw", "--parents",
6718                       "--topo-order", "%(head)", NULL
6719 };
6721 static bool
6722 main_draw(struct view *view, struct line *line, unsigned int lineno)
6724         struct commit *commit = line->data;
6726         if (!commit->author)
6727                 return FALSE;
6729         if (opt_date && draw_date(view, &commit->time))
6730                 return TRUE;
6732         if (opt_author && draw_author(view, commit->author))
6733                 return TRUE;
6735         if (opt_rev_graph && commit->graph_size &&
6736             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6737                 return TRUE;
6739         if (opt_show_refs && commit->refs) {
6740                 size_t i;
6742                 for (i = 0; i < commit->refs->size; i++) {
6743                         struct ref *ref = commit->refs->refs[i];
6744                         enum line_type type;
6746                         if (ref->head)
6747                                 type = LINE_MAIN_HEAD;
6748                         else if (ref->ltag)
6749                                 type = LINE_MAIN_LOCAL_TAG;
6750                         else if (ref->tag)
6751                                 type = LINE_MAIN_TAG;
6752                         else if (ref->tracked)
6753                                 type = LINE_MAIN_TRACKED;
6754                         else if (ref->remote)
6755                                 type = LINE_MAIN_REMOTE;
6756                         else
6757                                 type = LINE_MAIN_REF;
6759                         if (draw_text(view, type, "[", TRUE) ||
6760                             draw_text(view, type, ref->name, TRUE) ||
6761                             draw_text(view, type, "]", TRUE))
6762                                 return TRUE;
6764                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6765                                 return TRUE;
6766                 }
6767         }
6769         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6770         return TRUE;
6773 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6774 static bool
6775 main_read(struct view *view, char *line)
6777         static struct rev_graph *graph = graph_stacks;
6778         enum line_type type;
6779         struct commit *commit;
6781         if (!line) {
6782                 int i;
6784                 if (!view->lines && !view->parent)
6785                         die("No revisions match the given arguments.");
6786                 if (view->lines > 0) {
6787                         commit = view->line[view->lines - 1].data;
6788                         view->line[view->lines - 1].dirty = 1;
6789                         if (!commit->author) {
6790                                 view->lines--;
6791                                 free(commit);
6792                                 graph->commit = NULL;
6793                         }
6794                 }
6795                 update_rev_graph(view, graph);
6797                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6798                         clear_rev_graph(&graph_stacks[i]);
6799                 return TRUE;
6800         }
6802         type = get_line_type(line);
6803         if (type == LINE_COMMIT) {
6804                 commit = calloc(1, sizeof(struct commit));
6805                 if (!commit)
6806                         return FALSE;
6808                 line += STRING_SIZE("commit ");
6809                 if (*line == '-') {
6810                         graph->boundary = 1;
6811                         line++;
6812                 }
6814                 string_copy_rev(commit->id, line);
6815                 commit->refs = get_ref_list(commit->id);
6816                 graph->commit = commit;
6817                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6819                 while ((line = strchr(line, ' '))) {
6820                         line++;
6821                         push_rev_graph(graph->parents, line);
6822                         commit->has_parents = TRUE;
6823                 }
6824                 return TRUE;
6825         }
6827         if (!view->lines)
6828                 return TRUE;
6829         commit = view->line[view->lines - 1].data;
6831         switch (type) {
6832         case LINE_PARENT:
6833                 if (commit->has_parents)
6834                         break;
6835                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6836                 break;
6838         case LINE_AUTHOR:
6839                 parse_author_line(line + STRING_SIZE("author "),
6840                                   &commit->author, &commit->time);
6841                 update_rev_graph(view, graph);
6842                 graph = graph->next;
6843                 break;
6845         default:
6846                 /* Fill in the commit title if it has not already been set. */
6847                 if (commit->title[0])
6848                         break;
6850                 /* Require titles to start with a non-space character at the
6851                  * offset used by git log. */
6852                 if (strncmp(line, "    ", 4))
6853                         break;
6854                 line += 4;
6855                 /* Well, if the title starts with a whitespace character,
6856                  * try to be forgiving.  Otherwise we end up with no title. */
6857                 while (isspace(*line))
6858                         line++;
6859                 if (*line == '\0')
6860                         break;
6861                 /* FIXME: More graceful handling of titles; append "..." to
6862                  * shortened titles, etc. */
6864                 string_expand(commit->title, sizeof(commit->title), line, 1);
6865                 view->line[view->lines - 1].dirty = 1;
6866         }
6868         return TRUE;
6871 static enum request
6872 main_request(struct view *view, enum request request, struct line *line)
6874         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6876         switch (request) {
6877         case REQ_ENTER:
6878                 open_view(view, REQ_VIEW_DIFF, flags);
6879                 break;
6880         case REQ_REFRESH:
6881                 load_refs();
6882                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6883                 break;
6884         default:
6885                 return request;
6886         }
6888         return REQ_NONE;
6891 static bool
6892 grep_refs(struct ref_list *list, regex_t *regex)
6894         regmatch_t pmatch;
6895         size_t i;
6897         if (!opt_show_refs || !list)
6898                 return FALSE;
6900         for (i = 0; i < list->size; i++) {
6901                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6902                         return TRUE;
6903         }
6905         return FALSE;
6908 static bool
6909 main_grep(struct view *view, struct line *line)
6911         struct commit *commit = line->data;
6912         const char *text[] = {
6913                 commit->title,
6914                 opt_author ? commit->author : "",
6915                 mkdate(&commit->time, opt_date),
6916                 NULL
6917         };
6919         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6922 static void
6923 main_select(struct view *view, struct line *line)
6925         struct commit *commit = line->data;
6927         string_copy_rev(view->ref, commit->id);
6928         string_copy_rev(ref_commit, view->ref);
6931 static struct view_ops main_ops = {
6932         "commit",
6933         main_argv,
6934         NULL,
6935         main_read,
6936         main_draw,
6937         main_request,
6938         main_grep,
6939         main_select,
6940 };
6943 /*
6944  * Status management
6945  */
6947 /* Whether or not the curses interface has been initialized. */
6948 static bool cursed = FALSE;
6950 /* Terminal hacks and workarounds. */
6951 static bool use_scroll_redrawwin;
6952 static bool use_scroll_status_wclear;
6954 /* The status window is used for polling keystrokes. */
6955 static WINDOW *status_win;
6957 /* Reading from the prompt? */
6958 static bool input_mode = FALSE;
6960 static bool status_empty = FALSE;
6962 /* Update status and title window. */
6963 static void
6964 report(const char *msg, ...)
6966         struct view *view = display[current_view];
6968         if (input_mode)
6969                 return;
6971         if (!view) {
6972                 char buf[SIZEOF_STR];
6973                 va_list args;
6975                 va_start(args, msg);
6976                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6977                         buf[sizeof(buf) - 1] = 0;
6978                         buf[sizeof(buf) - 2] = '.';
6979                         buf[sizeof(buf) - 3] = '.';
6980                         buf[sizeof(buf) - 4] = '.';
6981                 }
6982                 va_end(args);
6983                 die("%s", buf);
6984         }
6986         if (!status_empty || *msg) {
6987                 va_list args;
6989                 va_start(args, msg);
6991                 wmove(status_win, 0, 0);
6992                 if (view->has_scrolled && use_scroll_status_wclear)
6993                         wclear(status_win);
6994                 if (*msg) {
6995                         vwprintw(status_win, msg, args);
6996                         status_empty = FALSE;
6997                 } else {
6998                         status_empty = TRUE;
6999                 }
7000                 wclrtoeol(status_win);
7001                 wnoutrefresh(status_win);
7003                 va_end(args);
7004         }
7006         update_view_title(view);
7009 static void
7010 init_display(void)
7012         const char *term;
7013         int x, y;
7015         /* Initialize the curses library */
7016         if (isatty(STDIN_FILENO)) {
7017                 cursed = !!initscr();
7018                 opt_tty = stdin;
7019         } else {
7020                 /* Leave stdin and stdout alone when acting as a pager. */
7021                 opt_tty = fopen("/dev/tty", "r+");
7022                 if (!opt_tty)
7023                         die("Failed to open /dev/tty");
7024                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7025         }
7027         if (!cursed)
7028                 die("Failed to initialize curses");
7030         nonl();         /* Disable conversion and detect newlines from input. */
7031         cbreak();       /* Take input chars one at a time, no wait for \n */
7032         noecho();       /* Don't echo input */
7033         leaveok(stdscr, FALSE);
7035         if (has_colors())
7036                 init_colors();
7038         getmaxyx(stdscr, y, x);
7039         status_win = newwin(1, 0, y - 1, 0);
7040         if (!status_win)
7041                 die("Failed to create status window");
7043         /* Enable keyboard mapping */
7044         keypad(status_win, TRUE);
7045         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7047         TABSIZE = opt_tab_size;
7049         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7050         if (term && !strcmp(term, "gnome-terminal")) {
7051                 /* In the gnome-terminal-emulator, the message from
7052                  * scrolling up one line when impossible followed by
7053                  * scrolling down one line causes corruption of the
7054                  * status line. This is fixed by calling wclear. */
7055                 use_scroll_status_wclear = TRUE;
7056                 use_scroll_redrawwin = FALSE;
7058         } else if (term && !strcmp(term, "xrvt-xpm")) {
7059                 /* No problems with full optimizations in xrvt-(unicode)
7060                  * and aterm. */
7061                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7063         } else {
7064                 /* When scrolling in (u)xterm the last line in the
7065                  * scrolling direction will update slowly. */
7066                 use_scroll_redrawwin = TRUE;
7067                 use_scroll_status_wclear = FALSE;
7068         }
7071 static int
7072 get_input(int prompt_position)
7074         struct view *view;
7075         int i, key, cursor_y, cursor_x;
7076         bool loading = FALSE;
7078         if (prompt_position)
7079                 input_mode = TRUE;
7081         while (TRUE) {
7082                 foreach_view (view, i) {
7083                         update_view(view);
7084                         if (view_is_displayed(view) && view->has_scrolled &&
7085                             use_scroll_redrawwin)
7086                                 redrawwin(view->win);
7087                         view->has_scrolled = FALSE;
7088                         if (view->pipe)
7089                                 loading = TRUE;
7090                 }
7092                 /* Update the cursor position. */
7093                 if (prompt_position) {
7094                         getbegyx(status_win, cursor_y, cursor_x);
7095                         cursor_x = prompt_position;
7096                 } else {
7097                         view = display[current_view];
7098                         getbegyx(view->win, cursor_y, cursor_x);
7099                         cursor_x = view->width - 1;
7100                         cursor_y += view->lineno - view->offset;
7101                 }
7102                 setsyx(cursor_y, cursor_x);
7104                 /* Refresh, accept single keystroke of input */
7105                 doupdate();
7106                 nodelay(status_win, loading);
7107                 key = wgetch(status_win);
7109                 /* wgetch() with nodelay() enabled returns ERR when
7110                  * there's no input. */
7111                 if (key == ERR) {
7113                 } else if (key == KEY_RESIZE) {
7114                         int height, width;
7116                         getmaxyx(stdscr, height, width);
7118                         wresize(status_win, 1, width);
7119                         mvwin(status_win, height - 1, 0);
7120                         wnoutrefresh(status_win);
7121                         resize_display();
7122                         redraw_display(TRUE);
7124                 } else {
7125                         input_mode = FALSE;
7126                         return key;
7127                 }
7128         }
7131 static char *
7132 prompt_input(const char *prompt, input_handler handler, void *data)
7134         enum input_status status = INPUT_OK;
7135         static char buf[SIZEOF_STR];
7136         size_t pos = 0;
7138         buf[pos] = 0;
7140         while (status == INPUT_OK || status == INPUT_SKIP) {
7141                 int key;
7143                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7144                 wclrtoeol(status_win);
7146                 key = get_input(pos + 1);
7147                 switch (key) {
7148                 case KEY_RETURN:
7149                 case KEY_ENTER:
7150                 case '\n':
7151                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7152                         break;
7154                 case KEY_BACKSPACE:
7155                         if (pos > 0)
7156                                 buf[--pos] = 0;
7157                         else
7158                                 status = INPUT_CANCEL;
7159                         break;
7161                 case KEY_ESC:
7162                         status = INPUT_CANCEL;
7163                         break;
7165                 default:
7166                         if (pos >= sizeof(buf)) {
7167                                 report("Input string too long");
7168                                 return NULL;
7169                         }
7171                         status = handler(data, buf, key);
7172                         if (status == INPUT_OK)
7173                                 buf[pos++] = (char) key;
7174                 }
7175         }
7177         /* Clear the status window */
7178         status_empty = FALSE;
7179         report("");
7181         if (status == INPUT_CANCEL)
7182                 return NULL;
7184         buf[pos++] = 0;
7186         return buf;
7189 static enum input_status
7190 prompt_yesno_handler(void *data, char *buf, int c)
7192         if (c == 'y' || c == 'Y')
7193                 return INPUT_STOP;
7194         if (c == 'n' || c == 'N')
7195                 return INPUT_CANCEL;
7196         return INPUT_SKIP;
7199 static bool
7200 prompt_yesno(const char *prompt)
7202         char prompt2[SIZEOF_STR];
7204         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7205                 return FALSE;
7207         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7210 static enum input_status
7211 read_prompt_handler(void *data, char *buf, int c)
7213         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7216 static char *
7217 read_prompt(const char *prompt)
7219         return prompt_input(prompt, read_prompt_handler, NULL);
7222 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7224         enum input_status status = INPUT_OK;
7225         int size = 0;
7227         while (items[size].text)
7228                 size++;
7230         while (status == INPUT_OK) {
7231                 const struct menu_item *item = &items[*selected];
7232                 int key;
7233                 int i;
7235                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7236                           prompt, *selected + 1, size);
7237                 if (item->hotkey)
7238                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7239                 wprintw(status_win, "%s", item->text);
7240                 wclrtoeol(status_win);
7242                 key = get_input(COLS - 1);
7243                 switch (key) {
7244                 case KEY_RETURN:
7245                 case KEY_ENTER:
7246                 case '\n':
7247                         status = INPUT_STOP;
7248                         break;
7250                 case KEY_LEFT:
7251                 case KEY_UP:
7252                         *selected = *selected - 1;
7253                         if (*selected < 0)
7254                                 *selected = size - 1;
7255                         break;
7257                 case KEY_RIGHT:
7258                 case KEY_DOWN:
7259                         *selected = (*selected + 1) % size;
7260                         break;
7262                 case KEY_ESC:
7263                         status = INPUT_CANCEL;
7264                         break;
7266                 default:
7267                         for (i = 0; items[i].text; i++)
7268                                 if (items[i].hotkey == key) {
7269                                         *selected = i;
7270                                         status = INPUT_STOP;
7271                                         break;
7272                                 }
7273                 }
7274         }
7276         /* Clear the status window */
7277         status_empty = FALSE;
7278         report("");
7280         return status != INPUT_CANCEL;
7283 /*
7284  * Repository properties
7285  */
7287 static struct ref **refs = NULL;
7288 static size_t refs_size = 0;
7289 static struct ref *refs_head = NULL;
7291 static struct ref_list **ref_lists = NULL;
7292 static size_t ref_lists_size = 0;
7294 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7295 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7296 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7298 static int
7299 compare_refs(const void *ref1_, const void *ref2_)
7301         const struct ref *ref1 = *(const struct ref **)ref1_;
7302         const struct ref *ref2 = *(const struct ref **)ref2_;
7304         if (ref1->tag != ref2->tag)
7305                 return ref2->tag - ref1->tag;
7306         if (ref1->ltag != ref2->ltag)
7307                 return ref2->ltag - ref2->ltag;
7308         if (ref1->head != ref2->head)
7309                 return ref2->head - ref1->head;
7310         if (ref1->tracked != ref2->tracked)
7311                 return ref2->tracked - ref1->tracked;
7312         if (ref1->remote != ref2->remote)
7313                 return ref2->remote - ref1->remote;
7314         return strcmp(ref1->name, ref2->name);
7317 static void
7318 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7320         size_t i;
7322         for (i = 0; i < refs_size; i++)
7323                 if (!visitor(data, refs[i]))
7324                         break;
7327 static struct ref *
7328 get_ref_head()
7330         return refs_head;
7333 static struct ref_list *
7334 get_ref_list(const char *id)
7336         struct ref_list *list;
7337         size_t i;
7339         for (i = 0; i < ref_lists_size; i++)
7340                 if (!strcmp(id, ref_lists[i]->id))
7341                         return ref_lists[i];
7343         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7344                 return NULL;
7345         list = calloc(1, sizeof(*list));
7346         if (!list)
7347                 return NULL;
7349         for (i = 0; i < refs_size; i++) {
7350                 if (!strcmp(id, refs[i]->id) &&
7351                     realloc_refs_list(&list->refs, list->size, 1))
7352                         list->refs[list->size++] = refs[i];
7353         }
7355         if (!list->refs) {
7356                 free(list);
7357                 return NULL;
7358         }
7360         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7361         ref_lists[ref_lists_size++] = list;
7362         return list;
7365 static int
7366 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7368         struct ref *ref = NULL;
7369         bool tag = FALSE;
7370         bool ltag = FALSE;
7371         bool remote = FALSE;
7372         bool tracked = FALSE;
7373         bool head = FALSE;
7374         int from = 0, to = refs_size - 1;
7376         if (!prefixcmp(name, "refs/tags/")) {
7377                 if (!suffixcmp(name, namelen, "^{}")) {
7378                         namelen -= 3;
7379                         name[namelen] = 0;
7380                 } else {
7381                         ltag = TRUE;
7382                 }
7384                 tag = TRUE;
7385                 namelen -= STRING_SIZE("refs/tags/");
7386                 name    += STRING_SIZE("refs/tags/");
7388         } else if (!prefixcmp(name, "refs/remotes/")) {
7389                 remote = TRUE;
7390                 namelen -= STRING_SIZE("refs/remotes/");
7391                 name    += STRING_SIZE("refs/remotes/");
7392                 tracked  = !strcmp(opt_remote, name);
7394         } else if (!prefixcmp(name, "refs/heads/")) {
7395                 namelen -= STRING_SIZE("refs/heads/");
7396                 name    += STRING_SIZE("refs/heads/");
7397                 if (!strncmp(opt_head, name, namelen))
7398                         return OK;
7400         } else if (!strcmp(name, "HEAD")) {
7401                 head     = TRUE;
7402                 if (*opt_head) {
7403                         namelen  = strlen(opt_head);
7404                         name     = opt_head;
7405                 }
7406         }
7408         /* If we are reloading or it's an annotated tag, replace the
7409          * previous SHA1 with the resolved commit id; relies on the fact
7410          * git-ls-remote lists the commit id of an annotated tag right
7411          * before the commit id it points to. */
7412         while (from <= to) {
7413                 size_t pos = (to + from) / 2;
7414                 int cmp = strcmp(name, refs[pos]->name);
7416                 if (!cmp) {
7417                         ref = refs[pos];
7418                         break;
7419                 }
7421                 if (cmp < 0)
7422                         to = pos - 1;
7423                 else
7424                         from = pos + 1;
7425         }
7427         if (!ref) {
7428                 if (!realloc_refs(&refs, refs_size, 1))
7429                         return ERR;
7430                 ref = calloc(1, sizeof(*ref) + namelen);
7431                 if (!ref)
7432                         return ERR;
7433                 memmove(refs + from + 1, refs + from,
7434                         (refs_size - from) * sizeof(*refs));
7435                 refs[from] = ref;
7436                 strncpy(ref->name, name, namelen);
7437                 refs_size++;
7438         }
7440         ref->head = head;
7441         ref->tag = tag;
7442         ref->ltag = ltag;
7443         ref->remote = remote;
7444         ref->tracked = tracked;
7445         string_copy_rev(ref->id, id);
7447         if (head)
7448                 refs_head = ref;
7449         return OK;
7452 static int
7453 load_refs(void)
7455         const char *head_argv[] = {
7456                 "git", "symbolic-ref", "HEAD", NULL
7457         };
7458         static const char *ls_remote_argv[SIZEOF_ARG] = {
7459                 "git", "ls-remote", opt_git_dir, NULL
7460         };
7461         static bool init = FALSE;
7462         size_t i;
7464         if (!init) {
7465                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7466                         die("TIG_LS_REMOTE contains too many arguments");
7467                 init = TRUE;
7468         }
7470         if (!*opt_git_dir)
7471                 return OK;
7473         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7474             !prefixcmp(opt_head, "refs/heads/")) {
7475                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7477                 memmove(opt_head, offset, strlen(offset) + 1);
7478         }
7480         refs_head = NULL;
7481         for (i = 0; i < refs_size; i++)
7482                 refs[i]->id[0] = 0;
7484         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7485                 return ERR;
7487         /* Update the ref lists to reflect changes. */
7488         for (i = 0; i < ref_lists_size; i++) {
7489                 struct ref_list *list = ref_lists[i];
7490                 size_t old, new;
7492                 for (old = new = 0; old < list->size; old++)
7493                         if (!strcmp(list->id, list->refs[old]->id))
7494                                 list->refs[new++] = list->refs[old];
7495                 list->size = new;
7496         }
7498         return OK;
7501 static void
7502 set_remote_branch(const char *name, const char *value, size_t valuelen)
7504         if (!strcmp(name, ".remote")) {
7505                 string_ncopy(opt_remote, value, valuelen);
7507         } else if (*opt_remote && !strcmp(name, ".merge")) {
7508                 size_t from = strlen(opt_remote);
7510                 if (!prefixcmp(value, "refs/heads/"))
7511                         value += STRING_SIZE("refs/heads/");
7513                 if (!string_format_from(opt_remote, &from, "/%s", value))
7514                         opt_remote[0] = 0;
7515         }
7518 static void
7519 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7521         const char *argv[SIZEOF_ARG] = { name, "=" };
7522         int argc = 1 + (cmd == option_set_command);
7523         int error = ERR;
7525         if (!argv_from_string(argv, &argc, value))
7526                 config_msg = "Too many option arguments";
7527         else
7528                 error = cmd(argc, argv);
7530         if (error == ERR)
7531                 warn("Option 'tig.%s': %s", name, config_msg);
7534 static bool
7535 set_environment_variable(const char *name, const char *value)
7537         size_t len = strlen(name) + 1 + strlen(value) + 1;
7538         char *env = malloc(len);
7540         if (env &&
7541             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7542             putenv(env) == 0)
7543                 return TRUE;
7544         free(env);
7545         return FALSE;
7548 static void
7549 set_work_tree(const char *value)
7551         char cwd[SIZEOF_STR];
7553         if (!getcwd(cwd, sizeof(cwd)))
7554                 die("Failed to get cwd path: %s", strerror(errno));
7555         if (chdir(opt_git_dir) < 0)
7556                 die("Failed to chdir(%s): %s", strerror(errno));
7557         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7558                 die("Failed to get git path: %s", strerror(errno));
7559         if (chdir(cwd) < 0)
7560                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7561         if (chdir(value) < 0)
7562                 die("Failed to chdir(%s): %s", value, strerror(errno));
7563         if (!getcwd(cwd, sizeof(cwd)))
7564                 die("Failed to get cwd path: %s", strerror(errno));
7565         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7566                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7567         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7568                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7569         opt_is_inside_work_tree = TRUE;
7572 static int
7573 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7575         if (!strcmp(name, "i18n.commitencoding"))
7576                 string_ncopy(opt_encoding, value, valuelen);
7578         else if (!strcmp(name, "core.editor"))
7579                 string_ncopy(opt_editor, value, valuelen);
7581         else if (!strcmp(name, "core.worktree"))
7582                 set_work_tree(value);
7584         else if (!prefixcmp(name, "tig.color."))
7585                 set_repo_config_option(name + 10, value, option_color_command);
7587         else if (!prefixcmp(name, "tig.bind."))
7588                 set_repo_config_option(name + 9, value, option_bind_command);
7590         else if (!prefixcmp(name, "tig."))
7591                 set_repo_config_option(name + 4, value, option_set_command);
7593         else if (*opt_head && !prefixcmp(name, "branch.") &&
7594                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7595                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7597         return OK;
7600 static int
7601 load_git_config(void)
7603         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7605         return io_run_load(config_list_argv, "=", read_repo_config_option);
7608 static int
7609 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7611         if (!opt_git_dir[0]) {
7612                 string_ncopy(opt_git_dir, name, namelen);
7614         } else if (opt_is_inside_work_tree == -1) {
7615                 /* This can be 3 different values depending on the
7616                  * version of git being used. If git-rev-parse does not
7617                  * understand --is-inside-work-tree it will simply echo
7618                  * the option else either "true" or "false" is printed.
7619                  * Default to true for the unknown case. */
7620                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7622         } else if (*name == '.') {
7623                 string_ncopy(opt_cdup, name, namelen);
7625         } else {
7626                 string_ncopy(opt_prefix, name, namelen);
7627         }
7629         return OK;
7632 static int
7633 load_repo_info(void)
7635         const char *rev_parse_argv[] = {
7636                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7637                         "--show-cdup", "--show-prefix", NULL
7638         };
7640         return io_run_load(rev_parse_argv, "=", read_repo_info);
7644 /*
7645  * Main
7646  */
7648 static const char usage[] =
7649 "tig " TIG_VERSION " (" __DATE__ ")\n"
7650 "\n"
7651 "Usage: tig        [options] [revs] [--] [paths]\n"
7652 "   or: tig show   [options] [revs] [--] [paths]\n"
7653 "   or: tig blame  [rev] path\n"
7654 "   or: tig status\n"
7655 "   or: tig <      [git command output]\n"
7656 "\n"
7657 "Options:\n"
7658 "  -v, --version   Show version and exit\n"
7659 "  -h, --help      Show help message and exit";
7661 static void __NORETURN
7662 quit(int sig)
7664         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7665         if (cursed)
7666                 endwin();
7667         exit(0);
7670 static void __NORETURN
7671 die(const char *err, ...)
7673         va_list args;
7675         endwin();
7677         va_start(args, err);
7678         fputs("tig: ", stderr);
7679         vfprintf(stderr, err, args);
7680         fputs("\n", stderr);
7681         va_end(args);
7683         exit(1);
7686 static void
7687 warn(const char *msg, ...)
7689         va_list args;
7691         va_start(args, msg);
7692         fputs("tig warning: ", stderr);
7693         vfprintf(stderr, msg, args);
7694         fputs("\n", stderr);
7695         va_end(args);
7698 static enum request
7699 parse_options(int argc, const char *argv[])
7701         enum request request = REQ_VIEW_MAIN;
7702         const char *subcommand;
7703         bool seen_dashdash = FALSE;
7704         /* XXX: This is vulnerable to the user overriding options
7705          * required for the main view parser. */
7706         const char *custom_argv[SIZEOF_ARG] = {
7707                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7708                         "--topo-order", NULL
7709         };
7710         int i, j = 6;
7712         if (!isatty(STDIN_FILENO)) {
7713                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7714                 return REQ_VIEW_PAGER;
7715         }
7717         if (argc <= 1)
7718                 return REQ_NONE;
7720         subcommand = argv[1];
7721         if (!strcmp(subcommand, "status")) {
7722                 if (argc > 2)
7723                         warn("ignoring arguments after `%s'", subcommand);
7724                 return REQ_VIEW_STATUS;
7726         } else if (!strcmp(subcommand, "blame")) {
7727                 if (argc <= 2 || argc > 4)
7728                         die("invalid number of options to blame\n\n%s", usage);
7730                 i = 2;
7731                 if (argc == 4) {
7732                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7733                         i++;
7734                 }
7736                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7737                 return REQ_VIEW_BLAME;
7739         } else if (!strcmp(subcommand, "show")) {
7740                 request = REQ_VIEW_DIFF;
7742         } else {
7743                 subcommand = NULL;
7744         }
7746         if (subcommand) {
7747                 custom_argv[1] = subcommand;
7748                 j = 2;
7749         }
7751         for (i = 1 + !!subcommand; i < argc; i++) {
7752                 const char *opt = argv[i];
7754                 if (seen_dashdash || !strcmp(opt, "--")) {
7755                         seen_dashdash = TRUE;
7757                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7758                         printf("tig version %s\n", TIG_VERSION);
7759                         quit(0);
7761                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7762                         printf("%s\n", usage);
7763                         quit(0);
7764                 }
7766                 custom_argv[j++] = opt;
7767                 if (j >= ARRAY_SIZE(custom_argv))
7768                         die("command too long");
7769         }
7771         if (!prepare_update(VIEW(request), custom_argv, NULL))
7772                 die("Failed to format arguments");
7774         return request;
7777 int
7778 main(int argc, const char *argv[])
7780         const char *codeset = "UTF-8";
7781         enum request request = parse_options(argc, argv);
7782         struct view *view;
7783         size_t i;
7785         signal(SIGINT, quit);
7786         signal(SIGPIPE, SIG_IGN);
7788         if (setlocale(LC_ALL, "")) {
7789                 codeset = nl_langinfo(CODESET);
7790         }
7792         if (load_repo_info() == ERR)
7793                 die("Failed to load repo info.");
7795         if (load_options() == ERR)
7796                 die("Failed to load user config.");
7798         if (load_git_config() == ERR)
7799                 die("Failed to load repo config.");
7801         /* Require a git repository unless when running in pager mode. */
7802         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7803                 die("Not a git repository");
7805         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7806                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7807                 if (opt_iconv_in == ICONV_NONE)
7808                         die("Failed to initialize character set conversion");
7809         }
7811         if (codeset && strcmp(codeset, "UTF-8")) {
7812                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7813                 if (opt_iconv_out == ICONV_NONE)
7814                         die("Failed to initialize character set conversion");
7815         }
7817         if (load_refs() == ERR)
7818                 die("Failed to load refs.");
7820         foreach_view (view, i)
7821                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7822                         die("Too many arguments in the `%s` environment variable",
7823                             view->cmd_env);
7825         init_display();
7827         if (request != REQ_NONE)
7828                 open_view(NULL, request, OPEN_PREPARED);
7829         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7831         while (view_driver(display[current_view], request)) {
7832                 int key = get_input(0);
7834                 view = display[current_view];
7835                 request = get_keybinding(view->keymap, key);
7837                 /* Some low-level request handling. This keeps access to
7838                  * status_win restricted. */
7839                 switch (request) {
7840                 case REQ_NONE:
7841                         report("Unknown key, press %s for help",
7842                                get_key(view->keymap, REQ_VIEW_HELP));
7843                         break;
7844                 case REQ_PROMPT:
7845                 {
7846                         char *cmd = read_prompt(":");
7848                         if (cmd && isdigit(*cmd)) {
7849                                 int lineno = view->lineno + 1;
7851                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7852                                         select_view_line(view, lineno - 1);
7853                                         report("");
7854                                 } else {
7855                                         report("Unable to parse '%s' as a line number", cmd);
7856                                 }
7858                         } else if (cmd) {
7859                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7860                                 const char *argv[SIZEOF_ARG] = { "git" };
7861                                 int argc = 1;
7863                                 /* When running random commands, initially show the
7864                                  * command in the title. However, it maybe later be
7865                                  * overwritten if a commit line is selected. */
7866                                 string_ncopy(next->ref, cmd, strlen(cmd));
7868                                 if (!argv_from_string(argv, &argc, cmd)) {
7869                                         report("Too many arguments");
7870                                 } else if (!prepare_update(next, argv, NULL)) {
7871                                         report("Failed to format command");
7872                                 } else {
7873                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7874                                 }
7875                         }
7877                         request = REQ_NONE;
7878                         break;
7879                 }
7880                 case REQ_SEARCH:
7881                 case REQ_SEARCH_BACK:
7882                 {
7883                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7884                         char *search = read_prompt(prompt);
7886                         if (search)
7887                                 string_ncopy(opt_search, search, strlen(search));
7888                         else if (*opt_search)
7889                                 request = request == REQ_SEARCH ?
7890                                         REQ_FIND_NEXT :
7891                                         REQ_FIND_PREV;
7892                         else
7893                                 request = REQ_NONE;
7894                         break;
7895                 }
7896                 default:
7897                         break;
7898                 }
7899         }
7901         quit(0);
7903         return 0;