Code

Remove format_flags enum and its companion format_argv forward declaration
[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 input_status {
144         INPUT_OK,
145         INPUT_SKIP,
146         INPUT_STOP,
147         INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156         int hotkey;
157         const char *text;
158         void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164  * Allocation helpers ... Entering macro hell to never be seen again.
165  */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
168 static type *                                                                   \
169 name(type **mem, size_t size, size_t increase)                                  \
170 {                                                                               \
171         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
172         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173         type *tmp = *mem;                                                       \
174                                                                                 \
175         if (mem == NULL || num_chunks != num_chunks_new) {                      \
176                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177                 if (tmp)                                                        \
178                         *mem = tmp;                                             \
179         }                                                                       \
180                                                                                 \
181         return tmp;                                                             \
184 /*
185  * String helpers
186  */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
191         if (srclen > dstlen - 1)
192                 srclen = dstlen - 1;
194         strncpy(dst, src, srclen);
195         dst[srclen] = 0;
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204         string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static void
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
215         size_t size, pos;
217         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218                 if (src[pos] == '\t') {
219                         size_t expanded = tabsize - (size % tabsize);
221                         if (expanded + size >= dstlen - 1)
222                                 expanded = dstlen - size - 1;
223                         memcpy(dst + size, "        ", expanded);
224                         size += expanded;
225                 } else {
226                         dst[size++] = src[pos];
227                 }
228         }
230         dst[size] = 0;
233 static char *
234 chomp_string(char *name)
236         int namelen;
238         while (isspace(*name))
239                 name++;
241         namelen = strlen(name) - 1;
242         while (namelen > 0 && isspace(name[namelen]))
243                 name[namelen--] = 0;
245         return name;
248 static bool
249 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
251         va_list args;
252         size_t pos = bufpos ? *bufpos : 0;
254         va_start(args, fmt);
255         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
256         va_end(args);
258         if (bufpos)
259                 *bufpos = pos;
261         return pos >= bufsize ? FALSE : TRUE;
264 #define string_format(buf, fmt, args...) \
265         string_nformat(buf, sizeof(buf), NULL, fmt, args)
267 #define string_format_from(buf, from, fmt, args...) \
268         string_nformat(buf, sizeof(buf), from, fmt, args)
270 static int
271 string_enum_compare(const char *str1, const char *str2, int len)
273         size_t i;
275 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
277         /* Diff-Header == DIFF_HEADER */
278         for (i = 0; i < len; i++) {
279                 if (toupper(str1[i]) == toupper(str2[i]))
280                         continue;
282                 if (string_enum_sep(str1[i]) &&
283                     string_enum_sep(str2[i]))
284                         continue;
286                 return str1[i] - str2[i];
287         }
289         return 0;
292 #define enum_equals(entry, str, len) \
293         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
295 struct enum_map {
296         const char *name;
297         int namelen;
298         int value;
299 };
301 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 static char *
304 enum_map_name(const char *name, size_t namelen)
306         static char buf[SIZEOF_STR];
307         int bufpos;
309         for (bufpos = 0; bufpos <= namelen; bufpos++) {
310                 buf[bufpos] = tolower(name[bufpos]);
311                 if (buf[bufpos] == '_')
312                         buf[bufpos] = '-';
313         }
315         buf[bufpos] = 0;
316         return buf;
319 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
321 static bool
322 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
324         size_t namelen = strlen(name);
325         int i;
327         for (i = 0; i < map_size; i++)
328                 if (enum_equals(map[i], name, namelen)) {
329                         *value = map[i].value;
330                         return TRUE;
331                 }
333         return FALSE;
336 #define map_enum(attr, map, name) \
337         map_enum_do(map, ARRAY_SIZE(map), attr, name)
339 #define prefixcmp(str1, str2) \
340         strncmp(str1, str2, STRING_SIZE(str2))
342 static inline int
343 suffixcmp(const char *str, int slen, const char *suffix)
345         size_t len = slen >= 0 ? slen : strlen(str);
346         size_t suffixlen = strlen(suffix);
348         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
352 /*
353  * Unicode / UTF-8 handling
354  *
355  * NOTE: Much of the following code for dealing with Unicode is derived from
356  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
357  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
358  */
360 static inline int
361 unicode_width(unsigned long c, int tab_size)
363         if (c >= 0x1100 &&
364            (c <= 0x115f                         /* Hangul Jamo */
365             || c == 0x2329
366             || c == 0x232a
367             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
368                                                 /* CJK ... Yi */
369             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
370             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
371             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
372             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
373             || (c >= 0xffe0  && c <= 0xffe6)
374             || (c >= 0x20000 && c <= 0x2fffd)
375             || (c >= 0x30000 && c <= 0x3fffd)))
376                 return 2;
378         if (c == '\t')
379                 return tab_size;
381         return 1;
384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
385  * Illegal bytes are set one. */
386 static const unsigned char utf8_bytes[256] = {
387         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,
388         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,
389         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,
390         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,
391         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,
392         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,
393         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,
394         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,
395 };
397 static inline unsigned char
398 utf8_char_length(const char *string, const char *end)
400         int c = *(unsigned char *) string;
402         return utf8_bytes[c];
405 /* Decode UTF-8 multi-byte representation into a Unicode character. */
406 static inline unsigned long
407 utf8_to_unicode(const char *string, size_t length)
409         unsigned long unicode;
411         switch (length) {
412         case 1:
413                 unicode  =   string[0];
414                 break;
415         case 2:
416                 unicode  =  (string[0] & 0x1f) << 6;
417                 unicode +=  (string[1] & 0x3f);
418                 break;
419         case 3:
420                 unicode  =  (string[0] & 0x0f) << 12;
421                 unicode += ((string[1] & 0x3f) << 6);
422                 unicode +=  (string[2] & 0x3f);
423                 break;
424         case 4:
425                 unicode  =  (string[0] & 0x0f) << 18;
426                 unicode += ((string[1] & 0x3f) << 12);
427                 unicode += ((string[2] & 0x3f) << 6);
428                 unicode +=  (string[3] & 0x3f);
429                 break;
430         case 5:
431                 unicode  =  (string[0] & 0x0f) << 24;
432                 unicode += ((string[1] & 0x3f) << 18);
433                 unicode += ((string[2] & 0x3f) << 12);
434                 unicode += ((string[3] & 0x3f) << 6);
435                 unicode +=  (string[4] & 0x3f);
436                 break;
437         case 6:
438                 unicode  =  (string[0] & 0x01) << 30;
439                 unicode += ((string[1] & 0x3f) << 24);
440                 unicode += ((string[2] & 0x3f) << 18);
441                 unicode += ((string[3] & 0x3f) << 12);
442                 unicode += ((string[4] & 0x3f) << 6);
443                 unicode +=  (string[5] & 0x3f);
444                 break;
445         default:
446                 return 0;
447         }
449         /* Invalid characters could return the special 0xfffd value but NUL
450          * should be just as good. */
451         return unicode > 0xffff ? 0 : unicode;
454 /* Calculates how much of string can be shown within the given maximum width
455  * and sets trimmed parameter to non-zero value if all of string could not be
456  * shown. If the reserve flag is TRUE, it will reserve at least one
457  * trailing character, which can be useful when drawing a delimiter.
458  *
459  * Returns the number of bytes to output from string to satisfy max_width. */
460 static size_t
461 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
463         const char *string = *start;
464         const char *end = strchr(string, '\0');
465         unsigned char last_bytes = 0;
466         size_t last_ucwidth = 0;
468         *width = 0;
469         *trimmed = 0;
471         while (string < end) {
472                 unsigned char bytes = utf8_char_length(string, end);
473                 size_t ucwidth;
474                 unsigned long unicode;
476                 if (string + bytes > end)
477                         break;
479                 /* Change representation to figure out whether
480                  * it is a single- or double-width character. */
482                 unicode = utf8_to_unicode(string, bytes);
483                 /* FIXME: Graceful handling of invalid Unicode character. */
484                 if (!unicode)
485                         break;
487                 ucwidth = unicode_width(unicode, tab_size);
488                 if (skip > 0) {
489                         skip -= ucwidth <= skip ? ucwidth : skip;
490                         *start += bytes;
491                 }
492                 *width  += ucwidth;
493                 if (*width > max_width) {
494                         *trimmed = 1;
495                         *width -= ucwidth;
496                         if (reserve && *width == max_width) {
497                                 string -= last_bytes;
498                                 *width -= last_ucwidth;
499                         }
500                         break;
501                 }
503                 string  += bytes;
504                 last_bytes = ucwidth ? bytes : 0;
505                 last_ucwidth = ucwidth;
506         }
508         return string - *start;
512 #define DATE_INFO \
513         DATE_(NO), \
514         DATE_(DEFAULT), \
515         DATE_(LOCAL), \
516         DATE_(RELATIVE), \
517         DATE_(SHORT)
519 enum date {
520 #define DATE_(name) DATE_##name
521         DATE_INFO
522 #undef  DATE_
523 };
525 static const struct enum_map date_map[] = {
526 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
527         DATE_INFO
528 #undef  DATE_
529 };
531 struct time {
532         time_t sec;
533         int tz;
534 };
536 static inline int timecmp(const struct time *t1, const struct time *t2)
538         return t1->sec - t2->sec;
541 static const char *
542 mkdate(const struct time *time, enum date date)
544         static char buf[DATE_COLS + 1];
545         static const struct enum_map reldate[] = {
546                 { "second", 1,                  60 * 2 },
547                 { "minute", 60,                 60 * 60 * 2 },
548                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
549                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
550                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
551                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
552         };
553         struct tm tm;
555         if (!date || !time || !time->sec)
556                 return "";
558         if (date == DATE_RELATIVE) {
559                 struct timeval now;
560                 time_t date = time->sec + time->tz;
561                 time_t seconds;
562                 int i;
564                 gettimeofday(&now, NULL);
565                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
566                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
567                         if (seconds >= reldate[i].value)
568                                 continue;
570                         seconds /= reldate[i].namelen;
571                         if (!string_format(buf, "%ld %s%s %s",
572                                            seconds, reldate[i].name,
573                                            seconds > 1 ? "s" : "",
574                                            now.tv_sec >= date ? "ago" : "ahead"))
575                                 break;
576                         return buf;
577                 }
578         }
580         if (date == DATE_LOCAL) {
581                 time_t date = time->sec + time->tz;
582                 localtime_r(&date, &tm);
583         }
584         else {
585                 gmtime_r(&time->sec, &tm);
586         }
587         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
591 #define AUTHOR_VALUES \
592         AUTHOR_(NO), \
593         AUTHOR_(FULL), \
594         AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598         AUTHOR_VALUES,
599 #undef  AUTHOR_
600         AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605         AUTHOR_VALUES
606 #undef  AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
612         static char initials[AUTHOR_COLS * 6 + 1];
613         size_t pos = 0;
614         const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618         memset(initials, 0, sizeof(initials));
619         while (author < end) {
620                 unsigned char bytes;
621                 size_t i;
623                 while (is_initial_sep(*author))
624                         author++;
626                 bytes = utf8_char_length(author, end);
627                 if (bytes < sizeof(initials) - 1 - pos) {
628                         while (bytes--) {
629                                 initials[pos++] = *author++;
630                         }
631                 }
633                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634                         if (i < sizeof(initials) - 1)
635                                 initials[i++] = *author;
636                 }
638                 initials[i++] = 0;
639         }
641         return initials;
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
648         int valuelen;
650         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651                 bool advance = cmd[valuelen] != 0;
653                 cmd[valuelen] = 0;
654                 argv[(*argc)++] = chomp_string(cmd);
655                 cmd = chomp_string(cmd + valuelen + advance);
656         }
658         if (*argc < SIZEOF_ARG)
659                 argv[*argc] = NULL;
660         return *argc < SIZEOF_ARG;
663 static bool
664 argv_from_env(const char **argv, const char *name)
666         char *env = argv ? getenv(name) : NULL;
667         int argc = 0;
669         if (env && *env)
670                 env = strdup(env);
671         return !env || argv_from_string(argv, &argc, env);
674 static void
675 argv_free(const char *argv[])
677         int argc;
679         for (argc = 0; argv[argc]; argc++)
680                 free((void *) argv[argc]);
681         argv[0] = NULL;
684 static bool
685 argv_copy(const char *dst[], const char *src[], bool allocate)
687         int argc;
689         for (argc = 0; src[argc]; argc++)
690                 if (!(dst[argc] = allocate ? strdup(src[argc]) : src[argc]))
691                         return FALSE;
692         return TRUE;
696 /*
697  * Executing external commands.
698  */
700 enum io_type {
701         IO_FD,                  /* File descriptor based IO. */
702         IO_BG,                  /* Execute command in the background. */
703         IO_FG,                  /* Execute command with same std{in,out,err}. */
704         IO_RD,                  /* Read only fork+exec IO. */
705         IO_WR,                  /* Write only fork+exec IO. */
706         IO_AP,                  /* Append fork+exec output to file. */
707 };
709 struct io {
710         enum io_type type;      /* The requested type of pipe. */
711         const char *dir;        /* Directory from which to execute. */
712         pid_t pid;              /* PID of spawned process. */
713         int pipe;               /* Pipe end for reading or writing. */
714         int error;              /* Error status. */
715         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
716         char *buf;              /* Read buffer. */
717         size_t bufalloc;        /* Allocated buffer size. */
718         size_t bufsize;         /* Buffer content size. */
719         char *bufpos;           /* Current buffer position. */
720         unsigned int eof:1;     /* Has end of file been reached. */
721 };
723 static void
724 io_reset(struct io *io)
726         io->pipe = -1;
727         io->pid = 0;
728         io->buf = io->bufpos = NULL;
729         io->bufalloc = io->bufsize = 0;
730         io->error = 0;
731         io->eof = 0;
734 static void
735 io_init(struct io *io, const char *dir, enum io_type type)
737         io_reset(io);
738         io->type = type;
739         io->dir = dir;
742 static void
743 io_prepare(struct io *io, const char *dir, enum io_type type, const char *argv[])
745         io_init(io, dir, type);
746         argv_copy(io->argv, argv, FALSE);
749 static bool
750 io_open(struct io *io, const char *fmt, ...)
752         char name[SIZEOF_STR] = "";
753         bool fits;
754         va_list args;
756         io_init(io, NULL, IO_FD);
758         va_start(args, fmt);
759         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
760         va_end(args);
762         if (!fits) {
763                 io->error = ENAMETOOLONG;
764                 return FALSE;
765         }
766         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
767         if (io->pipe == -1)
768                 io->error = errno;
769         return io->pipe != -1;
772 static bool
773 io_kill(struct io *io)
775         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
778 static bool
779 io_done(struct io *io)
781         pid_t pid = io->pid;
783         if (io->pipe != -1)
784                 close(io->pipe);
785         free(io->buf);
786         io_reset(io);
788         while (pid > 0) {
789                 int status;
790                 pid_t waiting = waitpid(pid, &status, 0);
792                 if (waiting < 0) {
793                         if (errno == EINTR)
794                                 continue;
795                         io->error = errno;
796                         return FALSE;
797                 }
799                 return waiting == pid &&
800                        !WIFSIGNALED(status) &&
801                        WIFEXITED(status) &&
802                        !WEXITSTATUS(status);
803         }
805         return TRUE;
808 static bool
809 io_start(struct io *io)
811         int pipefds[2] = { -1, -1 };
813         if (io->type == IO_FD)
814                 return TRUE;
816         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
817                 io->error = errno;
818                 return FALSE;
819         } else if (io->type == IO_AP) {
820                 pipefds[1] = io->pipe;
821         }
823         if ((io->pid = fork())) {
824                 if (io->pid == -1)
825                         io->error = errno;
826                 if (pipefds[!(io->type == IO_WR)] != -1)
827                         close(pipefds[!(io->type == IO_WR)]);
828                 if (io->pid != -1) {
829                         io->pipe = pipefds[!!(io->type == IO_WR)];
830                         return TRUE;
831                 }
833         } else {
834                 if (io->type != IO_FG) {
835                         int devnull = open("/dev/null", O_RDWR);
836                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
837                         int writefd = (io->type == IO_RD || io->type == IO_AP)
838                                                         ? pipefds[1] : devnull;
840                         dup2(readfd,  STDIN_FILENO);
841                         dup2(writefd, STDOUT_FILENO);
842                         dup2(devnull, STDERR_FILENO);
844                         close(devnull);
845                         if (pipefds[0] != -1)
846                                 close(pipefds[0]);
847                         if (pipefds[1] != -1)
848                                 close(pipefds[1]);
849                 }
851                 if (io->dir && *io->dir && chdir(io->dir) == -1)
852                         exit(errno);
854                 execvp(io->argv[0], (char *const*) io->argv);
855                 exit(errno);
856         }
858         if (pipefds[!!(io->type == IO_WR)] != -1)
859                 close(pipefds[!!(io->type == IO_WR)]);
860         return FALSE;
863 static bool
864 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
866         io_prepare(io, dir, type, argv);
867         return io_start(io);
870 static bool
871 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
873         struct io io = {};
875         io_prepare(&io, dir, type, argv);
876         io.pipe = fd;
877         return io_start(&io) && io_done(&io);
880 static bool
881 io_run_bg(const char **argv)
883         return io_complete(IO_BG, argv, NULL, -1);
886 static bool
887 io_run_fg(const char **argv, const char *dir)
889         return io_complete(IO_FG, argv, dir, -1);
892 static bool
893 io_run_append(const char **argv, int fd)
895         return io_complete(IO_AP, argv, NULL, -1);
898 static bool
899 io_eof(struct io *io)
901         return io->eof;
904 static int
905 io_error(struct io *io)
907         return io->error;
910 static char *
911 io_strerror(struct io *io)
913         return strerror(io->error);
916 static bool
917 io_can_read(struct io *io)
919         struct timeval tv = { 0, 500 };
920         fd_set fds;
922         FD_ZERO(&fds);
923         FD_SET(io->pipe, &fds);
925         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
928 static ssize_t
929 io_read(struct io *io, void *buf, size_t bufsize)
931         do {
932                 ssize_t readsize = read(io->pipe, buf, bufsize);
934                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
935                         continue;
936                 else if (readsize == -1)
937                         io->error = errno;
938                 else if (readsize == 0)
939                         io->eof = 1;
940                 return readsize;
941         } while (1);
944 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
946 static char *
947 io_get(struct io *io, int c, bool can_read)
949         char *eol;
950         ssize_t readsize;
952         while (TRUE) {
953                 if (io->bufsize > 0) {
954                         eol = memchr(io->bufpos, c, io->bufsize);
955                         if (eol) {
956                                 char *line = io->bufpos;
958                                 *eol = 0;
959                                 io->bufpos = eol + 1;
960                                 io->bufsize -= io->bufpos - line;
961                                 return line;
962                         }
963                 }
965                 if (io_eof(io)) {
966                         if (io->bufsize) {
967                                 io->bufpos[io->bufsize] = 0;
968                                 io->bufsize = 0;
969                                 return io->bufpos;
970                         }
971                         return NULL;
972                 }
974                 if (!can_read)
975                         return NULL;
977                 if (io->bufsize > 0 && io->bufpos > io->buf)
978                         memmove(io->buf, io->bufpos, io->bufsize);
980                 if (io->bufalloc == io->bufsize) {
981                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
982                                 return NULL;
983                         io->bufalloc += BUFSIZ;
984                 }
986                 io->bufpos = io->buf;
987                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
988                 if (io_error(io))
989                         return NULL;
990                 io->bufsize += readsize;
991         }
994 static bool
995 io_write(struct io *io, const void *buf, size_t bufsize)
997         size_t written = 0;
999         while (!io_error(io) && written < bufsize) {
1000                 ssize_t size;
1002                 size = write(io->pipe, buf + written, bufsize - written);
1003                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1004                         continue;
1005                 else if (size == -1)
1006                         io->error = errno;
1007                 else
1008                         written += size;
1009         }
1011         return written == bufsize;
1014 static bool
1015 io_read_buf(struct io *io, char buf[], size_t bufsize)
1017         char *result = io_get(io, '\n', TRUE);
1019         if (result) {
1020                 result = chomp_string(result);
1021                 string_ncopy_do(buf, bufsize, result, strlen(result));
1022         }
1024         return io_done(io) && result;
1027 static bool
1028 io_run_buf(const char **argv, char buf[], size_t bufsize)
1030         struct io io = {};
1032         io_prepare(&io, NULL, IO_RD, argv);
1033         return io_start(&io) && io_read_buf(&io, buf, bufsize);
1036 static int
1037 io_load(struct io *io, const char *separators,
1038         int (*read_property)(char *, size_t, char *, size_t))
1040         char *name;
1041         int state = OK;
1043         if (!io_start(io))
1044                 return ERR;
1046         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1047                 char *value;
1048                 size_t namelen;
1049                 size_t valuelen;
1051                 name = chomp_string(name);
1052                 namelen = strcspn(name, separators);
1054                 if (name[namelen]) {
1055                         name[namelen] = 0;
1056                         value = chomp_string(name + namelen + 1);
1057                         valuelen = strlen(value);
1059                 } else {
1060                         value = "";
1061                         valuelen = 0;
1062                 }
1064                 state = read_property(name, namelen, value, valuelen);
1065         }
1067         if (state != ERR && io_error(io))
1068                 state = ERR;
1069         io_done(io);
1071         return state;
1074 static int
1075 io_run_load(const char **argv, const char *separators,
1076             int (*read_property)(char *, size_t, char *, size_t))
1078         struct io io = {};
1080         io_prepare(&io, NULL, IO_RD, argv);
1081         return io_load(&io, separators, read_property);
1085 /*
1086  * User requests
1087  */
1089 #define REQ_INFO \
1090         /* XXX: Keep the view request first and in sync with views[]. */ \
1091         REQ_GROUP("View switching") \
1092         REQ_(VIEW_MAIN,         "Show main view"), \
1093         REQ_(VIEW_DIFF,         "Show diff view"), \
1094         REQ_(VIEW_LOG,          "Show log view"), \
1095         REQ_(VIEW_TREE,         "Show tree view"), \
1096         REQ_(VIEW_BLOB,         "Show blob view"), \
1097         REQ_(VIEW_BLAME,        "Show blame view"), \
1098         REQ_(VIEW_BRANCH,       "Show branch view"), \
1099         REQ_(VIEW_HELP,         "Show help page"), \
1100         REQ_(VIEW_PAGER,        "Show pager view"), \
1101         REQ_(VIEW_STATUS,       "Show status view"), \
1102         REQ_(VIEW_STAGE,        "Show stage view"), \
1103         \
1104         REQ_GROUP("View manipulation") \
1105         REQ_(ENTER,             "Enter current line and scroll"), \
1106         REQ_(NEXT,              "Move to next"), \
1107         REQ_(PREVIOUS,          "Move to previous"), \
1108         REQ_(PARENT,            "Move to parent"), \
1109         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1110         REQ_(REFRESH,           "Reload and refresh"), \
1111         REQ_(MAXIMIZE,          "Maximize the current view"), \
1112         REQ_(VIEW_CLOSE,        "Close the current view"), \
1113         REQ_(QUIT,              "Close all views and quit"), \
1114         \
1115         REQ_GROUP("View specific requests") \
1116         REQ_(STATUS_UPDATE,     "Update file status"), \
1117         REQ_(STATUS_REVERT,     "Revert file changes"), \
1118         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1119         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1120         \
1121         REQ_GROUP("Cursor navigation") \
1122         REQ_(MOVE_UP,           "Move cursor one line up"), \
1123         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1124         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1125         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1126         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1127         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1128         \
1129         REQ_GROUP("Scrolling") \
1130         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1131         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1132         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1133         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1134         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1135         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1136         \
1137         REQ_GROUP("Searching") \
1138         REQ_(SEARCH,            "Search the view"), \
1139         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1140         REQ_(FIND_NEXT,         "Find next search match"), \
1141         REQ_(FIND_PREV,         "Find previous search match"), \
1142         \
1143         REQ_GROUP("Option manipulation") \
1144         REQ_(OPTIONS,           "Open option menu"), \
1145         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1146         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1147         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1149         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1150         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1151         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1153         \
1154         REQ_GROUP("Misc") \
1155         REQ_(PROMPT,            "Bring up the prompt"), \
1156         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1157         REQ_(SHOW_VERSION,      "Show version information"), \
1158         REQ_(STOP_LOADING,      "Stop all loading views"), \
1159         REQ_(EDIT,              "Open in editor"), \
1160         REQ_(NONE,              "Do nothing")
1163 /* User action requests. */
1164 enum request {
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168         /* Offset all requests to avoid conflicts with ncurses getch values. */
1169         REQ_UNKNOWN = KEY_MAX + 1,
1170         REQ_OFFSET,
1171         REQ_INFO
1173 #undef  REQ_GROUP
1174 #undef  REQ_
1175 };
1177 struct request_info {
1178         enum request request;
1179         const char *name;
1180         int namelen;
1181         const char *help;
1182 };
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187         REQ_INFO
1188 #undef  REQ_GROUP
1189 #undef  REQ_
1190 };
1192 static enum request
1193 get_request(const char *name)
1195         int namelen = strlen(name);
1196         int i;
1198         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199                 if (enum_equals(req_info[i], name, namelen))
1200                         return req_info[i].request;
1202         return REQ_UNKNOWN;
1206 /*
1207  * Options
1208  */
1210 /* Option and state variables. */
1211 static enum date opt_date               = DATE_DEFAULT;
1212 static enum author opt_author           = AUTHOR_DEFAULT;
1213 static bool opt_line_number             = FALSE;
1214 static bool opt_line_graphics           = TRUE;
1215 static bool opt_rev_graph               = FALSE;
1216 static bool opt_show_refs               = TRUE;
1217 static int opt_num_interval             = 5;
1218 static double opt_hscroll               = 0.50;
1219 static double opt_scale_split_view      = 2.0 / 3.0;
1220 static int opt_tab_size                 = 8;
1221 static int opt_author_cols              = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR]        = "";
1223 static char opt_file[SIZEOF_STR]        = "";
1224 static char opt_ref[SIZEOF_REF]         = "";
1225 static char opt_head[SIZEOF_REF]        = "";
1226 static char opt_remote[SIZEOF_REF]      = "";
1227 static char opt_encoding[20]            = "UTF-8";
1228 static iconv_t opt_iconv_in             = ICONV_NONE;
1229 static iconv_t opt_iconv_out            = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR]      = "";
1231 static char opt_cdup[SIZEOF_STR]        = "";
1232 static char opt_prefix[SIZEOF_STR]      = "";
1233 static char opt_git_dir[SIZEOF_STR]     = "";
1234 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR]      = "";
1236 static FILE *opt_tty                    = NULL;
1238 #define is_initial_commit()     (!get_ref_head())
1239 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1242 /*
1243  * Line-oriented content detection.
1244  */
1246 #define LINE_INFO \
1247 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1248 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1249 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1250 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1251 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1252 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1253 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1254 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1255 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1261 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1262 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1263 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1264 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1265 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1266 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1267 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1268 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1269 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1270 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1271 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1272 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1273 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1275 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1276 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1277 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1278 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1279 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1280 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1281 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1282 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1283 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1284 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1285 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1286 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1287 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1288 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1289 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1290 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1291 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1292 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1293 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1294 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1295 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1296 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1297 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1298 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1299 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1300 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1301 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1302 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1303 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1304 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1305 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1307 enum line_type {
1308 #define LINE(type, line, fg, bg, attr) \
1309         LINE_##type
1310         LINE_INFO,
1311         LINE_NONE
1312 #undef  LINE
1313 };
1315 struct line_info {
1316         const char *name;       /* Option name. */
1317         int namelen;            /* Size of option name. */
1318         const char *line;       /* The start of line to match. */
1319         int linelen;            /* Size of string to match. */
1320         int fg, bg, attr;       /* Color and text attributes for the lines. */
1321 };
1323 static struct line_info line_info[] = {
1324 #define LINE(type, line, fg, bg, attr) \
1325         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1326         LINE_INFO
1327 #undef  LINE
1328 };
1330 static enum line_type
1331 get_line_type(const char *line)
1333         int linelen = strlen(line);
1334         enum line_type type;
1336         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1337                 /* Case insensitive search matches Signed-off-by lines better. */
1338                 if (linelen >= line_info[type].linelen &&
1339                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1340                         return type;
1342         return LINE_DEFAULT;
1345 static inline int
1346 get_line_attr(enum line_type type)
1348         assert(type < ARRAY_SIZE(line_info));
1349         return COLOR_PAIR(type) | line_info[type].attr;
1352 static struct line_info *
1353 get_line_info(const char *name)
1355         size_t namelen = strlen(name);
1356         enum line_type type;
1358         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1359                 if (enum_equals(line_info[type], name, namelen))
1360                         return &line_info[type];
1362         return NULL;
1365 static void
1366 init_colors(void)
1368         int default_bg = line_info[LINE_DEFAULT].bg;
1369         int default_fg = line_info[LINE_DEFAULT].fg;
1370         enum line_type type;
1372         start_color();
1374         if (assume_default_colors(default_fg, default_bg) == ERR) {
1375                 default_bg = COLOR_BLACK;
1376                 default_fg = COLOR_WHITE;
1377         }
1379         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1380                 struct line_info *info = &line_info[type];
1381                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1382                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1384                 init_pair(type, fg, bg);
1385         }
1388 struct line {
1389         enum line_type type;
1391         /* State flags */
1392         unsigned int selected:1;
1393         unsigned int dirty:1;
1394         unsigned int cleareol:1;
1395         unsigned int other:16;
1397         void *data;             /* User data */
1398 };
1401 /*
1402  * Keys
1403  */
1405 struct keybinding {
1406         int alias;
1407         enum request request;
1408 };
1410 static struct keybinding default_keybindings[] = {
1411         /* View switching */
1412         { 'm',          REQ_VIEW_MAIN },
1413         { 'd',          REQ_VIEW_DIFF },
1414         { 'l',          REQ_VIEW_LOG },
1415         { 't',          REQ_VIEW_TREE },
1416         { 'f',          REQ_VIEW_BLOB },
1417         { 'B',          REQ_VIEW_BLAME },
1418         { 'H',          REQ_VIEW_BRANCH },
1419         { 'p',          REQ_VIEW_PAGER },
1420         { 'h',          REQ_VIEW_HELP },
1421         { 'S',          REQ_VIEW_STATUS },
1422         { 'c',          REQ_VIEW_STAGE },
1424         /* View manipulation */
1425         { 'q',          REQ_VIEW_CLOSE },
1426         { KEY_TAB,      REQ_VIEW_NEXT },
1427         { KEY_RETURN,   REQ_ENTER },
1428         { KEY_UP,       REQ_PREVIOUS },
1429         { KEY_DOWN,     REQ_NEXT },
1430         { 'R',          REQ_REFRESH },
1431         { KEY_F(5),     REQ_REFRESH },
1432         { 'O',          REQ_MAXIMIZE },
1434         /* Cursor navigation */
1435         { 'k',          REQ_MOVE_UP },
1436         { 'j',          REQ_MOVE_DOWN },
1437         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1438         { KEY_END,      REQ_MOVE_LAST_LINE },
1439         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1440         { ' ',          REQ_MOVE_PAGE_DOWN },
1441         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1442         { 'b',          REQ_MOVE_PAGE_UP },
1443         { '-',          REQ_MOVE_PAGE_UP },
1445         /* Scrolling */
1446         { KEY_LEFT,     REQ_SCROLL_LEFT },
1447         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1448         { KEY_IC,       REQ_SCROLL_LINE_UP },
1449         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1450         { 'w',          REQ_SCROLL_PAGE_UP },
1451         { 's',          REQ_SCROLL_PAGE_DOWN },
1453         /* Searching */
1454         { '/',          REQ_SEARCH },
1455         { '?',          REQ_SEARCH_BACK },
1456         { 'n',          REQ_FIND_NEXT },
1457         { 'N',          REQ_FIND_PREV },
1459         /* Misc */
1460         { 'Q',          REQ_QUIT },
1461         { 'z',          REQ_STOP_LOADING },
1462         { 'v',          REQ_SHOW_VERSION },
1463         { 'r',          REQ_SCREEN_REDRAW },
1464         { 'o',          REQ_OPTIONS },
1465         { '.',          REQ_TOGGLE_LINENO },
1466         { 'D',          REQ_TOGGLE_DATE },
1467         { 'A',          REQ_TOGGLE_AUTHOR },
1468         { 'g',          REQ_TOGGLE_REV_GRAPH },
1469         { 'F',          REQ_TOGGLE_REFS },
1470         { 'I',          REQ_TOGGLE_SORT_ORDER },
1471         { 'i',          REQ_TOGGLE_SORT_FIELD },
1472         { ':',          REQ_PROMPT },
1473         { 'u',          REQ_STATUS_UPDATE },
1474         { '!',          REQ_STATUS_REVERT },
1475         { 'M',          REQ_STATUS_MERGE },
1476         { '@',          REQ_STAGE_NEXT },
1477         { ',',          REQ_PARENT },
1478         { 'e',          REQ_EDIT },
1479 };
1481 #define KEYMAP_INFO \
1482         KEYMAP_(GENERIC), \
1483         KEYMAP_(MAIN), \
1484         KEYMAP_(DIFF), \
1485         KEYMAP_(LOG), \
1486         KEYMAP_(TREE), \
1487         KEYMAP_(BLOB), \
1488         KEYMAP_(BLAME), \
1489         KEYMAP_(BRANCH), \
1490         KEYMAP_(PAGER), \
1491         KEYMAP_(HELP), \
1492         KEYMAP_(STATUS), \
1493         KEYMAP_(STAGE)
1495 enum keymap {
1496 #define KEYMAP_(name) KEYMAP_##name
1497         KEYMAP_INFO
1498 #undef  KEYMAP_
1499 };
1501 static const struct enum_map keymap_table[] = {
1502 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1503         KEYMAP_INFO
1504 #undef  KEYMAP_
1505 };
1507 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1509 struct keybinding_table {
1510         struct keybinding *data;
1511         size_t size;
1512 };
1514 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1516 static void
1517 add_keybinding(enum keymap keymap, enum request request, int key)
1519         struct keybinding_table *table = &keybindings[keymap];
1520         size_t i;
1522         for (i = 0; i < keybindings[keymap].size; i++) {
1523                 if (keybindings[keymap].data[i].alias == key) {
1524                         keybindings[keymap].data[i].request = request;
1525                         return;
1526                 }
1527         }
1529         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1530         if (!table->data)
1531                 die("Failed to allocate keybinding");
1532         table->data[table->size].alias = key;
1533         table->data[table->size++].request = request;
1535         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1536                 int i;
1538                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1539                         if (default_keybindings[i].alias == key)
1540                                 default_keybindings[i].request = REQ_NONE;
1541         }
1544 /* Looks for a key binding first in the given map, then in the generic map, and
1545  * lastly in the default keybindings. */
1546 static enum request
1547 get_keybinding(enum keymap keymap, int key)
1549         size_t i;
1551         for (i = 0; i < keybindings[keymap].size; i++)
1552                 if (keybindings[keymap].data[i].alias == key)
1553                         return keybindings[keymap].data[i].request;
1555         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1556                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1557                         return keybindings[KEYMAP_GENERIC].data[i].request;
1559         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1560                 if (default_keybindings[i].alias == key)
1561                         return default_keybindings[i].request;
1563         return (enum request) key;
1567 struct key {
1568         const char *name;
1569         int value;
1570 };
1572 static const struct key key_table[] = {
1573         { "Enter",      KEY_RETURN },
1574         { "Space",      ' ' },
1575         { "Backspace",  KEY_BACKSPACE },
1576         { "Tab",        KEY_TAB },
1577         { "Escape",     KEY_ESC },
1578         { "Left",       KEY_LEFT },
1579         { "Right",      KEY_RIGHT },
1580         { "Up",         KEY_UP },
1581         { "Down",       KEY_DOWN },
1582         { "Insert",     KEY_IC },
1583         { "Delete",     KEY_DC },
1584         { "Hash",       '#' },
1585         { "Home",       KEY_HOME },
1586         { "End",        KEY_END },
1587         { "PageUp",     KEY_PPAGE },
1588         { "PageDown",   KEY_NPAGE },
1589         { "F1",         KEY_F(1) },
1590         { "F2",         KEY_F(2) },
1591         { "F3",         KEY_F(3) },
1592         { "F4",         KEY_F(4) },
1593         { "F5",         KEY_F(5) },
1594         { "F6",         KEY_F(6) },
1595         { "F7",         KEY_F(7) },
1596         { "F8",         KEY_F(8) },
1597         { "F9",         KEY_F(9) },
1598         { "F10",        KEY_F(10) },
1599         { "F11",        KEY_F(11) },
1600         { "F12",        KEY_F(12) },
1601 };
1603 static int
1604 get_key_value(const char *name)
1606         int i;
1608         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1609                 if (!strcasecmp(key_table[i].name, name))
1610                         return key_table[i].value;
1612         if (strlen(name) == 1 && isprint(*name))
1613                 return (int) *name;
1615         return ERR;
1618 static const char *
1619 get_key_name(int key_value)
1621         static char key_char[] = "'X'";
1622         const char *seq = NULL;
1623         int key;
1625         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1626                 if (key_table[key].value == key_value)
1627                         seq = key_table[key].name;
1629         if (seq == NULL &&
1630             key_value < 127 &&
1631             isprint(key_value)) {
1632                 key_char[1] = (char) key_value;
1633                 seq = key_char;
1634         }
1636         return seq ? seq : "(no key)";
1639 static bool
1640 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1642         const char *sep = *pos > 0 ? ", " : "";
1643         const char *keyname = get_key_name(keybinding->alias);
1645         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1648 static bool
1649 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1650                            enum keymap keymap, bool all)
1652         int i;
1654         for (i = 0; i < keybindings[keymap].size; i++) {
1655                 if (keybindings[keymap].data[i].request == request) {
1656                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1657                                 return FALSE;
1658                         if (!all)
1659                                 break;
1660                 }
1661         }
1663         return TRUE;
1666 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1668 static const char *
1669 get_keys(enum keymap keymap, enum request request, bool all)
1671         static char buf[BUFSIZ];
1672         size_t pos = 0;
1673         int i;
1675         buf[pos] = 0;
1677         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1678                 return "Too many keybindings!";
1679         if (pos > 0 && !all)
1680                 return buf;
1682         if (keymap != KEYMAP_GENERIC) {
1683                 /* Only the generic keymap includes the default keybindings when
1684                  * listing all keys. */
1685                 if (all)
1686                         return buf;
1688                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1689                         return "Too many keybindings!";
1690                 if (pos)
1691                         return buf;
1692         }
1694         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1695                 if (default_keybindings[i].request == request) {
1696                         if (!append_key(buf, &pos, &default_keybindings[i]))
1697                                 return "Too many keybindings!";
1698                         if (!all)
1699                                 return buf;
1700                 }
1701         }
1703         return buf;
1706 struct run_request {
1707         enum keymap keymap;
1708         int key;
1709         const char *argv[SIZEOF_ARG];
1710 };
1712 static struct run_request *run_request;
1713 static size_t run_requests;
1715 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1717 static enum request
1718 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1720         struct run_request *req;
1722         if (argc >= ARRAY_SIZE(req->argv) - 1)
1723                 return REQ_NONE;
1725         if (!realloc_run_requests(&run_request, run_requests, 1))
1726                 return REQ_NONE;
1728         req = &run_request[run_requests];
1729         req->keymap = keymap;
1730         req->key = key;
1731         req->argv[0] = NULL;
1733         if (!argv_copy(req->argv, argv, TRUE))
1734                 return REQ_NONE;
1736         return REQ_NONE + ++run_requests;
1739 static struct run_request *
1740 get_run_request(enum request request)
1742         if (request <= REQ_NONE)
1743                 return NULL;
1744         return &run_request[request - REQ_NONE - 1];
1747 static void
1748 add_builtin_run_requests(void)
1750         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1751         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1752         const char *commit[] = { "git", "commit", NULL };
1753         const char *gc[] = { "git", "gc", NULL };
1754         struct {
1755                 enum keymap keymap;
1756                 int key;
1757                 int argc;
1758                 const char **argv;
1759         } reqs[] = {
1760                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1761                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1762                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1763                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1764         };
1765         int i;
1767         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1768                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1770                 if (req != reqs[i].key)
1771                         continue;
1772                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1773                 if (req != REQ_NONE)
1774                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1775         }
1778 /*
1779  * User config file handling.
1780  */
1782 static int   config_lineno;
1783 static bool  config_errors;
1784 static const char *config_msg;
1786 static const struct enum_map color_map[] = {
1787 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1788         COLOR_MAP(DEFAULT),
1789         COLOR_MAP(BLACK),
1790         COLOR_MAP(BLUE),
1791         COLOR_MAP(CYAN),
1792         COLOR_MAP(GREEN),
1793         COLOR_MAP(MAGENTA),
1794         COLOR_MAP(RED),
1795         COLOR_MAP(WHITE),
1796         COLOR_MAP(YELLOW),
1797 };
1799 static const struct enum_map attr_map[] = {
1800 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1801         ATTR_MAP(NORMAL),
1802         ATTR_MAP(BLINK),
1803         ATTR_MAP(BOLD),
1804         ATTR_MAP(DIM),
1805         ATTR_MAP(REVERSE),
1806         ATTR_MAP(STANDOUT),
1807         ATTR_MAP(UNDERLINE),
1808 };
1810 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1812 static int parse_step(double *opt, const char *arg)
1814         *opt = atoi(arg);
1815         if (!strchr(arg, '%'))
1816                 return OK;
1818         /* "Shift down" so 100% and 1 does not conflict. */
1819         *opt = (*opt - 1) / 100;
1820         if (*opt >= 1.0) {
1821                 *opt = 0.99;
1822                 config_msg = "Step value larger than 100%";
1823                 return ERR;
1824         }
1825         if (*opt < 0.0) {
1826                 *opt = 1;
1827                 config_msg = "Invalid step value";
1828                 return ERR;
1829         }
1830         return OK;
1833 static int
1834 parse_int(int *opt, const char *arg, int min, int max)
1836         int value = atoi(arg);
1838         if (min <= value && value <= max) {
1839                 *opt = value;
1840                 return OK;
1841         }
1843         config_msg = "Integer value out of bound";
1844         return ERR;
1847 static bool
1848 set_color(int *color, const char *name)
1850         if (map_enum(color, color_map, name))
1851                 return TRUE;
1852         if (!prefixcmp(name, "color"))
1853                 return parse_int(color, name + 5, 0, 255) == OK;
1854         return FALSE;
1857 /* Wants: object fgcolor bgcolor [attribute] */
1858 static int
1859 option_color_command(int argc, const char *argv[])
1861         struct line_info *info;
1863         if (argc < 3) {
1864                 config_msg = "Wrong number of arguments given to color command";
1865                 return ERR;
1866         }
1868         info = get_line_info(argv[0]);
1869         if (!info) {
1870                 static const struct enum_map obsolete[] = {
1871                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1872                         ENUM_MAP("main-date",   LINE_DATE),
1873                         ENUM_MAP("main-author", LINE_AUTHOR),
1874                 };
1875                 int index;
1877                 if (!map_enum(&index, obsolete, argv[0])) {
1878                         config_msg = "Unknown color name";
1879                         return ERR;
1880                 }
1881                 info = &line_info[index];
1882         }
1884         if (!set_color(&info->fg, argv[1]) ||
1885             !set_color(&info->bg, argv[2])) {
1886                 config_msg = "Unknown color";
1887                 return ERR;
1888         }
1890         info->attr = 0;
1891         while (argc-- > 3) {
1892                 int attr;
1894                 if (!set_attribute(&attr, argv[argc])) {
1895                         config_msg = "Unknown attribute";
1896                         return ERR;
1897                 }
1898                 info->attr |= attr;
1899         }
1901         return OK;
1904 static int parse_bool(bool *opt, const char *arg)
1906         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1907                 ? TRUE : FALSE;
1908         return OK;
1911 static int parse_enum_do(unsigned int *opt, const char *arg,
1912                          const struct enum_map *map, size_t map_size)
1914         bool is_true;
1916         assert(map_size > 1);
1918         if (map_enum_do(map, map_size, (int *) opt, arg))
1919                 return OK;
1921         if (parse_bool(&is_true, arg) != OK)
1922                 return ERR;
1924         *opt = is_true ? map[1].value : map[0].value;
1925         return OK;
1928 #define parse_enum(opt, arg, map) \
1929         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1931 static int
1932 parse_string(char *opt, const char *arg, size_t optsize)
1934         int arglen = strlen(arg);
1936         switch (arg[0]) {
1937         case '\"':
1938         case '\'':
1939                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1940                         config_msg = "Unmatched quotation";
1941                         return ERR;
1942                 }
1943                 arg += 1; arglen -= 2;
1944         default:
1945                 string_ncopy_do(opt, optsize, arg, arglen);
1946                 return OK;
1947         }
1950 /* Wants: name = value */
1951 static int
1952 option_set_command(int argc, const char *argv[])
1954         if (argc != 3) {
1955                 config_msg = "Wrong number of arguments given to set command";
1956                 return ERR;
1957         }
1959         if (strcmp(argv[1], "=")) {
1960                 config_msg = "No value assigned";
1961                 return ERR;
1962         }
1964         if (!strcmp(argv[0], "show-author"))
1965                 return parse_enum(&opt_author, argv[2], author_map);
1967         if (!strcmp(argv[0], "show-date"))
1968                 return parse_enum(&opt_date, argv[2], date_map);
1970         if (!strcmp(argv[0], "show-rev-graph"))
1971                 return parse_bool(&opt_rev_graph, argv[2]);
1973         if (!strcmp(argv[0], "show-refs"))
1974                 return parse_bool(&opt_show_refs, argv[2]);
1976         if (!strcmp(argv[0], "show-line-numbers"))
1977                 return parse_bool(&opt_line_number, argv[2]);
1979         if (!strcmp(argv[0], "line-graphics"))
1980                 return parse_bool(&opt_line_graphics, argv[2]);
1982         if (!strcmp(argv[0], "line-number-interval"))
1983                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1985         if (!strcmp(argv[0], "author-width"))
1986                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1988         if (!strcmp(argv[0], "horizontal-scroll"))
1989                 return parse_step(&opt_hscroll, argv[2]);
1991         if (!strcmp(argv[0], "split-view-height"))
1992                 return parse_step(&opt_scale_split_view, argv[2]);
1994         if (!strcmp(argv[0], "tab-size"))
1995                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1997         if (!strcmp(argv[0], "commit-encoding"))
1998                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2000         config_msg = "Unknown variable name";
2001         return ERR;
2004 /* Wants: mode request key */
2005 static int
2006 option_bind_command(int argc, const char *argv[])
2008         enum request request;
2009         int keymap = -1;
2010         int key;
2012         if (argc < 3) {
2013                 config_msg = "Wrong number of arguments given to bind command";
2014                 return ERR;
2015         }
2017         if (!set_keymap(&keymap, argv[0])) {
2018                 config_msg = "Unknown key map";
2019                 return ERR;
2020         }
2022         key = get_key_value(argv[1]);
2023         if (key == ERR) {
2024                 config_msg = "Unknown key";
2025                 return ERR;
2026         }
2028         request = get_request(argv[2]);
2029         if (request == REQ_UNKNOWN) {
2030                 static const struct enum_map obsolete[] = {
2031                         ENUM_MAP("cherry-pick",         REQ_NONE),
2032                         ENUM_MAP("screen-resize",       REQ_NONE),
2033                         ENUM_MAP("tree-parent",         REQ_PARENT),
2034                 };
2035                 int alias;
2037                 if (map_enum(&alias, obsolete, argv[2])) {
2038                         if (alias != REQ_NONE)
2039                                 add_keybinding(keymap, alias, key);
2040                         config_msg = "Obsolete request name";
2041                         return ERR;
2042                 }
2043         }
2044         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2045                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2046         if (request == REQ_UNKNOWN) {
2047                 config_msg = "Unknown request name";
2048                 return ERR;
2049         }
2051         add_keybinding(keymap, request, key);
2053         return OK;
2056 static int
2057 set_option(const char *opt, char *value)
2059         const char *argv[SIZEOF_ARG];
2060         int argc = 0;
2062         if (!argv_from_string(argv, &argc, value)) {
2063                 config_msg = "Too many option arguments";
2064                 return ERR;
2065         }
2067         if (!strcmp(opt, "color"))
2068                 return option_color_command(argc, argv);
2070         if (!strcmp(opt, "set"))
2071                 return option_set_command(argc, argv);
2073         if (!strcmp(opt, "bind"))
2074                 return option_bind_command(argc, argv);
2076         config_msg = "Unknown option command";
2077         return ERR;
2080 static int
2081 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2083         int status = OK;
2085         config_lineno++;
2086         config_msg = "Internal error";
2088         /* Check for comment markers, since read_properties() will
2089          * only ensure opt and value are split at first " \t". */
2090         optlen = strcspn(opt, "#");
2091         if (optlen == 0)
2092                 return OK;
2094         if (opt[optlen] != 0) {
2095                 config_msg = "No option value";
2096                 status = ERR;
2098         }  else {
2099                 /* Look for comment endings in the value. */
2100                 size_t len = strcspn(value, "#");
2102                 if (len < valuelen) {
2103                         valuelen = len;
2104                         value[valuelen] = 0;
2105                 }
2107                 status = set_option(opt, value);
2108         }
2110         if (status == ERR) {
2111                 warn("Error on line %d, near '%.*s': %s",
2112                      config_lineno, (int) optlen, opt, config_msg);
2113                 config_errors = TRUE;
2114         }
2116         /* Always keep going if errors are encountered. */
2117         return OK;
2120 static void
2121 load_option_file(const char *path)
2123         struct io io = {};
2125         /* It's OK that the file doesn't exist. */
2126         if (!io_open(&io, "%s", path))
2127                 return;
2129         config_lineno = 0;
2130         config_errors = FALSE;
2132         if (io_load(&io, " \t", read_option) == ERR ||
2133             config_errors == TRUE)
2134                 warn("Errors while loading %s.", path);
2137 static int
2138 load_options(void)
2140         const char *home = getenv("HOME");
2141         const char *tigrc_user = getenv("TIGRC_USER");
2142         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2143         char buf[SIZEOF_STR];
2145         if (!tigrc_system)
2146                 tigrc_system = SYSCONFDIR "/tigrc";
2147         load_option_file(tigrc_system);
2149         if (!tigrc_user) {
2150                 if (!home || !string_format(buf, "%s/.tigrc", home))
2151                         return ERR;
2152                 tigrc_user = buf;
2153         }
2154         load_option_file(tigrc_user);
2156         /* Add _after_ loading config files to avoid adding run requests
2157          * that conflict with keybindings. */
2158         add_builtin_run_requests();
2160         return OK;
2164 /*
2165  * The viewer
2166  */
2168 struct view;
2169 struct view_ops;
2171 /* The display array of active views and the index of the current view. */
2172 static struct view *display[2];
2173 static unsigned int current_view;
2175 #define foreach_displayed_view(view, i) \
2176         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2178 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2180 /* Current head and commit ID */
2181 static char ref_blob[SIZEOF_REF]        = "";
2182 static char ref_commit[SIZEOF_REF]      = "HEAD";
2183 static char ref_head[SIZEOF_REF]        = "HEAD";
2184 static char ref_branch[SIZEOF_REF]      = "";
2186 enum view_type {
2187         VIEW_MAIN,
2188         VIEW_DIFF,
2189         VIEW_LOG,
2190         VIEW_TREE,
2191         VIEW_BLOB,
2192         VIEW_BLAME,
2193         VIEW_BRANCH,
2194         VIEW_HELP,
2195         VIEW_PAGER,
2196         VIEW_STATUS,
2197         VIEW_STAGE,
2198 };
2200 struct view {
2201         enum view_type type;    /* View type */
2202         const char *name;       /* View name */
2203         const char *cmd_env;    /* Command line set via environment */
2204         const char *id;         /* Points to either of ref_{head,commit,blob} */
2206         struct view_ops *ops;   /* View operations */
2208         enum keymap keymap;     /* What keymap does this view have */
2209         bool git_dir;           /* Whether the view requires a git directory. */
2211         char ref[SIZEOF_REF];   /* Hovered commit reference */
2212         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2214         int height, width;      /* The width and height of the main window */
2215         WINDOW *win;            /* The main window */
2216         WINDOW *title;          /* The title window living below the main window */
2218         /* Navigation */
2219         unsigned long offset;   /* Offset of the window top */
2220         unsigned long yoffset;  /* Offset from the window side. */
2221         unsigned long lineno;   /* Current line number */
2222         unsigned long p_offset; /* Previous offset of the window top */
2223         unsigned long p_yoffset;/* Previous offset from the window side */
2224         unsigned long p_lineno; /* Previous current line number */
2225         bool p_restore;         /* Should the previous position be restored. */
2227         /* Searching */
2228         char grep[SIZEOF_STR];  /* Search string */
2229         regex_t *regex;         /* Pre-compiled regexp */
2231         /* If non-NULL, points to the view that opened this view. If this view
2232          * is closed tig will switch back to the parent view. */
2233         struct view *parent;
2234         struct view *prev;
2236         /* Buffering */
2237         size_t lines;           /* Total number of lines */
2238         struct line *line;      /* Line index */
2239         unsigned int digits;    /* Number of digits in the lines member. */
2241         /* Drawing */
2242         struct line *curline;   /* Line currently being drawn. */
2243         enum line_type curtype; /* Attribute currently used for drawing. */
2244         unsigned long col;      /* Column when drawing. */
2245         bool has_scrolled;      /* View was scrolled. */
2247         /* Loading */
2248         struct io io;
2249         struct io *pipe;
2250         time_t start_time;
2251         time_t update_secs;
2252 };
2254 struct view_ops {
2255         /* What type of content being displayed. Used in the title bar. */
2256         const char *type;
2257         /* Default command arguments. */
2258         const char **argv;
2259         /* Open and reads in all view content. */
2260         bool (*open)(struct view *view);
2261         /* Read one line; updates view->line. */
2262         bool (*read)(struct view *view, char *data);
2263         /* Draw one line; @lineno must be < view->height. */
2264         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2265         /* Depending on view handle a special requests. */
2266         enum request (*request)(struct view *view, enum request request, struct line *line);
2267         /* Search for regexp in a line. */
2268         bool (*grep)(struct view *view, struct line *line);
2269         /* Select line */
2270         void (*select)(struct view *view, struct line *line);
2271         /* Prepare view for loading */
2272         bool (*prepare)(struct view *view);
2273 };
2275 static struct view_ops blame_ops;
2276 static struct view_ops blob_ops;
2277 static struct view_ops diff_ops;
2278 static struct view_ops help_ops;
2279 static struct view_ops log_ops;
2280 static struct view_ops main_ops;
2281 static struct view_ops pager_ops;
2282 static struct view_ops stage_ops;
2283 static struct view_ops status_ops;
2284 static struct view_ops tree_ops;
2285 static struct view_ops branch_ops;
2287 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2288         { type, name, #env, ref, ops, map, git }
2290 #define VIEW_(id, name, ops, git, ref) \
2291         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2293 static struct view views[] = {
2294         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2295         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2296         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2297         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2298         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2299         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2300         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2301         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2302         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2303         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2304         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2305 };
2307 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2309 #define foreach_view(view, i) \
2310         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2312 #define view_is_displayed(view) \
2313         (view == display[0] || view == display[1])
2315 static enum request
2316 view_request(struct view *view, enum request request)
2318         if (!view || !view->lines)
2319                 return request;
2320         return view->ops->request(view, request, &view->line[view->lineno]);
2324 /*
2325  * View drawing.
2326  */
2328 static inline void
2329 set_view_attr(struct view *view, enum line_type type)
2331         if (!view->curline->selected && view->curtype != type) {
2332                 (void) wattrset(view->win, get_line_attr(type));
2333                 wchgat(view->win, -1, 0, type, NULL);
2334                 view->curtype = type;
2335         }
2338 static int
2339 draw_chars(struct view *view, enum line_type type, const char *string,
2340            int max_len, bool use_tilde)
2342         static char out_buffer[BUFSIZ * 2];
2343         int len = 0;
2344         int col = 0;
2345         int trimmed = FALSE;
2346         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2348         if (max_len <= 0)
2349                 return 0;
2351         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2353         set_view_attr(view, type);
2354         if (len > 0) {
2355                 if (opt_iconv_out != ICONV_NONE) {
2356                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2357                         size_t inlen = len + 1;
2359                         char *outbuf = out_buffer;
2360                         size_t outlen = sizeof(out_buffer);
2362                         size_t ret;
2364                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2365                         if (ret != (size_t) -1) {
2366                                 string = out_buffer;
2367                                 len = sizeof(out_buffer) - outlen;
2368                         }
2369                 }
2371                 waddnstr(view->win, string, len);
2372         }
2373         if (trimmed && use_tilde) {
2374                 set_view_attr(view, LINE_DELIMITER);
2375                 waddch(view->win, '~');
2376                 col++;
2377         }
2379         return col;
2382 static int
2383 draw_space(struct view *view, enum line_type type, int max, int spaces)
2385         static char space[] = "                    ";
2386         int col = 0;
2388         spaces = MIN(max, spaces);
2390         while (spaces > 0) {
2391                 int len = MIN(spaces, sizeof(space) - 1);
2393                 col += draw_chars(view, type, space, len, FALSE);
2394                 spaces -= len;
2395         }
2397         return col;
2400 static bool
2401 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2403         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2404         return view->width + view->yoffset <= view->col;
2407 static bool
2408 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2410         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2411         int max = view->width + view->yoffset - view->col;
2412         int i;
2414         if (max < size)
2415                 size = max;
2417         set_view_attr(view, type);
2418         /* Using waddch() instead of waddnstr() ensures that
2419          * they'll be rendered correctly for the cursor line. */
2420         for (i = skip; i < size; i++)
2421                 waddch(view->win, graphic[i]);
2423         view->col += size;
2424         if (size < max && skip <= size)
2425                 waddch(view->win, ' ');
2426         view->col++;
2428         return view->width + view->yoffset <= view->col;
2431 static bool
2432 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2434         int max = MIN(view->width + view->yoffset - view->col, len);
2435         int col;
2437         if (text)
2438                 col = draw_chars(view, type, text, max - 1, trim);
2439         else
2440                 col = draw_space(view, type, max - 1, max - 1);
2442         view->col += col;
2443         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2444         return view->width + view->yoffset <= view->col;
2447 static bool
2448 draw_date(struct view *view, struct time *time)
2450         const char *date = mkdate(time, opt_date);
2451         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2453         return draw_field(view, LINE_DATE, date, cols, FALSE);
2456 static bool
2457 draw_author(struct view *view, const char *author)
2459         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2460         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2462         if (abbreviate && author)
2463                 author = get_author_initials(author);
2465         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2468 static bool
2469 draw_mode(struct view *view, mode_t mode)
2471         const char *str;
2473         if (S_ISDIR(mode))
2474                 str = "drwxr-xr-x";
2475         else if (S_ISLNK(mode))
2476                 str = "lrwxrwxrwx";
2477         else if (S_ISGITLINK(mode))
2478                 str = "m---------";
2479         else if (S_ISREG(mode) && mode & S_IXUSR)
2480                 str = "-rwxr-xr-x";
2481         else if (S_ISREG(mode))
2482                 str = "-rw-r--r--";
2483         else
2484                 str = "----------";
2486         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2489 static bool
2490 draw_lineno(struct view *view, unsigned int lineno)
2492         char number[10];
2493         int digits3 = view->digits < 3 ? 3 : view->digits;
2494         int max = MIN(view->width + view->yoffset - view->col, digits3);
2495         char *text = NULL;
2496         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2498         lineno += view->offset + 1;
2499         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2500                 static char fmt[] = "%1ld";
2502                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2503                 if (string_format(number, fmt, lineno))
2504                         text = number;
2505         }
2506         if (text)
2507                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2508         else
2509                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2510         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2513 static bool
2514 draw_view_line(struct view *view, unsigned int lineno)
2516         struct line *line;
2517         bool selected = (view->offset + lineno == view->lineno);
2519         assert(view_is_displayed(view));
2521         if (view->offset + lineno >= view->lines)
2522                 return FALSE;
2524         line = &view->line[view->offset + lineno];
2526         wmove(view->win, lineno, 0);
2527         if (line->cleareol)
2528                 wclrtoeol(view->win);
2529         view->col = 0;
2530         view->curline = line;
2531         view->curtype = LINE_NONE;
2532         line->selected = FALSE;
2533         line->dirty = line->cleareol = 0;
2535         if (selected) {
2536                 set_view_attr(view, LINE_CURSOR);
2537                 line->selected = TRUE;
2538                 view->ops->select(view, line);
2539         }
2541         return view->ops->draw(view, line, lineno);
2544 static void
2545 redraw_view_dirty(struct view *view)
2547         bool dirty = FALSE;
2548         int lineno;
2550         for (lineno = 0; lineno < view->height; lineno++) {
2551                 if (view->offset + lineno >= view->lines)
2552                         break;
2553                 if (!view->line[view->offset + lineno].dirty)
2554                         continue;
2555                 dirty = TRUE;
2556                 if (!draw_view_line(view, lineno))
2557                         break;
2558         }
2560         if (!dirty)
2561                 return;
2562         wnoutrefresh(view->win);
2565 static void
2566 redraw_view_from(struct view *view, int lineno)
2568         assert(0 <= lineno && lineno < view->height);
2570         for (; lineno < view->height; lineno++) {
2571                 if (!draw_view_line(view, lineno))
2572                         break;
2573         }
2575         wnoutrefresh(view->win);
2578 static void
2579 redraw_view(struct view *view)
2581         werase(view->win);
2582         redraw_view_from(view, 0);
2586 static void
2587 update_view_title(struct view *view)
2589         char buf[SIZEOF_STR];
2590         char state[SIZEOF_STR];
2591         size_t bufpos = 0, statelen = 0;
2593         assert(view_is_displayed(view));
2595         if (view->type != VIEW_STATUS && view->lines) {
2596                 unsigned int view_lines = view->offset + view->height;
2597                 unsigned int lines = view->lines
2598                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2599                                    : 0;
2601                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2602                                    view->ops->type,
2603                                    view->lineno + 1,
2604                                    view->lines,
2605                                    lines);
2607         }
2609         if (view->pipe) {
2610                 time_t secs = time(NULL) - view->start_time;
2612                 /* Three git seconds are a long time ... */
2613                 if (secs > 2)
2614                         string_format_from(state, &statelen, " loading %lds", secs);
2615         }
2617         string_format_from(buf, &bufpos, "[%s]", view->name);
2618         if (*view->ref && bufpos < view->width) {
2619                 size_t refsize = strlen(view->ref);
2620                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2622                 if (minsize < view->width)
2623                         refsize = view->width - minsize + 7;
2624                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2625         }
2627         if (statelen && bufpos < view->width) {
2628                 string_format_from(buf, &bufpos, "%s", state);
2629         }
2631         if (view == display[current_view])
2632                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2633         else
2634                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2636         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2637         wclrtoeol(view->title);
2638         wnoutrefresh(view->title);
2641 static int
2642 apply_step(double step, int value)
2644         if (step >= 1)
2645                 return (int) step;
2646         value *= step + 0.01;
2647         return value ? value : 1;
2650 static void
2651 resize_display(void)
2653         int offset, i;
2654         struct view *base = display[0];
2655         struct view *view = display[1] ? display[1] : display[0];
2657         /* Setup window dimensions */
2659         getmaxyx(stdscr, base->height, base->width);
2661         /* Make room for the status window. */
2662         base->height -= 1;
2664         if (view != base) {
2665                 /* Horizontal split. */
2666                 view->width   = base->width;
2667                 view->height  = apply_step(opt_scale_split_view, base->height);
2668                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2669                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2670                 base->height -= view->height;
2672                 /* Make room for the title bar. */
2673                 view->height -= 1;
2674         }
2676         /* Make room for the title bar. */
2677         base->height -= 1;
2679         offset = 0;
2681         foreach_displayed_view (view, i) {
2682                 if (!view->win) {
2683                         view->win = newwin(view->height, 0, offset, 0);
2684                         if (!view->win)
2685                                 die("Failed to create %s view", view->name);
2687                         scrollok(view->win, FALSE);
2689                         view->title = newwin(1, 0, offset + view->height, 0);
2690                         if (!view->title)
2691                                 die("Failed to create title window");
2693                 } else {
2694                         wresize(view->win, view->height, view->width);
2695                         mvwin(view->win,   offset, 0);
2696                         mvwin(view->title, offset + view->height, 0);
2697                 }
2699                 offset += view->height + 1;
2700         }
2703 static void
2704 redraw_display(bool clear)
2706         struct view *view;
2707         int i;
2709         foreach_displayed_view (view, i) {
2710                 if (clear)
2711                         wclear(view->win);
2712                 redraw_view(view);
2713                 update_view_title(view);
2714         }
2718 /*
2719  * Option management
2720  */
2722 static void
2723 toggle_enum_option_do(unsigned int *opt, const char *help,
2724                       const struct enum_map *map, size_t size)
2726         *opt = (*opt + 1) % size;
2727         redraw_display(FALSE);
2728         report("Displaying %s %s", enum_name(map[*opt]), help);
2731 #define toggle_enum_option(opt, help, map) \
2732         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2734 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2735 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2737 static void
2738 toggle_view_option(bool *option, const char *help)
2740         *option = !*option;
2741         redraw_display(FALSE);
2742         report("%sabling %s", *option ? "En" : "Dis", help);
2745 static void
2746 open_option_menu(void)
2748         const struct menu_item menu[] = {
2749                 { '.', "line numbers", &opt_line_number },
2750                 { 'D', "date display", &opt_date },
2751                 { 'A', "author display", &opt_author },
2752                 { 'g', "revision graph display", &opt_rev_graph },
2753                 { 'F', "reference display", &opt_show_refs },
2754                 { 0 }
2755         };
2756         int selected = 0;
2758         if (prompt_menu("Toggle option", menu, &selected)) {
2759                 if (menu[selected].data == &opt_date)
2760                         toggle_date();
2761                 else if (menu[selected].data == &opt_author)
2762                         toggle_author();
2763                 else
2764                         toggle_view_option(menu[selected].data, menu[selected].text);
2765         }
2768 static void
2769 maximize_view(struct view *view)
2771         memset(display, 0, sizeof(display));
2772         current_view = 0;
2773         display[current_view] = view;
2774         resize_display();
2775         redraw_display(FALSE);
2776         report("");
2780 /*
2781  * Navigation
2782  */
2784 static bool
2785 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2787         if (lineno >= view->lines)
2788                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2790         if (offset > lineno || offset + view->height <= lineno) {
2791                 unsigned long half = view->height / 2;
2793                 if (lineno > half)
2794                         offset = lineno - half;
2795                 else
2796                         offset = 0;
2797         }
2799         if (offset != view->offset || lineno != view->lineno) {
2800                 view->offset = offset;
2801                 view->lineno = lineno;
2802                 return TRUE;
2803         }
2805         return FALSE;
2808 /* Scrolling backend */
2809 static void
2810 do_scroll_view(struct view *view, int lines)
2812         bool redraw_current_line = FALSE;
2814         /* The rendering expects the new offset. */
2815         view->offset += lines;
2817         assert(0 <= view->offset && view->offset < view->lines);
2818         assert(lines);
2820         /* Move current line into the view. */
2821         if (view->lineno < view->offset) {
2822                 view->lineno = view->offset;
2823                 redraw_current_line = TRUE;
2824         } else if (view->lineno >= view->offset + view->height) {
2825                 view->lineno = view->offset + view->height - 1;
2826                 redraw_current_line = TRUE;
2827         }
2829         assert(view->offset <= view->lineno && view->lineno < view->lines);
2831         /* Redraw the whole screen if scrolling is pointless. */
2832         if (view->height < ABS(lines)) {
2833                 redraw_view(view);
2835         } else {
2836                 int line = lines > 0 ? view->height - lines : 0;
2837                 int end = line + ABS(lines);
2839                 scrollok(view->win, TRUE);
2840                 wscrl(view->win, lines);
2841                 scrollok(view->win, FALSE);
2843                 while (line < end && draw_view_line(view, line))
2844                         line++;
2846                 if (redraw_current_line)
2847                         draw_view_line(view, view->lineno - view->offset);
2848                 wnoutrefresh(view->win);
2849         }
2851         view->has_scrolled = TRUE;
2852         report("");
2855 /* Scroll frontend */
2856 static void
2857 scroll_view(struct view *view, enum request request)
2859         int lines = 1;
2861         assert(view_is_displayed(view));
2863         switch (request) {
2864         case REQ_SCROLL_LEFT:
2865                 if (view->yoffset == 0) {
2866                         report("Cannot scroll beyond the first column");
2867                         return;
2868                 }
2869                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2870                         view->yoffset = 0;
2871                 else
2872                         view->yoffset -= apply_step(opt_hscroll, view->width);
2873                 redraw_view_from(view, 0);
2874                 report("");
2875                 return;
2876         case REQ_SCROLL_RIGHT:
2877                 view->yoffset += apply_step(opt_hscroll, view->width);
2878                 redraw_view(view);
2879                 report("");
2880                 return;
2881         case REQ_SCROLL_PAGE_DOWN:
2882                 lines = view->height;
2883         case REQ_SCROLL_LINE_DOWN:
2884                 if (view->offset + lines > view->lines)
2885                         lines = view->lines - view->offset;
2887                 if (lines == 0 || view->offset + view->height >= view->lines) {
2888                         report("Cannot scroll beyond the last line");
2889                         return;
2890                 }
2891                 break;
2893         case REQ_SCROLL_PAGE_UP:
2894                 lines = view->height;
2895         case REQ_SCROLL_LINE_UP:
2896                 if (lines > view->offset)
2897                         lines = view->offset;
2899                 if (lines == 0) {
2900                         report("Cannot scroll beyond the first line");
2901                         return;
2902                 }
2904                 lines = -lines;
2905                 break;
2907         default:
2908                 die("request %d not handled in switch", request);
2909         }
2911         do_scroll_view(view, lines);
2914 /* Cursor moving */
2915 static void
2916 move_view(struct view *view, enum request request)
2918         int scroll_steps = 0;
2919         int steps;
2921         switch (request) {
2922         case REQ_MOVE_FIRST_LINE:
2923                 steps = -view->lineno;
2924                 break;
2926         case REQ_MOVE_LAST_LINE:
2927                 steps = view->lines - view->lineno - 1;
2928                 break;
2930         case REQ_MOVE_PAGE_UP:
2931                 steps = view->height > view->lineno
2932                       ? -view->lineno : -view->height;
2933                 break;
2935         case REQ_MOVE_PAGE_DOWN:
2936                 steps = view->lineno + view->height >= view->lines
2937                       ? view->lines - view->lineno - 1 : view->height;
2938                 break;
2940         case REQ_MOVE_UP:
2941                 steps = -1;
2942                 break;
2944         case REQ_MOVE_DOWN:
2945                 steps = 1;
2946                 break;
2948         default:
2949                 die("request %d not handled in switch", request);
2950         }
2952         if (steps <= 0 && view->lineno == 0) {
2953                 report("Cannot move beyond the first line");
2954                 return;
2956         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2957                 report("Cannot move beyond the last line");
2958                 return;
2959         }
2961         /* Move the current line */
2962         view->lineno += steps;
2963         assert(0 <= view->lineno && view->lineno < view->lines);
2965         /* Check whether the view needs to be scrolled */
2966         if (view->lineno < view->offset ||
2967             view->lineno >= view->offset + view->height) {
2968                 scroll_steps = steps;
2969                 if (steps < 0 && -steps > view->offset) {
2970                         scroll_steps = -view->offset;
2972                 } else if (steps > 0) {
2973                         if (view->lineno == view->lines - 1 &&
2974                             view->lines > view->height) {
2975                                 scroll_steps = view->lines - view->offset - 1;
2976                                 if (scroll_steps >= view->height)
2977                                         scroll_steps -= view->height - 1;
2978                         }
2979                 }
2980         }
2982         if (!view_is_displayed(view)) {
2983                 view->offset += scroll_steps;
2984                 assert(0 <= view->offset && view->offset < view->lines);
2985                 view->ops->select(view, &view->line[view->lineno]);
2986                 return;
2987         }
2989         /* Repaint the old "current" line if we be scrolling */
2990         if (ABS(steps) < view->height)
2991                 draw_view_line(view, view->lineno - steps - view->offset);
2993         if (scroll_steps) {
2994                 do_scroll_view(view, scroll_steps);
2995                 return;
2996         }
2998         /* Draw the current line */
2999         draw_view_line(view, view->lineno - view->offset);
3001         wnoutrefresh(view->win);
3002         report("");
3006 /*
3007  * Searching
3008  */
3010 static void search_view(struct view *view, enum request request);
3012 static bool
3013 grep_text(struct view *view, const char *text[])
3015         regmatch_t pmatch;
3016         size_t i;
3018         for (i = 0; text[i]; i++)
3019                 if (*text[i] &&
3020                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3021                         return TRUE;
3022         return FALSE;
3025 static void
3026 select_view_line(struct view *view, unsigned long lineno)
3028         unsigned long old_lineno = view->lineno;
3029         unsigned long old_offset = view->offset;
3031         if (goto_view_line(view, view->offset, lineno)) {
3032                 if (view_is_displayed(view)) {
3033                         if (old_offset != view->offset) {
3034                                 redraw_view(view);
3035                         } else {
3036                                 draw_view_line(view, old_lineno - view->offset);
3037                                 draw_view_line(view, view->lineno - view->offset);
3038                                 wnoutrefresh(view->win);
3039                         }
3040                 } else {
3041                         view->ops->select(view, &view->line[view->lineno]);
3042                 }
3043         }
3046 static void
3047 find_next(struct view *view, enum request request)
3049         unsigned long lineno = view->lineno;
3050         int direction;
3052         if (!*view->grep) {
3053                 if (!*opt_search)
3054                         report("No previous search");
3055                 else
3056                         search_view(view, request);
3057                 return;
3058         }
3060         switch (request) {
3061         case REQ_SEARCH:
3062         case REQ_FIND_NEXT:
3063                 direction = 1;
3064                 break;
3066         case REQ_SEARCH_BACK:
3067         case REQ_FIND_PREV:
3068                 direction = -1;
3069                 break;
3071         default:
3072                 return;
3073         }
3075         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3076                 lineno += direction;
3078         /* Note, lineno is unsigned long so will wrap around in which case it
3079          * will become bigger than view->lines. */
3080         for (; lineno < view->lines; lineno += direction) {
3081                 if (view->ops->grep(view, &view->line[lineno])) {
3082                         select_view_line(view, lineno);
3083                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3084                         return;
3085                 }
3086         }
3088         report("No match found for '%s'", view->grep);
3091 static void
3092 search_view(struct view *view, enum request request)
3094         int regex_err;
3096         if (view->regex) {
3097                 regfree(view->regex);
3098                 *view->grep = 0;
3099         } else {
3100                 view->regex = calloc(1, sizeof(*view->regex));
3101                 if (!view->regex)
3102                         return;
3103         }
3105         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3106         if (regex_err != 0) {
3107                 char buf[SIZEOF_STR] = "unknown error";
3109                 regerror(regex_err, view->regex, buf, sizeof(buf));
3110                 report("Search failed: %s", buf);
3111                 return;
3112         }
3114         string_copy(view->grep, opt_search);
3116         find_next(view, request);
3119 /*
3120  * Incremental updating
3121  */
3123 static void
3124 reset_view(struct view *view)
3126         int i;
3128         for (i = 0; i < view->lines; i++)
3129                 free(view->line[i].data);
3130         free(view->line);
3132         view->p_offset = view->offset;
3133         view->p_yoffset = view->yoffset;
3134         view->p_lineno = view->lineno;
3136         view->line = NULL;
3137         view->offset = 0;
3138         view->yoffset = 0;
3139         view->lines  = 0;
3140         view->lineno = 0;
3141         view->vid[0] = 0;
3142         view->update_secs = 0;
3145 static const char *
3146 format_arg(const char *name)
3148         static struct {
3149                 const char *name;
3150                 size_t namelen;
3151                 const char *value;
3152                 const char *value_if_empty;
3153         } vars[] = {
3154 #define FORMAT_VAR(name, value, value_if_empty) \
3155         { name, STRING_SIZE(name), value, value_if_empty }
3156                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3157                 FORMAT_VAR("%(file)",           opt_file,       ""),
3158                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3159                 FORMAT_VAR("%(head)",           ref_head,       ""),
3160                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3161                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3162                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3163         };
3164         int i;
3166         for (i = 0; i < ARRAY_SIZE(vars); i++)
3167                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3168                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3170         report("Unknown replacement: `%s`", name);
3171         return NULL;
3174 static bool
3175 format_argv(const char *dst_argv[], const char *src_argv[], bool replace)
3177         char buf[SIZEOF_STR];
3178         int argc;
3180         argv_free(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 || !replace) {
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 && replace ? 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_io(struct view *view, const char *dir, const char *argv[], bool replace)
3268         io_init(&view->io, dir, IO_RD);
3269         return format_argv(view->io.argv, argv, replace);
3272 static bool
3273 prepare_update(struct view *view, const char *argv[], const char *dir)
3275         if (view->pipe)
3276                 end_update(view, TRUE);
3277         return prepare_io(view, dir, argv, FALSE);
3280 static bool
3281 start_update(struct view *view, const char **argv, const char *dir)
3283         if (view->pipe)
3284                 io_done(view->pipe);
3285         return prepare_io(view, dir, argv, FALSE) &&
3286                io_start(&view->io);
3289 static bool
3290 prepare_update_file(struct view *view, const char *name)
3292         if (view->pipe)
3293                 end_update(view, TRUE);
3294         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3297 static bool
3298 begin_update(struct view *view, bool refresh)
3300         if (view->pipe)
3301                 end_update(view, TRUE);
3303         if (!refresh) {
3304                 if (view->ops->prepare) {
3305                         if (!view->ops->prepare(view))
3306                                 return FALSE;
3307                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3308                         return FALSE;
3309                 }
3311                 /* Put the current ref_* value to the view title ref
3312                  * member. This is needed by the blob view. Most other
3313                  * views sets it automatically after loading because the
3314                  * first line is a commit line. */
3315                 string_copy_rev(view->ref, view->id);
3316         }
3318         if (!io_start(&view->io))
3319                 return FALSE;
3321         setup_update(view, view->id);
3323         return TRUE;
3326 static bool
3327 update_view(struct view *view)
3329         char out_buffer[BUFSIZ * 2];
3330         char *line;
3331         /* Clear the view and redraw everything since the tree sorting
3332          * might have rearranged things. */
3333         bool redraw = view->lines == 0;
3334         bool can_read = TRUE;
3336         if (!view->pipe)
3337                 return TRUE;
3339         if (!io_can_read(view->pipe)) {
3340                 if (view->lines == 0 && view_is_displayed(view)) {
3341                         time_t secs = time(NULL) - view->start_time;
3343                         if (secs > 1 && secs > view->update_secs) {
3344                                 if (view->update_secs == 0)
3345                                         redraw_view(view);
3346                                 update_view_title(view);
3347                                 view->update_secs = secs;
3348                         }
3349                 }
3350                 return TRUE;
3351         }
3353         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3354                 if (opt_iconv_in != ICONV_NONE) {
3355                         ICONV_CONST char *inbuf = line;
3356                         size_t inlen = strlen(line) + 1;
3358                         char *outbuf = out_buffer;
3359                         size_t outlen = sizeof(out_buffer);
3361                         size_t ret;
3363                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3364                         if (ret != (size_t) -1)
3365                                 line = out_buffer;
3366                 }
3368                 if (!view->ops->read(view, line)) {
3369                         report("Allocation failure");
3370                         end_update(view, TRUE);
3371                         return FALSE;
3372                 }
3373         }
3375         {
3376                 unsigned long lines = view->lines;
3377                 int digits;
3379                 for (digits = 0; lines; digits++)
3380                         lines /= 10;
3382                 /* Keep the displayed view in sync with line number scaling. */
3383                 if (digits != view->digits) {
3384                         view->digits = digits;
3385                         if (opt_line_number || view->type == VIEW_BLAME)
3386                                 redraw = TRUE;
3387                 }
3388         }
3390         if (io_error(view->pipe)) {
3391                 report("Failed to read: %s", io_strerror(view->pipe));
3392                 end_update(view, TRUE);
3394         } else if (io_eof(view->pipe)) {
3395                 if (view_is_displayed(view))
3396                         report("");
3397                 end_update(view, FALSE);
3398         }
3400         if (restore_view_position(view))
3401                 redraw = TRUE;
3403         if (!view_is_displayed(view))
3404                 return TRUE;
3406         if (redraw)
3407                 redraw_view_from(view, 0);
3408         else
3409                 redraw_view_dirty(view);
3411         /* Update the title _after_ the redraw so that if the redraw picks up a
3412          * commit reference in view->ref it'll be available here. */
3413         update_view_title(view);
3414         return TRUE;
3417 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3419 static struct line *
3420 add_line_data(struct view *view, void *data, enum line_type type)
3422         struct line *line;
3424         if (!realloc_lines(&view->line, view->lines, 1))
3425                 return NULL;
3427         line = &view->line[view->lines++];
3428         memset(line, 0, sizeof(*line));
3429         line->type = type;
3430         line->data = data;
3431         line->dirty = 1;
3433         return line;
3436 static struct line *
3437 add_line_text(struct view *view, const char *text, enum line_type type)
3439         char *data = text ? strdup(text) : NULL;
3441         return data ? add_line_data(view, data, type) : NULL;
3444 static struct line *
3445 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3447         char buf[SIZEOF_STR];
3448         va_list args;
3450         va_start(args, fmt);
3451         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3452                 buf[0] = 0;
3453         va_end(args);
3455         return buf[0] ? add_line_text(view, buf, type) : NULL;
3458 /*
3459  * View opening
3460  */
3462 enum open_flags {
3463         OPEN_DEFAULT = 0,       /* Use default view switching. */
3464         OPEN_SPLIT = 1,         /* Split current view. */
3465         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3466         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3467         OPEN_PREPARED = 32,     /* Open already prepared command. */
3468 };
3470 static void
3471 open_view(struct view *prev, enum request request, enum open_flags flags)
3473         bool split = !!(flags & OPEN_SPLIT);
3474         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3475         bool nomaximize = !!(flags & OPEN_REFRESH);
3476         struct view *view = VIEW(request);
3477         int nviews = displayed_views();
3478         struct view *base_view = display[0];
3480         if (view == prev && nviews == 1 && !reload) {
3481                 report("Already in %s view", view->name);
3482                 return;
3483         }
3485         if (view->git_dir && !opt_git_dir[0]) {
3486                 report("The %s view is disabled in pager view", view->name);
3487                 return;
3488         }
3490         if (split) {
3491                 display[1] = view;
3492                 current_view = 1;
3493                 view->parent = prev;
3494         } else if (!nomaximize) {
3495                 /* Maximize the current view. */
3496                 memset(display, 0, sizeof(display));
3497                 current_view = 0;
3498                 display[current_view] = view;
3499         }
3501         /* No prev signals that this is the first loaded view. */
3502         if (prev && view != prev) {
3503                 view->prev = prev;
3504         }
3506         /* Resize the view when switching between split- and full-screen,
3507          * or when switching between two different full-screen views. */
3508         if (nviews != displayed_views() ||
3509             (nviews == 1 && base_view != display[0]))
3510                 resize_display();
3512         if (view->ops->open) {
3513                 if (view->pipe)
3514                         end_update(view, TRUE);
3515                 if (!view->ops->open(view)) {
3516                         report("Failed to load %s view", view->name);
3517                         return;
3518                 }
3519                 restore_view_position(view);
3521         } else if ((reload || strcmp(view->vid, view->id)) &&
3522                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3523                 report("Failed to load %s view", view->name);
3524                 return;
3525         }
3527         if (split && prev->lineno - prev->offset >= prev->height) {
3528                 /* Take the title line into account. */
3529                 int lines = prev->lineno - prev->offset - prev->height + 1;
3531                 /* Scroll the view that was split if the current line is
3532                  * outside the new limited view. */
3533                 do_scroll_view(prev, lines);
3534         }
3536         if (prev && view != prev && split && view_is_displayed(prev)) {
3537                 /* "Blur" the previous view. */
3538                 update_view_title(prev);
3539         }
3541         if (view->pipe && view->lines == 0) {
3542                 /* Clear the old view and let the incremental updating refill
3543                  * the screen. */
3544                 werase(view->win);
3545                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3546                 report("");
3547         } else if (view_is_displayed(view)) {
3548                 redraw_view(view);
3549                 report("");
3550         }
3553 static void
3554 open_external_viewer(const char *argv[], const char *dir)
3556         def_prog_mode();           /* save current tty modes */
3557         endwin();                  /* restore original tty modes */
3558         io_run_fg(argv, dir);
3559         fprintf(stderr, "Press Enter to continue");
3560         getc(opt_tty);
3561         reset_prog_mode();
3562         redraw_display(TRUE);
3565 static void
3566 open_mergetool(const char *file)
3568         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3570         open_external_viewer(mergetool_argv, opt_cdup);
3573 static void
3574 open_editor(const char *file)
3576         const char *editor_argv[] = { "vi", file, NULL };
3577         const char *editor;
3579         editor = getenv("GIT_EDITOR");
3580         if (!editor && *opt_editor)
3581                 editor = opt_editor;
3582         if (!editor)
3583                 editor = getenv("VISUAL");
3584         if (!editor)
3585                 editor = getenv("EDITOR");
3586         if (!editor)
3587                 editor = "vi";
3589         editor_argv[0] = editor;
3590         open_external_viewer(editor_argv, opt_cdup);
3593 static void
3594 open_run_request(enum request request)
3596         struct run_request *req = get_run_request(request);
3597         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3599         if (!req) {
3600                 report("Unknown run request");
3601                 return;
3602         }
3604         if (format_argv(argv, req->argv, TRUE))
3605                 open_external_viewer(argv, NULL);
3606         argv_free(argv);
3609 /*
3610  * User request switch noodle
3611  */
3613 static int
3614 view_driver(struct view *view, enum request request)
3616         int i;
3618         if (request == REQ_NONE)
3619                 return TRUE;
3621         if (request > REQ_NONE) {
3622                 open_run_request(request);
3623                 view_request(view, REQ_REFRESH);
3624                 return TRUE;
3625         }
3627         request = view_request(view, request);
3628         if (request == REQ_NONE)
3629                 return TRUE;
3631         switch (request) {
3632         case REQ_MOVE_UP:
3633         case REQ_MOVE_DOWN:
3634         case REQ_MOVE_PAGE_UP:
3635         case REQ_MOVE_PAGE_DOWN:
3636         case REQ_MOVE_FIRST_LINE:
3637         case REQ_MOVE_LAST_LINE:
3638                 move_view(view, request);
3639                 break;
3641         case REQ_SCROLL_LEFT:
3642         case REQ_SCROLL_RIGHT:
3643         case REQ_SCROLL_LINE_DOWN:
3644         case REQ_SCROLL_LINE_UP:
3645         case REQ_SCROLL_PAGE_DOWN:
3646         case REQ_SCROLL_PAGE_UP:
3647                 scroll_view(view, request);
3648                 break;
3650         case REQ_VIEW_BLAME:
3651                 if (!opt_file[0]) {
3652                         report("No file chosen, press %s to open tree view",
3653                                get_key(view->keymap, REQ_VIEW_TREE));
3654                         break;
3655                 }
3656                 open_view(view, request, OPEN_DEFAULT);
3657                 break;
3659         case REQ_VIEW_BLOB:
3660                 if (!ref_blob[0]) {
3661                         report("No file chosen, press %s to open tree view",
3662                                get_key(view->keymap, REQ_VIEW_TREE));
3663                         break;
3664                 }
3665                 open_view(view, request, OPEN_DEFAULT);
3666                 break;
3668         case REQ_VIEW_PAGER:
3669                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3670                         report("No pager content, press %s to run command from prompt",
3671                                get_key(view->keymap, REQ_PROMPT));
3672                         break;
3673                 }
3674                 open_view(view, request, OPEN_DEFAULT);
3675                 break;
3677         case REQ_VIEW_STAGE:
3678                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3679                         report("No stage content, press %s to open the status view and choose file",
3680                                get_key(view->keymap, REQ_VIEW_STATUS));
3681                         break;
3682                 }
3683                 open_view(view, request, OPEN_DEFAULT);
3684                 break;
3686         case REQ_VIEW_STATUS:
3687                 if (opt_is_inside_work_tree == FALSE) {
3688                         report("The status view requires a working tree");
3689                         break;
3690                 }
3691                 open_view(view, request, OPEN_DEFAULT);
3692                 break;
3694         case REQ_VIEW_MAIN:
3695         case REQ_VIEW_DIFF:
3696         case REQ_VIEW_LOG:
3697         case REQ_VIEW_TREE:
3698         case REQ_VIEW_HELP:
3699         case REQ_VIEW_BRANCH:
3700                 open_view(view, request, OPEN_DEFAULT);
3701                 break;
3703         case REQ_NEXT:
3704         case REQ_PREVIOUS:
3705                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3707                 if (view->parent) {
3708                         int line;
3710                         view = view->parent;
3711                         line = view->lineno;
3712                         move_view(view, request);
3713                         if (view_is_displayed(view))
3714                                 update_view_title(view);
3715                         if (line != view->lineno)
3716                                 view_request(view, REQ_ENTER);
3717                 } else {
3718                         move_view(view, request);
3719                 }
3720                 break;
3722         case REQ_VIEW_NEXT:
3723         {
3724                 int nviews = displayed_views();
3725                 int next_view = (current_view + 1) % nviews;
3727                 if (next_view == current_view) {
3728                         report("Only one view is displayed");
3729                         break;
3730                 }
3732                 current_view = next_view;
3733                 /* Blur out the title of the previous view. */
3734                 update_view_title(view);
3735                 report("");
3736                 break;
3737         }
3738         case REQ_REFRESH:
3739                 report("Refreshing is not yet supported for the %s view", view->name);
3740                 break;
3742         case REQ_MAXIMIZE:
3743                 if (displayed_views() == 2)
3744                         maximize_view(view);
3745                 break;
3747         case REQ_OPTIONS:
3748                 open_option_menu();
3749                 break;
3751         case REQ_TOGGLE_LINENO:
3752                 toggle_view_option(&opt_line_number, "line numbers");
3753                 break;
3755         case REQ_TOGGLE_DATE:
3756                 toggle_date();
3757                 break;
3759         case REQ_TOGGLE_AUTHOR:
3760                 toggle_author();
3761                 break;
3763         case REQ_TOGGLE_REV_GRAPH:
3764                 toggle_view_option(&opt_rev_graph, "revision graph display");
3765                 break;
3767         case REQ_TOGGLE_REFS:
3768                 toggle_view_option(&opt_show_refs, "reference display");
3769                 break;
3771         case REQ_TOGGLE_SORT_FIELD:
3772         case REQ_TOGGLE_SORT_ORDER:
3773                 report("Sorting is not yet supported for the %s view", view->name);
3774                 break;
3776         case REQ_SEARCH:
3777         case REQ_SEARCH_BACK:
3778                 search_view(view, request);
3779                 break;
3781         case REQ_FIND_NEXT:
3782         case REQ_FIND_PREV:
3783                 find_next(view, request);
3784                 break;
3786         case REQ_STOP_LOADING:
3787                 foreach_view(view, i) {
3788                         if (view->pipe)
3789                                 report("Stopped loading the %s view", view->name),
3790                         end_update(view, TRUE);
3791                 }
3792                 break;
3794         case REQ_SHOW_VERSION:
3795                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3796                 return TRUE;
3798         case REQ_SCREEN_REDRAW:
3799                 redraw_display(TRUE);
3800                 break;
3802         case REQ_EDIT:
3803                 report("Nothing to edit");
3804                 break;
3806         case REQ_ENTER:
3807                 report("Nothing to enter");
3808                 break;
3810         case REQ_VIEW_CLOSE:
3811                 /* XXX: Mark closed views by letting view->prev point to the
3812                  * view itself. Parents to closed view should never be
3813                  * followed. */
3814                 if (view->prev && view->prev != view) {
3815                         maximize_view(view->prev);
3816                         view->prev = view;
3817                         break;
3818                 }
3819                 /* Fall-through */
3820         case REQ_QUIT:
3821                 return FALSE;
3823         default:
3824                 report("Unknown key, press %s for help",
3825                        get_key(view->keymap, REQ_VIEW_HELP));
3826                 return TRUE;
3827         }
3829         return TRUE;
3833 /*
3834  * View backend utilities
3835  */
3837 enum sort_field {
3838         ORDERBY_NAME,
3839         ORDERBY_DATE,
3840         ORDERBY_AUTHOR,
3841 };
3843 struct sort_state {
3844         const enum sort_field *fields;
3845         size_t size, current;
3846         bool reverse;
3847 };
3849 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3850 #define get_sort_field(state) ((state).fields[(state).current])
3851 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3853 static void
3854 sort_view(struct view *view, enum request request, struct sort_state *state,
3855           int (*compare)(const void *, const void *))
3857         switch (request) {
3858         case REQ_TOGGLE_SORT_FIELD:
3859                 state->current = (state->current + 1) % state->size;
3860                 break;
3862         case REQ_TOGGLE_SORT_ORDER:
3863                 state->reverse = !state->reverse;
3864                 break;
3865         default:
3866                 die("Not a sort request");
3867         }
3869         qsort(view->line, view->lines, sizeof(*view->line), compare);
3870         redraw_view(view);
3873 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3875 /* Small author cache to reduce memory consumption. It uses binary
3876  * search to lookup or find place to position new entries. No entries
3877  * are ever freed. */
3878 static const char *
3879 get_author(const char *name)
3881         static const char **authors;
3882         static size_t authors_size;
3883         int from = 0, to = authors_size - 1;
3885         while (from <= to) {
3886                 size_t pos = (to + from) / 2;
3887                 int cmp = strcmp(name, authors[pos]);
3889                 if (!cmp)
3890                         return authors[pos];
3892                 if (cmp < 0)
3893                         to = pos - 1;
3894                 else
3895                         from = pos + 1;
3896         }
3898         if (!realloc_authors(&authors, authors_size, 1))
3899                 return NULL;
3900         name = strdup(name);
3901         if (!name)
3902                 return NULL;
3904         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3905         authors[from] = name;
3906         authors_size++;
3908         return name;
3911 static void
3912 parse_timesec(struct time *time, const char *sec)
3914         time->sec = (time_t) atol(sec);
3917 static void
3918 parse_timezone(struct time *time, const char *zone)
3920         long tz;
3922         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3923         tz += ('0' - zone[2]) * 60 * 60;
3924         tz += ('0' - zone[3]) * 60 * 10;
3925         tz += ('0' - zone[4]) * 60;
3927         if (zone[0] == '-')
3928                 tz = -tz;
3930         time->tz = tz;
3931         time->sec -= tz;
3934 /* Parse author lines where the name may be empty:
3935  *      author  <email@address.tld> 1138474660 +0100
3936  */
3937 static void
3938 parse_author_line(char *ident, const char **author, struct time *time)
3940         char *nameend = strchr(ident, '<');
3941         char *emailend = strchr(ident, '>');
3943         if (nameend && emailend)
3944                 *nameend = *emailend = 0;
3945         ident = chomp_string(ident);
3946         if (!*ident) {
3947                 if (nameend)
3948                         ident = chomp_string(nameend + 1);
3949                 if (!*ident)
3950                         ident = "Unknown";
3951         }
3953         *author = get_author(ident);
3955         /* Parse epoch and timezone */
3956         if (emailend && emailend[1] == ' ') {
3957                 char *secs = emailend + 2;
3958                 char *zone = strchr(secs, ' ');
3960                 parse_timesec(time, secs);
3962                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3963                         parse_timezone(time, zone + 1);
3964         }
3967 static bool
3968 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3970         char rev[SIZEOF_REV];
3971         const char *revlist_argv[] = {
3972                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3973         };
3974         struct menu_item *items;
3975         char text[SIZEOF_STR];
3976         bool ok = TRUE;
3977         int i;
3979         items = calloc(*parents + 1, sizeof(*items));
3980         if (!items)
3981                 return FALSE;
3983         for (i = 0; i < *parents; i++) {
3984                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3985                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3986                     !(items[i].text = strdup(text))) {
3987                         ok = FALSE;
3988                         break;
3989                 }
3990         }
3992         if (ok) {
3993                 *parents = 0;
3994                 ok = prompt_menu("Select parent", items, parents);
3995         }
3996         for (i = 0; items[i].text; i++)
3997                 free((char *) items[i].text);
3998         free(items);
3999         return ok;
4002 static bool
4003 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4005         char buf[SIZEOF_STR * 4];
4006         const char *revlist_argv[] = {
4007                 "git", "log", "--no-color", "-1",
4008                         "--pretty=format:%P", id, "--", path, NULL
4009         };
4010         int parents;
4012         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4013             (parents = strlen(buf) / 40) < 0) {
4014                 report("Failed to get parent information");
4015                 return FALSE;
4017         } else if (parents == 0) {
4018                 if (path)
4019                         report("Path '%s' does not exist in the parent", path);
4020                 else
4021                         report("The selected commit has no parents");
4022                 return FALSE;
4023         }
4025         if (parents == 1)
4026                 parents = 0;
4027         else if (!open_commit_parent_menu(buf, &parents))
4028                 return FALSE;
4030         string_copy_rev(rev, &buf[41 * parents]);
4031         return TRUE;
4034 /*
4035  * Pager backend
4036  */
4038 static bool
4039 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4041         char text[SIZEOF_STR];
4043         if (opt_line_number && draw_lineno(view, lineno))
4044                 return TRUE;
4046         string_expand(text, sizeof(text), line->data, opt_tab_size);
4047         draw_text(view, line->type, text, TRUE);
4048         return TRUE;
4051 static bool
4052 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4054         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4055         char ref[SIZEOF_STR];
4057         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4058                 return TRUE;
4060         /* This is the only fatal call, since it can "corrupt" the buffer. */
4061         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4062                 return FALSE;
4064         return TRUE;
4067 static void
4068 add_pager_refs(struct view *view, struct line *line)
4070         char buf[SIZEOF_STR];
4071         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4072         struct ref_list *list;
4073         size_t bufpos = 0, i;
4074         const char *sep = "Refs: ";
4075         bool is_tag = FALSE;
4077         assert(line->type == LINE_COMMIT);
4079         list = get_ref_list(commit_id);
4080         if (!list) {
4081                 if (view->type == VIEW_DIFF)
4082                         goto try_add_describe_ref;
4083                 return;
4084         }
4086         for (i = 0; i < list->size; i++) {
4087                 struct ref *ref = list->refs[i];
4088                 const char *fmt = ref->tag    ? "%s[%s]" :
4089                                   ref->remote ? "%s<%s>" : "%s%s";
4091                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4092                         return;
4093                 sep = ", ";
4094                 if (ref->tag)
4095                         is_tag = TRUE;
4096         }
4098         if (!is_tag && view->type == VIEW_DIFF) {
4099 try_add_describe_ref:
4100                 /* Add <tag>-g<commit_id> "fake" reference. */
4101                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4102                         return;
4103         }
4105         if (bufpos == 0)
4106                 return;
4108         add_line_text(view, buf, LINE_PP_REFS);
4111 static bool
4112 pager_read(struct view *view, char *data)
4114         struct line *line;
4116         if (!data)
4117                 return TRUE;
4119         line = add_line_text(view, data, get_line_type(data));
4120         if (!line)
4121                 return FALSE;
4123         if (line->type == LINE_COMMIT &&
4124             (view->type == VIEW_DIFF ||
4125              view->type == VIEW_LOG))
4126                 add_pager_refs(view, line);
4128         return TRUE;
4131 static enum request
4132 pager_request(struct view *view, enum request request, struct line *line)
4134         int split = 0;
4136         if (request != REQ_ENTER)
4137                 return request;
4139         if (line->type == LINE_COMMIT &&
4140            (view->type == VIEW_LOG ||
4141             view->type == VIEW_PAGER)) {
4142                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4143                 split = 1;
4144         }
4146         /* Always scroll the view even if it was split. That way
4147          * you can use Enter to scroll through the log view and
4148          * split open each commit diff. */
4149         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4151         /* FIXME: A minor workaround. Scrolling the view will call report("")
4152          * but if we are scrolling a non-current view this won't properly
4153          * update the view title. */
4154         if (split)
4155                 update_view_title(view);
4157         return REQ_NONE;
4160 static bool
4161 pager_grep(struct view *view, struct line *line)
4163         const char *text[] = { line->data, NULL };
4165         return grep_text(view, text);
4168 static void
4169 pager_select(struct view *view, struct line *line)
4171         if (line->type == LINE_COMMIT) {
4172                 char *text = (char *)line->data + STRING_SIZE("commit ");
4174                 if (view->type != VIEW_PAGER)
4175                         string_copy_rev(view->ref, text);
4176                 string_copy_rev(ref_commit, text);
4177         }
4180 static struct view_ops pager_ops = {
4181         "line",
4182         NULL,
4183         NULL,
4184         pager_read,
4185         pager_draw,
4186         pager_request,
4187         pager_grep,
4188         pager_select,
4189 };
4191 static const char *log_argv[SIZEOF_ARG] = {
4192         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4193 };
4195 static enum request
4196 log_request(struct view *view, enum request request, struct line *line)
4198         switch (request) {
4199         case REQ_REFRESH:
4200                 load_refs();
4201                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4202                 return REQ_NONE;
4203         default:
4204                 return pager_request(view, request, line);
4205         }
4208 static struct view_ops log_ops = {
4209         "line",
4210         log_argv,
4211         NULL,
4212         pager_read,
4213         pager_draw,
4214         log_request,
4215         pager_grep,
4216         pager_select,
4217 };
4219 static const char *diff_argv[SIZEOF_ARG] = {
4220         "git", "show", "--pretty=fuller", "--no-color", "--root",
4221                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4222 };
4224 static struct view_ops diff_ops = {
4225         "line",
4226         diff_argv,
4227         NULL,
4228         pager_read,
4229         pager_draw,
4230         pager_request,
4231         pager_grep,
4232         pager_select,
4233 };
4235 /*
4236  * Help backend
4237  */
4239 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4241 static bool
4242 help_open_keymap_title(struct view *view, enum keymap keymap)
4244         struct line *line;
4246         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4247                                help_keymap_hidden[keymap] ? '+' : '-',
4248                                enum_name(keymap_table[keymap]));
4249         if (line)
4250                 line->other = keymap;
4252         return help_keymap_hidden[keymap];
4255 static void
4256 help_open_keymap(struct view *view, enum keymap keymap)
4258         const char *group = NULL;
4259         char buf[SIZEOF_STR];
4260         size_t bufpos;
4261         bool add_title = TRUE;
4262         int i;
4264         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4265                 const char *key = NULL;
4267                 if (req_info[i].request == REQ_NONE)
4268                         continue;
4270                 if (!req_info[i].request) {
4271                         group = req_info[i].help;
4272                         continue;
4273                 }
4275                 key = get_keys(keymap, req_info[i].request, TRUE);
4276                 if (!key || !*key)
4277                         continue;
4279                 if (add_title && help_open_keymap_title(view, keymap))
4280                         return;
4281                 add_title = FALSE;
4283                 if (group) {
4284                         add_line_text(view, group, LINE_HELP_GROUP);
4285                         group = NULL;
4286                 }
4288                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4289                                 enum_name(req_info[i]), req_info[i].help);
4290         }
4292         group = "External commands:";
4294         for (i = 0; i < run_requests; i++) {
4295                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4296                 const char *key;
4297                 int argc;
4299                 if (!req || req->keymap != keymap)
4300                         continue;
4302                 key = get_key_name(req->key);
4303                 if (!*key)
4304                         key = "(no key defined)";
4306                 if (add_title && help_open_keymap_title(view, keymap))
4307                         return;
4308                 if (group) {
4309                         add_line_text(view, group, LINE_HELP_GROUP);
4310                         group = NULL;
4311                 }
4313                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4314                         if (!string_format_from(buf, &bufpos, "%s%s",
4315                                                 argc ? " " : "", req->argv[argc]))
4316                                 return;
4318                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4319         }
4322 static bool
4323 help_open(struct view *view)
4325         enum keymap keymap;
4327         reset_view(view);
4328         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4329         add_line_text(view, "", LINE_DEFAULT);
4331         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4332                 help_open_keymap(view, keymap);
4334         return TRUE;
4337 static enum request
4338 help_request(struct view *view, enum request request, struct line *line)
4340         switch (request) {
4341         case REQ_ENTER:
4342                 if (line->type == LINE_HELP_KEYMAP) {
4343                         help_keymap_hidden[line->other] =
4344                                 !help_keymap_hidden[line->other];
4345                         view->p_restore = TRUE;
4346                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4347                 }
4349                 return REQ_NONE;
4350         default:
4351                 return pager_request(view, request, line);
4352         }
4355 static struct view_ops help_ops = {
4356         "line",
4357         NULL,
4358         help_open,
4359         NULL,
4360         pager_draw,
4361         help_request,
4362         pager_grep,
4363         pager_select,
4364 };
4367 /*
4368  * Tree backend
4369  */
4371 struct tree_stack_entry {
4372         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4373         unsigned long lineno;           /* Line number to restore */
4374         char *name;                     /* Position of name in opt_path */
4375 };
4377 /* The top of the path stack. */
4378 static struct tree_stack_entry *tree_stack = NULL;
4379 unsigned long tree_lineno = 0;
4381 static void
4382 pop_tree_stack_entry(void)
4384         struct tree_stack_entry *entry = tree_stack;
4386         tree_lineno = entry->lineno;
4387         entry->name[0] = 0;
4388         tree_stack = entry->prev;
4389         free(entry);
4392 static void
4393 push_tree_stack_entry(const char *name, unsigned long lineno)
4395         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4396         size_t pathlen = strlen(opt_path);
4398         if (!entry)
4399                 return;
4401         entry->prev = tree_stack;
4402         entry->name = opt_path + pathlen;
4403         tree_stack = entry;
4405         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4406                 pop_tree_stack_entry();
4407                 return;
4408         }
4410         /* Move the current line to the first tree entry. */
4411         tree_lineno = 1;
4412         entry->lineno = lineno;
4415 /* Parse output from git-ls-tree(1):
4416  *
4417  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4418  */
4420 #define SIZEOF_TREE_ATTR \
4421         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4423 #define SIZEOF_TREE_MODE \
4424         STRING_SIZE("100644 ")
4426 #define TREE_ID_OFFSET \
4427         STRING_SIZE("100644 blob ")
4429 struct tree_entry {
4430         char id[SIZEOF_REV];
4431         mode_t mode;
4432         struct time time;               /* Date from the author ident. */
4433         const char *author;             /* Author of the commit. */
4434         char name[1];
4435 };
4437 static const char *
4438 tree_path(const struct line *line)
4440         return ((struct tree_entry *) line->data)->name;
4443 static int
4444 tree_compare_entry(const struct line *line1, const struct line *line2)
4446         if (line1->type != line2->type)
4447                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4448         return strcmp(tree_path(line1), tree_path(line2));
4451 static const enum sort_field tree_sort_fields[] = {
4452         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4453 };
4454 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4456 static int
4457 tree_compare(const void *l1, const void *l2)
4459         const struct line *line1 = (const struct line *) l1;
4460         const struct line *line2 = (const struct line *) l2;
4461         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4462         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4464         if (line1->type == LINE_TREE_HEAD)
4465                 return -1;
4466         if (line2->type == LINE_TREE_HEAD)
4467                 return 1;
4469         switch (get_sort_field(tree_sort_state)) {
4470         case ORDERBY_DATE:
4471                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4473         case ORDERBY_AUTHOR:
4474                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4476         case ORDERBY_NAME:
4477         default:
4478                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4479         }
4483 static struct line *
4484 tree_entry(struct view *view, enum line_type type, const char *path,
4485            const char *mode, const char *id)
4487         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4488         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4490         if (!entry || !line) {
4491                 free(entry);
4492                 return NULL;
4493         }
4495         strncpy(entry->name, path, strlen(path));
4496         if (mode)
4497                 entry->mode = strtoul(mode, NULL, 8);
4498         if (id)
4499                 string_copy_rev(entry->id, id);
4501         return line;
4504 static bool
4505 tree_read_date(struct view *view, char *text, bool *read_date)
4507         static const char *author_name;
4508         static struct time author_time;
4510         if (!text && *read_date) {
4511                 *read_date = FALSE;
4512                 return TRUE;
4514         } else if (!text) {
4515                 char *path = *opt_path ? opt_path : ".";
4516                 /* Find next entry to process */
4517                 const char *log_file[] = {
4518                         "git", "log", "--no-color", "--pretty=raw",
4519                                 "--cc", "--raw", view->id, "--", path, NULL
4520                 };
4522                 if (!view->lines) {
4523                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4524                         report("Tree is empty");
4525                         return TRUE;
4526                 }
4528                 if (!start_update(view, log_file, opt_cdup)) {
4529                         report("Failed to load tree data");
4530                         return TRUE;
4531                 }
4533                 *read_date = TRUE;
4534                 return FALSE;
4536         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4537                 parse_author_line(text + STRING_SIZE("author "),
4538                                   &author_name, &author_time);
4540         } else if (*text == ':') {
4541                 char *pos;
4542                 size_t annotated = 1;
4543                 size_t i;
4545                 pos = strchr(text, '\t');
4546                 if (!pos)
4547                         return TRUE;
4548                 text = pos + 1;
4549                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4550                         text += strlen(opt_path);
4551                 pos = strchr(text, '/');
4552                 if (pos)
4553                         *pos = 0;
4555                 for (i = 1; i < view->lines; i++) {
4556                         struct line *line = &view->line[i];
4557                         struct tree_entry *entry = line->data;
4559                         annotated += !!entry->author;
4560                         if (entry->author || strcmp(entry->name, text))
4561                                 continue;
4563                         entry->author = author_name;
4564                         entry->time = author_time;
4565                         line->dirty = 1;
4566                         break;
4567                 }
4569                 if (annotated == view->lines)
4570                         io_kill(view->pipe);
4571         }
4572         return TRUE;
4575 static bool
4576 tree_read(struct view *view, char *text)
4578         static bool read_date = FALSE;
4579         struct tree_entry *data;
4580         struct line *entry, *line;
4581         enum line_type type;
4582         size_t textlen = text ? strlen(text) : 0;
4583         char *path = text + SIZEOF_TREE_ATTR;
4585         if (read_date || !text)
4586                 return tree_read_date(view, text, &read_date);
4588         if (textlen <= SIZEOF_TREE_ATTR)
4589                 return FALSE;
4590         if (view->lines == 0 &&
4591             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4592                 return FALSE;
4594         /* Strip the path part ... */
4595         if (*opt_path) {
4596                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4597                 size_t striplen = strlen(opt_path);
4599                 if (pathlen > striplen)
4600                         memmove(path, path + striplen,
4601                                 pathlen - striplen + 1);
4603                 /* Insert "link" to parent directory. */
4604                 if (view->lines == 1 &&
4605                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4606                         return FALSE;
4607         }
4609         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4610         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4611         if (!entry)
4612                 return FALSE;
4613         data = entry->data;
4615         /* Skip "Directory ..." and ".." line. */
4616         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4617                 if (tree_compare_entry(line, entry) <= 0)
4618                         continue;
4620                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4622                 line->data = data;
4623                 line->type = type;
4624                 for (; line <= entry; line++)
4625                         line->dirty = line->cleareol = 1;
4626                 return TRUE;
4627         }
4629         if (tree_lineno > view->lineno) {
4630                 view->lineno = tree_lineno;
4631                 tree_lineno = 0;
4632         }
4634         return TRUE;
4637 static bool
4638 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4640         struct tree_entry *entry = line->data;
4642         if (line->type == LINE_TREE_HEAD) {
4643                 if (draw_text(view, line->type, "Directory path /", TRUE))
4644                         return TRUE;
4645         } else {
4646                 if (draw_mode(view, entry->mode))
4647                         return TRUE;
4649                 if (opt_author && draw_author(view, entry->author))
4650                         return TRUE;
4652                 if (opt_date && draw_date(view, &entry->time))
4653                         return TRUE;
4654         }
4655         if (draw_text(view, line->type, entry->name, TRUE))
4656                 return TRUE;
4657         return TRUE;
4660 static void
4661 open_blob_editor(const char *id)
4663         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4664         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4665         int fd = mkstemp(file);
4667         if (fd == -1)
4668                 report("Failed to create temporary file");
4669         else if (!io_run_append(blob_argv, fd))
4670                 report("Failed to save blob data to file");
4671         else
4672                 open_editor(file);
4673         if (fd != -1)
4674                 unlink(file);
4677 static enum request
4678 tree_request(struct view *view, enum request request, struct line *line)
4680         enum open_flags flags;
4681         struct tree_entry *entry = line->data;
4683         switch (request) {
4684         case REQ_VIEW_BLAME:
4685                 if (line->type != LINE_TREE_FILE) {
4686                         report("Blame only supported for files");
4687                         return REQ_NONE;
4688                 }
4690                 string_copy(opt_ref, view->vid);
4691                 return request;
4693         case REQ_EDIT:
4694                 if (line->type != LINE_TREE_FILE) {
4695                         report("Edit only supported for files");
4696                 } else if (!is_head_commit(view->vid)) {
4697                         open_blob_editor(entry->id);
4698                 } else {
4699                         open_editor(opt_file);
4700                 }
4701                 return REQ_NONE;
4703         case REQ_TOGGLE_SORT_FIELD:
4704         case REQ_TOGGLE_SORT_ORDER:
4705                 sort_view(view, request, &tree_sort_state, tree_compare);
4706                 return REQ_NONE;
4708         case REQ_PARENT:
4709                 if (!*opt_path) {
4710                         /* quit view if at top of tree */
4711                         return REQ_VIEW_CLOSE;
4712                 }
4713                 /* fake 'cd  ..' */
4714                 line = &view->line[1];
4715                 break;
4717         case REQ_ENTER:
4718                 break;
4720         default:
4721                 return request;
4722         }
4724         /* Cleanup the stack if the tree view is at a different tree. */
4725         while (!*opt_path && tree_stack)
4726                 pop_tree_stack_entry();
4728         switch (line->type) {
4729         case LINE_TREE_DIR:
4730                 /* Depending on whether it is a subdirectory or parent link
4731                  * mangle the path buffer. */
4732                 if (line == &view->line[1] && *opt_path) {
4733                         pop_tree_stack_entry();
4735                 } else {
4736                         const char *basename = tree_path(line);
4738                         push_tree_stack_entry(basename, view->lineno);
4739                 }
4741                 /* Trees and subtrees share the same ID, so they are not not
4742                  * unique like blobs. */
4743                 flags = OPEN_RELOAD;
4744                 request = REQ_VIEW_TREE;
4745                 break;
4747         case LINE_TREE_FILE:
4748                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4749                 request = REQ_VIEW_BLOB;
4750                 break;
4752         default:
4753                 return REQ_NONE;
4754         }
4756         open_view(view, request, flags);
4757         if (request == REQ_VIEW_TREE)
4758                 view->lineno = tree_lineno;
4760         return REQ_NONE;
4763 static bool
4764 tree_grep(struct view *view, struct line *line)
4766         struct tree_entry *entry = line->data;
4767         const char *text[] = {
4768                 entry->name,
4769                 opt_author ? entry->author : "",
4770                 mkdate(&entry->time, opt_date),
4771                 NULL
4772         };
4774         return grep_text(view, text);
4777 static void
4778 tree_select(struct view *view, struct line *line)
4780         struct tree_entry *entry = line->data;
4782         if (line->type == LINE_TREE_FILE) {
4783                 string_copy_rev(ref_blob, entry->id);
4784                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4786         } else if (line->type != LINE_TREE_DIR) {
4787                 return;
4788         }
4790         string_copy_rev(view->ref, entry->id);
4793 static bool
4794 tree_prepare(struct view *view)
4796         if (view->lines == 0 && opt_prefix[0]) {
4797                 char *pos = opt_prefix;
4799                 while (pos && *pos) {
4800                         char *end = strchr(pos, '/');
4802                         if (end)
4803                                 *end = 0;
4804                         push_tree_stack_entry(pos, 0);
4805                         pos = end;
4806                         if (end) {
4807                                 *end = '/';
4808                                 pos++;
4809                         }
4810                 }
4812         } else if (strcmp(view->vid, view->id)) {
4813                 opt_path[0] = 0;
4814         }
4816         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4819 static const char *tree_argv[SIZEOF_ARG] = {
4820         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4821 };
4823 static struct view_ops tree_ops = {
4824         "file",
4825         tree_argv,
4826         NULL,
4827         tree_read,
4828         tree_draw,
4829         tree_request,
4830         tree_grep,
4831         tree_select,
4832         tree_prepare,
4833 };
4835 static bool
4836 blob_read(struct view *view, char *line)
4838         if (!line)
4839                 return TRUE;
4840         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4843 static enum request
4844 blob_request(struct view *view, enum request request, struct line *line)
4846         switch (request) {
4847         case REQ_EDIT:
4848                 open_blob_editor(view->vid);
4849                 return REQ_NONE;
4850         default:
4851                 return pager_request(view, request, line);
4852         }
4855 static const char *blob_argv[SIZEOF_ARG] = {
4856         "git", "cat-file", "blob", "%(blob)", NULL
4857 };
4859 static struct view_ops blob_ops = {
4860         "line",
4861         blob_argv,
4862         NULL,
4863         blob_read,
4864         pager_draw,
4865         blob_request,
4866         pager_grep,
4867         pager_select,
4868 };
4870 /*
4871  * Blame backend
4872  *
4873  * Loading the blame view is a two phase job:
4874  *
4875  *  1. File content is read either using opt_file from the
4876  *     filesystem or using git-cat-file.
4877  *  2. Then blame information is incrementally added by
4878  *     reading output from git-blame.
4879  */
4881 struct blame_commit {
4882         char id[SIZEOF_REV];            /* SHA1 ID. */
4883         char title[128];                /* First line of the commit message. */
4884         const char *author;             /* Author of the commit. */
4885         struct time time;               /* Date from the author ident. */
4886         char filename[128];             /* Name of file. */
4887         bool has_previous;              /* Was a "previous" line detected. */
4888 };
4890 struct blame {
4891         struct blame_commit *commit;
4892         unsigned long lineno;
4893         char text[1];
4894 };
4896 static bool
4897 blame_open(struct view *view)
4899         char path[SIZEOF_STR];
4901         if (!view->prev && *opt_prefix) {
4902                 string_copy(path, opt_file);
4903                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4904                         return FALSE;
4905         }
4907         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4908                 const char *blame_cat_file_argv[] = {
4909                         "git", "cat-file", "blob", path, NULL
4910                 };
4912                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4913                     !start_update(view, blame_cat_file_argv, opt_cdup))
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 *blame_argv[] = {
5004                         "git", "blame", "--incremental",
5005                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5006                 };
5008                 if (view->lines == 0 && !view->prev)
5009                         die("No blame exist for %s", view->vid);
5011                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5012                         report("Failed to load blame data");
5013                         return TRUE;
5014                 }
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 = view_is_displayed(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 (!start_update(view, branch_log, NULL)) {
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 : OPEN_DEFAULT;
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->prev)
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 = view_is_displayed(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;