Code

Move and rewrite io_format to become prepare_io
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int head:1;    /* Is it the current HEAD? */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int ltag:1;    /* If so, is the tag local? */
127         unsigned int remote:1;  /* Is it a remote ref? */
128         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129         char name[1];           /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         size_t size;            /* Number of refs. */
135         struct ref **refs;      /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151         INPUT_OK,
152         INPUT_SKIP,
153         INPUT_STOP,
154         INPUT_CANCEL
155 };
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163         int hotkey;
164         const char *text;
165         void *data;
166 };
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
170 /*
171  * Allocation helpers ... Entering macro hell to never be seen again.
172  */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
175 static type *                                                                   \
176 name(type **mem, size_t size, size_t increase)                                  \
177 {                                                                               \
178         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
179         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180         type *tmp = *mem;                                                       \
181                                                                                 \
182         if (mem == NULL || num_chunks != num_chunks_new) {                      \
183                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184                 if (tmp)                                                        \
185                         *mem = tmp;                                             \
186         }                                                                       \
187                                                                                 \
188         return tmp;                                                             \
191 /*
192  * String helpers
193  */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198         if (srclen > dstlen - 1)
199                 srclen = dstlen - 1;
201         strncpy(dst, src, srclen);
202         dst[srclen] = 0;
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211         string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
222         size_t size, pos;
224         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225                 if (src[pos] == '\t') {
226                         size_t expanded = tabsize - (size % tabsize);
228                         if (expanded + size >= dstlen - 1)
229                                 expanded = dstlen - size - 1;
230                         memcpy(dst + size, "        ", expanded);
231                         size += expanded;
232                 } else {
233                         dst[size++] = src[pos];
234                 }
235         }
237         dst[size] = 0;
240 static char *
241 chomp_string(char *name)
243         int namelen;
245         while (isspace(*name))
246                 name++;
248         namelen = strlen(name) - 1;
249         while (namelen > 0 && isspace(name[namelen]))
250                 name[namelen--] = 0;
252         return name;
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
258         va_list args;
259         size_t pos = bufpos ? *bufpos : 0;
261         va_start(args, fmt);
262         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263         va_end(args);
265         if (bufpos)
266                 *bufpos = pos;
268         return pos >= bufsize ? FALSE : TRUE;
271 #define string_format(buf, fmt, args...) \
272         string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275         string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
280         size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284         /* Diff-Header == DIFF_HEADER */
285         for (i = 0; i < len; i++) {
286                 if (toupper(str1[i]) == toupper(str2[i]))
287                         continue;
289                 if (string_enum_sep(str1[i]) &&
290                     string_enum_sep(str2[i]))
291                         continue;
293                 return str1[i] - str2[i];
294         }
296         return 0;
299 #define enum_equals(entry, str, len) \
300         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303         const char *name;
304         int namelen;
305         int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
313         static char buf[SIZEOF_STR];
314         int bufpos;
316         for (bufpos = 0; bufpos <= namelen; bufpos++) {
317                 buf[bufpos] = tolower(name[bufpos]);
318                 if (buf[bufpos] == '_')
319                         buf[bufpos] = '-';
320         }
322         buf[bufpos] = 0;
323         return buf;
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
331         size_t namelen = strlen(name);
332         int i;
334         for (i = 0; i < map_size; i++)
335                 if (enum_equals(map[i], name, namelen)) {
336                         *value = map[i].value;
337                         return TRUE;
338                 }
340         return FALSE;
343 #define map_enum(attr, map, name) \
344         map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347         strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
352         size_t len = slen >= 0 ? slen : strlen(str);
353         size_t suffixlen = strlen(suffix);
355         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
359 /*
360  * Unicode / UTF-8 handling
361  *
362  * NOTE: Much of the following code for dealing with Unicode is derived from
363  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
365  */
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
370         if (c >= 0x1100 &&
371            (c <= 0x115f                         /* Hangul Jamo */
372             || c == 0x2329
373             || c == 0x232a
374             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
375                                                 /* CJK ... Yi */
376             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
377             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
378             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
379             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
380             || (c >= 0xffe0  && c <= 0xffe6)
381             || (c >= 0x20000 && c <= 0x2fffd)
382             || (c >= 0x30000 && c <= 0x3fffd)))
383                 return 2;
385         if (c == '\t')
386                 return tab_size;
388         return 1;
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392  * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
396         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
397         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
398         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
399         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
400         2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
401         3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
402 };
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
407         int c = *(unsigned char *) string;
409         return utf8_bytes[c];
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
416         unsigned long unicode;
418         switch (length) {
419         case 1:
420                 unicode  =   string[0];
421                 break;
422         case 2:
423                 unicode  =  (string[0] & 0x1f) << 6;
424                 unicode +=  (string[1] & 0x3f);
425                 break;
426         case 3:
427                 unicode  =  (string[0] & 0x0f) << 12;
428                 unicode += ((string[1] & 0x3f) << 6);
429                 unicode +=  (string[2] & 0x3f);
430                 break;
431         case 4:
432                 unicode  =  (string[0] & 0x0f) << 18;
433                 unicode += ((string[1] & 0x3f) << 12);
434                 unicode += ((string[2] & 0x3f) << 6);
435                 unicode +=  (string[3] & 0x3f);
436                 break;
437         case 5:
438                 unicode  =  (string[0] & 0x0f) << 24;
439                 unicode += ((string[1] & 0x3f) << 18);
440                 unicode += ((string[2] & 0x3f) << 12);
441                 unicode += ((string[3] & 0x3f) << 6);
442                 unicode +=  (string[4] & 0x3f);
443                 break;
444         case 6:
445                 unicode  =  (string[0] & 0x01) << 30;
446                 unicode += ((string[1] & 0x3f) << 24);
447                 unicode += ((string[2] & 0x3f) << 18);
448                 unicode += ((string[3] & 0x3f) << 12);
449                 unicode += ((string[4] & 0x3f) << 6);
450                 unicode +=  (string[5] & 0x3f);
451                 break;
452         default:
453                 return 0;
454         }
456         /* Invalid characters could return the special 0xfffd value but NUL
457          * should be just as good. */
458         return unicode > 0xffff ? 0 : unicode;
461 /* Calculates how much of string can be shown within the given maximum width
462  * and sets trimmed parameter to non-zero value if all of string could not be
463  * shown. If the reserve flag is TRUE, it will reserve at least one
464  * trailing character, which can be useful when drawing a delimiter.
465  *
466  * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
470         const char *string = *start;
471         const char *end = strchr(string, '\0');
472         unsigned char last_bytes = 0;
473         size_t last_ucwidth = 0;
475         *width = 0;
476         *trimmed = 0;
478         while (string < end) {
479                 unsigned char bytes = utf8_char_length(string, end);
480                 size_t ucwidth;
481                 unsigned long unicode;
483                 if (string + bytes > end)
484                         break;
486                 /* Change representation to figure out whether
487                  * it is a single- or double-width character. */
489                 unicode = utf8_to_unicode(string, bytes);
490                 /* FIXME: Graceful handling of invalid Unicode character. */
491                 if (!unicode)
492                         break;
494                 ucwidth = unicode_width(unicode, tab_size);
495                 if (skip > 0) {
496                         skip -= ucwidth <= skip ? ucwidth : skip;
497                         *start += bytes;
498                 }
499                 *width  += ucwidth;
500                 if (*width > max_width) {
501                         *trimmed = 1;
502                         *width -= ucwidth;
503                         if (reserve && *width == max_width) {
504                                 string -= last_bytes;
505                                 *width -= last_ucwidth;
506                         }
507                         break;
508                 }
510                 string  += bytes;
511                 last_bytes = ucwidth ? bytes : 0;
512                 last_ucwidth = ucwidth;
513         }
515         return string - *start;
519 #define DATE_INFO \
520         DATE_(NO), \
521         DATE_(DEFAULT), \
522         DATE_(LOCAL), \
523         DATE_(RELATIVE), \
524         DATE_(SHORT)
526 enum date {
527 #define DATE_(name) DATE_##name
528         DATE_INFO
529 #undef  DATE_
530 };
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534         DATE_INFO
535 #undef  DATE_
536 };
538 struct time {
539         time_t sec;
540         int tz;
541 };
543 static inline int timecmp(const struct time *t1, const struct time *t2)
545         return t1->sec - t2->sec;
548 static const char *
549 mkdate(const struct time *time, enum date date)
551         static char buf[DATE_COLS + 1];
552         static const struct enum_map reldate[] = {
553                 { "second", 1,                  60 * 2 },
554                 { "minute", 60,                 60 * 60 * 2 },
555                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
556                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
557                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
558                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
559         };
560         struct tm tm;
562         if (!date || !time || !time->sec)
563                 return "";
565         if (date == DATE_RELATIVE) {
566                 struct timeval now;
567                 time_t date = time->sec + time->tz;
568                 time_t seconds;
569                 int i;
571                 gettimeofday(&now, NULL);
572                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
573                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
574                         if (seconds >= reldate[i].value)
575                                 continue;
577                         seconds /= reldate[i].namelen;
578                         if (!string_format(buf, "%ld %s%s %s",
579                                            seconds, reldate[i].name,
580                                            seconds > 1 ? "s" : "",
581                                            now.tv_sec >= date ? "ago" : "ahead"))
582                                 break;
583                         return buf;
584                 }
585         }
587         if (date == DATE_LOCAL) {
588                 time_t date = time->sec + time->tz;
589                 localtime_r(&date, &tm);
590         }
591         else {
592                 gmtime_r(&time->sec, &tm);
593         }
594         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
598 #define AUTHOR_VALUES \
599         AUTHOR_(NO), \
600         AUTHOR_(FULL), \
601         AUTHOR_(ABBREVIATED)
603 enum author {
604 #define AUTHOR_(name) AUTHOR_##name
605         AUTHOR_VALUES,
606 #undef  AUTHOR_
607         AUTHOR_DEFAULT = AUTHOR_FULL
608 };
610 static const struct enum_map author_map[] = {
611 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
612         AUTHOR_VALUES
613 #undef  AUTHOR_
614 };
616 static const char *
617 get_author_initials(const char *author)
619         static char initials[AUTHOR_COLS * 6 + 1];
620         size_t pos = 0;
621         const char *end = strchr(author, '\0');
623 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
625         memset(initials, 0, sizeof(initials));
626         while (author < end) {
627                 unsigned char bytes;
628                 size_t i;
630                 while (is_initial_sep(*author))
631                         author++;
633                 bytes = utf8_char_length(author, end);
634                 if (bytes < sizeof(initials) - 1 - pos) {
635                         while (bytes--) {
636                                 initials[pos++] = *author++;
637                         }
638                 }
640                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
641                         if (i < sizeof(initials) - 1)
642                                 initials[i++] = *author;
643                 }
645                 initials[i++] = 0;
646         }
648         return initials;
652 static bool
653 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
655         int valuelen;
657         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
658                 bool advance = cmd[valuelen] != 0;
660                 cmd[valuelen] = 0;
661                 argv[(*argc)++] = chomp_string(cmd);
662                 cmd = chomp_string(cmd + valuelen + advance);
663         }
665         if (*argc < SIZEOF_ARG)
666                 argv[*argc] = NULL;
667         return *argc < SIZEOF_ARG;
670 static bool
671 argv_from_env(const char **argv, const char *name)
673         char *env = argv ? getenv(name) : NULL;
674         int argc = 0;
676         if (env && *env)
677                 env = strdup(env);
678         return !env || argv_from_string(argv, &argc, env);
681 static void
682 argv_free(const char *argv[])
684         int argc;
686         for (argc = 0; argv[argc]; argc++)
687                 free((void *) argv[argc]);
688         argv[0] = NULL;
691 static bool
692 argv_copy(const char *dst[], const char *src[], bool allocate)
694         int argc;
696         for (argc = 0; src[argc]; argc++)
697                 if (!(dst[argc] = allocate ? strdup(src[argc]) : src[argc]))
698                         return FALSE;
699         return TRUE;
703 /*
704  * Executing external commands.
705  */
707 enum io_type {
708         IO_FD,                  /* File descriptor based IO. */
709         IO_BG,                  /* Execute command in the background. */
710         IO_FG,                  /* Execute command with same std{in,out,err}. */
711         IO_RD,                  /* Read only fork+exec IO. */
712         IO_WR,                  /* Write only fork+exec IO. */
713         IO_AP,                  /* Append fork+exec output to file. */
714 };
716 struct io {
717         enum io_type type;      /* The requested type of pipe. */
718         const char *dir;        /* Directory from which to execute. */
719         pid_t pid;              /* PID of spawned process. */
720         int pipe;               /* Pipe end for reading or writing. */
721         int error;              /* Error status. */
722         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
723         char *buf;              /* Read buffer. */
724         size_t bufalloc;        /* Allocated buffer size. */
725         size_t bufsize;         /* Buffer content size. */
726         char *bufpos;           /* Current buffer position. */
727         unsigned int eof:1;     /* Has end of file been reached. */
728 };
730 static void
731 io_reset(struct io *io)
733         io->pipe = -1;
734         io->pid = 0;
735         io->buf = io->bufpos = NULL;
736         io->bufalloc = io->bufsize = 0;
737         io->error = 0;
738         io->eof = 0;
741 static void
742 io_init(struct io *io, const char *dir, enum io_type type)
744         io_reset(io);
745         io->type = type;
746         io->dir = dir;
749 static void
750 io_prepare(struct io *io, const char *dir, enum io_type type, const char *argv[])
752         io_init(io, dir, type);
753         argv_copy(io->argv, argv, FALSE);
756 static bool
757 io_open(struct io *io, const char *fmt, ...)
759         char name[SIZEOF_STR] = "";
760         bool fits;
761         va_list args;
763         io_init(io, NULL, IO_FD);
765         va_start(args, fmt);
766         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
767         va_end(args);
769         if (!fits) {
770                 io->error = ENAMETOOLONG;
771                 return FALSE;
772         }
773         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
774         if (io->pipe == -1)
775                 io->error = errno;
776         return io->pipe != -1;
779 static bool
780 io_kill(struct io *io)
782         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
785 static bool
786 io_done(struct io *io)
788         pid_t pid = io->pid;
790         if (io->pipe != -1)
791                 close(io->pipe);
792         free(io->buf);
793         io_reset(io);
795         while (pid > 0) {
796                 int status;
797                 pid_t waiting = waitpid(pid, &status, 0);
799                 if (waiting < 0) {
800                         if (errno == EINTR)
801                                 continue;
802                         io->error = errno;
803                         return FALSE;
804                 }
806                 return waiting == pid &&
807                        !WIFSIGNALED(status) &&
808                        WIFEXITED(status) &&
809                        !WEXITSTATUS(status);
810         }
812         return TRUE;
815 static bool
816 io_start(struct io *io)
818         int pipefds[2] = { -1, -1 };
820         if (io->type == IO_FD)
821                 return TRUE;
823         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
824                 io->error = errno;
825                 return FALSE;
826         } else if (io->type == IO_AP) {
827                 pipefds[1] = io->pipe;
828         }
830         if ((io->pid = fork())) {
831                 if (io->pid == -1)
832                         io->error = errno;
833                 if (pipefds[!(io->type == IO_WR)] != -1)
834                         close(pipefds[!(io->type == IO_WR)]);
835                 if (io->pid != -1) {
836                         io->pipe = pipefds[!!(io->type == IO_WR)];
837                         return TRUE;
838                 }
840         } else {
841                 if (io->type != IO_FG) {
842                         int devnull = open("/dev/null", O_RDWR);
843                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
844                         int writefd = (io->type == IO_RD || io->type == IO_AP)
845                                                         ? pipefds[1] : devnull;
847                         dup2(readfd,  STDIN_FILENO);
848                         dup2(writefd, STDOUT_FILENO);
849                         dup2(devnull, STDERR_FILENO);
851                         close(devnull);
852                         if (pipefds[0] != -1)
853                                 close(pipefds[0]);
854                         if (pipefds[1] != -1)
855                                 close(pipefds[1]);
856                 }
858                 if (io->dir && *io->dir && chdir(io->dir) == -1)
859                         exit(errno);
861                 execvp(io->argv[0], (char *const*) io->argv);
862                 exit(errno);
863         }
865         if (pipefds[!!(io->type == IO_WR)] != -1)
866                 close(pipefds[!!(io->type == IO_WR)]);
867         return FALSE;
870 static bool
871 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
873         io_prepare(io, dir, type, argv);
874         return io_start(io);
877 static bool
878 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
880         struct io io = {};
882         io_prepare(&io, dir, type, argv);
883         io.pipe = fd;
884         return io_start(&io) && io_done(&io);
887 static bool
888 io_run_bg(const char **argv)
890         return io_complete(IO_BG, argv, NULL, -1);
893 static bool
894 io_run_fg(const char **argv, const char *dir)
896         return io_complete(IO_FG, argv, dir, -1);
899 static bool
900 io_run_append(const char **argv, int fd)
902         return io_complete(IO_AP, argv, NULL, -1);
905 static bool
906 io_eof(struct io *io)
908         return io->eof;
911 static int
912 io_error(struct io *io)
914         return io->error;
917 static char *
918 io_strerror(struct io *io)
920         return strerror(io->error);
923 static bool
924 io_can_read(struct io *io)
926         struct timeval tv = { 0, 500 };
927         fd_set fds;
929         FD_ZERO(&fds);
930         FD_SET(io->pipe, &fds);
932         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
935 static ssize_t
936 io_read(struct io *io, void *buf, size_t bufsize)
938         do {
939                 ssize_t readsize = read(io->pipe, buf, bufsize);
941                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
942                         continue;
943                 else if (readsize == -1)
944                         io->error = errno;
945                 else if (readsize == 0)
946                         io->eof = 1;
947                 return readsize;
948         } while (1);
951 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
953 static char *
954 io_get(struct io *io, int c, bool can_read)
956         char *eol;
957         ssize_t readsize;
959         while (TRUE) {
960                 if (io->bufsize > 0) {
961                         eol = memchr(io->bufpos, c, io->bufsize);
962                         if (eol) {
963                                 char *line = io->bufpos;
965                                 *eol = 0;
966                                 io->bufpos = eol + 1;
967                                 io->bufsize -= io->bufpos - line;
968                                 return line;
969                         }
970                 }
972                 if (io_eof(io)) {
973                         if (io->bufsize) {
974                                 io->bufpos[io->bufsize] = 0;
975                                 io->bufsize = 0;
976                                 return io->bufpos;
977                         }
978                         return NULL;
979                 }
981                 if (!can_read)
982                         return NULL;
984                 if (io->bufsize > 0 && io->bufpos > io->buf)
985                         memmove(io->buf, io->bufpos, io->bufsize);
987                 if (io->bufalloc == io->bufsize) {
988                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
989                                 return NULL;
990                         io->bufalloc += BUFSIZ;
991                 }
993                 io->bufpos = io->buf;
994                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
995                 if (io_error(io))
996                         return NULL;
997                 io->bufsize += readsize;
998         }
1001 static bool
1002 io_write(struct io *io, const void *buf, size_t bufsize)
1004         size_t written = 0;
1006         while (!io_error(io) && written < bufsize) {
1007                 ssize_t size;
1009                 size = write(io->pipe, buf + written, bufsize - written);
1010                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1011                         continue;
1012                 else if (size == -1)
1013                         io->error = errno;
1014                 else
1015                         written += size;
1016         }
1018         return written == bufsize;
1021 static bool
1022 io_read_buf(struct io *io, char buf[], size_t bufsize)
1024         char *result = io_get(io, '\n', TRUE);
1026         if (result) {
1027                 result = chomp_string(result);
1028                 string_ncopy_do(buf, bufsize, result, strlen(result));
1029         }
1031         return io_done(io) && result;
1034 static bool
1035 io_run_buf(const char **argv, char buf[], size_t bufsize)
1037         struct io io = {};
1039         io_prepare(&io, NULL, IO_RD, argv);
1040         return io_start(&io) && io_read_buf(&io, buf, bufsize);
1043 static int
1044 io_load(struct io *io, const char *separators,
1045         int (*read_property)(char *, size_t, char *, size_t))
1047         char *name;
1048         int state = OK;
1050         if (!io_start(io))
1051                 return ERR;
1053         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054                 char *value;
1055                 size_t namelen;
1056                 size_t valuelen;
1058                 name = chomp_string(name);
1059                 namelen = strcspn(name, separators);
1061                 if (name[namelen]) {
1062                         name[namelen] = 0;
1063                         value = chomp_string(name + namelen + 1);
1064                         valuelen = strlen(value);
1066                 } else {
1067                         value = "";
1068                         valuelen = 0;
1069                 }
1071                 state = read_property(name, namelen, value, valuelen);
1072         }
1074         if (state != ERR && io_error(io))
1075                 state = ERR;
1076         io_done(io);
1078         return state;
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083             int (*read_property)(char *, size_t, char *, size_t))
1085         struct io io = {};
1087         io_prepare(&io, NULL, IO_RD, argv);
1088         return io_load(&io, separators, read_property);
1092 /*
1093  * User requests
1094  */
1096 #define REQ_INFO \
1097         /* XXX: Keep the view request first and in sync with views[]. */ \
1098         REQ_GROUP("View switching") \
1099         REQ_(VIEW_MAIN,         "Show main view"), \
1100         REQ_(VIEW_DIFF,         "Show diff view"), \
1101         REQ_(VIEW_LOG,          "Show log view"), \
1102         REQ_(VIEW_TREE,         "Show tree view"), \
1103         REQ_(VIEW_BLOB,         "Show blob view"), \
1104         REQ_(VIEW_BLAME,        "Show blame view"), \
1105         REQ_(VIEW_BRANCH,       "Show branch view"), \
1106         REQ_(VIEW_HELP,         "Show help page"), \
1107         REQ_(VIEW_PAGER,        "Show pager view"), \
1108         REQ_(VIEW_STATUS,       "Show status view"), \
1109         REQ_(VIEW_STAGE,        "Show stage view"), \
1110         \
1111         REQ_GROUP("View manipulation") \
1112         REQ_(ENTER,             "Enter current line and scroll"), \
1113         REQ_(NEXT,              "Move to next"), \
1114         REQ_(PREVIOUS,          "Move to previous"), \
1115         REQ_(PARENT,            "Move to parent"), \
1116         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1117         REQ_(REFRESH,           "Reload and refresh"), \
1118         REQ_(MAXIMIZE,          "Maximize the current view"), \
1119         REQ_(VIEW_CLOSE,        "Close the current view"), \
1120         REQ_(QUIT,              "Close all views and quit"), \
1121         \
1122         REQ_GROUP("View specific requests") \
1123         REQ_(STATUS_UPDATE,     "Update file status"), \
1124         REQ_(STATUS_REVERT,     "Revert file changes"), \
1125         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1126         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1127         \
1128         REQ_GROUP("Cursor navigation") \
1129         REQ_(MOVE_UP,           "Move cursor one line up"), \
1130         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1131         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1132         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1133         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1134         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1135         \
1136         REQ_GROUP("Scrolling") \
1137         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1138         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1139         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1140         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1141         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1142         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1143         \
1144         REQ_GROUP("Searching") \
1145         REQ_(SEARCH,            "Search the view"), \
1146         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1147         REQ_(FIND_NEXT,         "Find next search match"), \
1148         REQ_(FIND_PREV,         "Find previous search match"), \
1149         \
1150         REQ_GROUP("Option manipulation") \
1151         REQ_(OPTIONS,           "Open option menu"), \
1152         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1153         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1154         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1155         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1156         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1157         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1158         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1159         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1160         \
1161         REQ_GROUP("Misc") \
1162         REQ_(PROMPT,            "Bring up the prompt"), \
1163         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1164         REQ_(SHOW_VERSION,      "Show version information"), \
1165         REQ_(STOP_LOADING,      "Stop all loading views"), \
1166         REQ_(EDIT,              "Open in editor"), \
1167         REQ_(NONE,              "Do nothing")
1170 /* User action requests. */
1171 enum request {
1172 #define REQ_GROUP(help)
1173 #define REQ_(req, help) REQ_##req
1175         /* Offset all requests to avoid conflicts with ncurses getch values. */
1176         REQ_UNKNOWN = KEY_MAX + 1,
1177         REQ_OFFSET,
1178         REQ_INFO
1180 #undef  REQ_GROUP
1181 #undef  REQ_
1182 };
1184 struct request_info {
1185         enum request request;
1186         const char *name;
1187         int namelen;
1188         const char *help;
1189 };
1191 static const struct request_info req_info[] = {
1192 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1193 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1194         REQ_INFO
1195 #undef  REQ_GROUP
1196 #undef  REQ_
1197 };
1199 static enum request
1200 get_request(const char *name)
1202         int namelen = strlen(name);
1203         int i;
1205         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1206                 if (enum_equals(req_info[i], name, namelen))
1207                         return req_info[i].request;
1209         return REQ_UNKNOWN;
1213 /*
1214  * Options
1215  */
1217 /* Option and state variables. */
1218 static enum date opt_date               = DATE_DEFAULT;
1219 static enum author opt_author           = AUTHOR_DEFAULT;
1220 static bool opt_line_number             = FALSE;
1221 static bool opt_line_graphics           = TRUE;
1222 static bool opt_rev_graph               = FALSE;
1223 static bool opt_show_refs               = TRUE;
1224 static int opt_num_interval             = 5;
1225 static double opt_hscroll               = 0.50;
1226 static double opt_scale_split_view      = 2.0 / 3.0;
1227 static int opt_tab_size                 = 8;
1228 static int opt_author_cols              = AUTHOR_COLS;
1229 static char opt_path[SIZEOF_STR]        = "";
1230 static char opt_file[SIZEOF_STR]        = "";
1231 static char opt_ref[SIZEOF_REF]         = "";
1232 static char opt_head[SIZEOF_REF]        = "";
1233 static char opt_remote[SIZEOF_REF]      = "";
1234 static char opt_encoding[20]            = "UTF-8";
1235 static iconv_t opt_iconv_in             = ICONV_NONE;
1236 static iconv_t opt_iconv_out            = ICONV_NONE;
1237 static char opt_search[SIZEOF_STR]      = "";
1238 static char opt_cdup[SIZEOF_STR]        = "";
1239 static char opt_prefix[SIZEOF_STR]      = "";
1240 static char opt_git_dir[SIZEOF_STR]     = "";
1241 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1242 static char opt_editor[SIZEOF_STR]      = "";
1243 static FILE *opt_tty                    = NULL;
1245 #define is_initial_commit()     (!get_ref_head())
1246 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1249 /*
1250  * Line-oriented content detection.
1251  */
1253 #define LINE_INFO \
1254 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1255 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1256 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1257 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1258 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1259 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1261 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1262 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1263 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1264 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1265 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1266 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1267 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1268 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1269 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1270 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1271 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1272 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1273 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1275 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1276 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1277 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1278 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1279 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1280 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1281 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1282 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1283 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1284 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1285 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1286 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1287 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1288 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1289 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1290 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1291 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1292 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1293 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1294 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1295 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1296 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1297 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1298 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1299 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1300 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1301 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1302 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1303 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1304 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1305 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1306 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1307 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1308 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1309 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1310 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1311 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1312 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1314 enum line_type {
1315 #define LINE(type, line, fg, bg, attr) \
1316         LINE_##type
1317         LINE_INFO,
1318         LINE_NONE
1319 #undef  LINE
1320 };
1322 struct line_info {
1323         const char *name;       /* Option name. */
1324         int namelen;            /* Size of option name. */
1325         const char *line;       /* The start of line to match. */
1326         int linelen;            /* Size of string to match. */
1327         int fg, bg, attr;       /* Color and text attributes for the lines. */
1328 };
1330 static struct line_info line_info[] = {
1331 #define LINE(type, line, fg, bg, attr) \
1332         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1333         LINE_INFO
1334 #undef  LINE
1335 };
1337 static enum line_type
1338 get_line_type(const char *line)
1340         int linelen = strlen(line);
1341         enum line_type type;
1343         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1344                 /* Case insensitive search matches Signed-off-by lines better. */
1345                 if (linelen >= line_info[type].linelen &&
1346                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1347                         return type;
1349         return LINE_DEFAULT;
1352 static inline int
1353 get_line_attr(enum line_type type)
1355         assert(type < ARRAY_SIZE(line_info));
1356         return COLOR_PAIR(type) | line_info[type].attr;
1359 static struct line_info *
1360 get_line_info(const char *name)
1362         size_t namelen = strlen(name);
1363         enum line_type type;
1365         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1366                 if (enum_equals(line_info[type], name, namelen))
1367                         return &line_info[type];
1369         return NULL;
1372 static void
1373 init_colors(void)
1375         int default_bg = line_info[LINE_DEFAULT].bg;
1376         int default_fg = line_info[LINE_DEFAULT].fg;
1377         enum line_type type;
1379         start_color();
1381         if (assume_default_colors(default_fg, default_bg) == ERR) {
1382                 default_bg = COLOR_BLACK;
1383                 default_fg = COLOR_WHITE;
1384         }
1386         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1387                 struct line_info *info = &line_info[type];
1388                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1389                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1391                 init_pair(type, fg, bg);
1392         }
1395 struct line {
1396         enum line_type type;
1398         /* State flags */
1399         unsigned int selected:1;
1400         unsigned int dirty:1;
1401         unsigned int cleareol:1;
1402         unsigned int other:16;
1404         void *data;             /* User data */
1405 };
1408 /*
1409  * Keys
1410  */
1412 struct keybinding {
1413         int alias;
1414         enum request request;
1415 };
1417 static struct keybinding default_keybindings[] = {
1418         /* View switching */
1419         { 'm',          REQ_VIEW_MAIN },
1420         { 'd',          REQ_VIEW_DIFF },
1421         { 'l',          REQ_VIEW_LOG },
1422         { 't',          REQ_VIEW_TREE },
1423         { 'f',          REQ_VIEW_BLOB },
1424         { 'B',          REQ_VIEW_BLAME },
1425         { 'H',          REQ_VIEW_BRANCH },
1426         { 'p',          REQ_VIEW_PAGER },
1427         { 'h',          REQ_VIEW_HELP },
1428         { 'S',          REQ_VIEW_STATUS },
1429         { 'c',          REQ_VIEW_STAGE },
1431         /* View manipulation */
1432         { 'q',          REQ_VIEW_CLOSE },
1433         { KEY_TAB,      REQ_VIEW_NEXT },
1434         { KEY_RETURN,   REQ_ENTER },
1435         { KEY_UP,       REQ_PREVIOUS },
1436         { KEY_DOWN,     REQ_NEXT },
1437         { 'R',          REQ_REFRESH },
1438         { KEY_F(5),     REQ_REFRESH },
1439         { 'O',          REQ_MAXIMIZE },
1441         /* Cursor navigation */
1442         { 'k',          REQ_MOVE_UP },
1443         { 'j',          REQ_MOVE_DOWN },
1444         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1445         { KEY_END,      REQ_MOVE_LAST_LINE },
1446         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1447         { ' ',          REQ_MOVE_PAGE_DOWN },
1448         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1449         { 'b',          REQ_MOVE_PAGE_UP },
1450         { '-',          REQ_MOVE_PAGE_UP },
1452         /* Scrolling */
1453         { KEY_LEFT,     REQ_SCROLL_LEFT },
1454         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1455         { KEY_IC,       REQ_SCROLL_LINE_UP },
1456         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1457         { 'w',          REQ_SCROLL_PAGE_UP },
1458         { 's',          REQ_SCROLL_PAGE_DOWN },
1460         /* Searching */
1461         { '/',          REQ_SEARCH },
1462         { '?',          REQ_SEARCH_BACK },
1463         { 'n',          REQ_FIND_NEXT },
1464         { 'N',          REQ_FIND_PREV },
1466         /* Misc */
1467         { 'Q',          REQ_QUIT },
1468         { 'z',          REQ_STOP_LOADING },
1469         { 'v',          REQ_SHOW_VERSION },
1470         { 'r',          REQ_SCREEN_REDRAW },
1471         { 'o',          REQ_OPTIONS },
1472         { '.',          REQ_TOGGLE_LINENO },
1473         { 'D',          REQ_TOGGLE_DATE },
1474         { 'A',          REQ_TOGGLE_AUTHOR },
1475         { 'g',          REQ_TOGGLE_REV_GRAPH },
1476         { 'F',          REQ_TOGGLE_REFS },
1477         { 'I',          REQ_TOGGLE_SORT_ORDER },
1478         { 'i',          REQ_TOGGLE_SORT_FIELD },
1479         { ':',          REQ_PROMPT },
1480         { 'u',          REQ_STATUS_UPDATE },
1481         { '!',          REQ_STATUS_REVERT },
1482         { 'M',          REQ_STATUS_MERGE },
1483         { '@',          REQ_STAGE_NEXT },
1484         { ',',          REQ_PARENT },
1485         { 'e',          REQ_EDIT },
1486 };
1488 #define KEYMAP_INFO \
1489         KEYMAP_(GENERIC), \
1490         KEYMAP_(MAIN), \
1491         KEYMAP_(DIFF), \
1492         KEYMAP_(LOG), \
1493         KEYMAP_(TREE), \
1494         KEYMAP_(BLOB), \
1495         KEYMAP_(BLAME), \
1496         KEYMAP_(BRANCH), \
1497         KEYMAP_(PAGER), \
1498         KEYMAP_(HELP), \
1499         KEYMAP_(STATUS), \
1500         KEYMAP_(STAGE)
1502 enum keymap {
1503 #define KEYMAP_(name) KEYMAP_##name
1504         KEYMAP_INFO
1505 #undef  KEYMAP_
1506 };
1508 static const struct enum_map keymap_table[] = {
1509 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1510         KEYMAP_INFO
1511 #undef  KEYMAP_
1512 };
1514 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1516 struct keybinding_table {
1517         struct keybinding *data;
1518         size_t size;
1519 };
1521 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1523 static void
1524 add_keybinding(enum keymap keymap, enum request request, int key)
1526         struct keybinding_table *table = &keybindings[keymap];
1527         size_t i;
1529         for (i = 0; i < keybindings[keymap].size; i++) {
1530                 if (keybindings[keymap].data[i].alias == key) {
1531                         keybindings[keymap].data[i].request = request;
1532                         return;
1533                 }
1534         }
1536         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1537         if (!table->data)
1538                 die("Failed to allocate keybinding");
1539         table->data[table->size].alias = key;
1540         table->data[table->size++].request = request;
1542         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1543                 int i;
1545                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1546                         if (default_keybindings[i].alias == key)
1547                                 default_keybindings[i].request = REQ_NONE;
1548         }
1551 /* Looks for a key binding first in the given map, then in the generic map, and
1552  * lastly in the default keybindings. */
1553 static enum request
1554 get_keybinding(enum keymap keymap, int key)
1556         size_t i;
1558         for (i = 0; i < keybindings[keymap].size; i++)
1559                 if (keybindings[keymap].data[i].alias == key)
1560                         return keybindings[keymap].data[i].request;
1562         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1563                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1564                         return keybindings[KEYMAP_GENERIC].data[i].request;
1566         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1567                 if (default_keybindings[i].alias == key)
1568                         return default_keybindings[i].request;
1570         return (enum request) key;
1574 struct key {
1575         const char *name;
1576         int value;
1577 };
1579 static const struct key key_table[] = {
1580         { "Enter",      KEY_RETURN },
1581         { "Space",      ' ' },
1582         { "Backspace",  KEY_BACKSPACE },
1583         { "Tab",        KEY_TAB },
1584         { "Escape",     KEY_ESC },
1585         { "Left",       KEY_LEFT },
1586         { "Right",      KEY_RIGHT },
1587         { "Up",         KEY_UP },
1588         { "Down",       KEY_DOWN },
1589         { "Insert",     KEY_IC },
1590         { "Delete",     KEY_DC },
1591         { "Hash",       '#' },
1592         { "Home",       KEY_HOME },
1593         { "End",        KEY_END },
1594         { "PageUp",     KEY_PPAGE },
1595         { "PageDown",   KEY_NPAGE },
1596         { "F1",         KEY_F(1) },
1597         { "F2",         KEY_F(2) },
1598         { "F3",         KEY_F(3) },
1599         { "F4",         KEY_F(4) },
1600         { "F5",         KEY_F(5) },
1601         { "F6",         KEY_F(6) },
1602         { "F7",         KEY_F(7) },
1603         { "F8",         KEY_F(8) },
1604         { "F9",         KEY_F(9) },
1605         { "F10",        KEY_F(10) },
1606         { "F11",        KEY_F(11) },
1607         { "F12",        KEY_F(12) },
1608 };
1610 static int
1611 get_key_value(const char *name)
1613         int i;
1615         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1616                 if (!strcasecmp(key_table[i].name, name))
1617                         return key_table[i].value;
1619         if (strlen(name) == 1 && isprint(*name))
1620                 return (int) *name;
1622         return ERR;
1625 static const char *
1626 get_key_name(int key_value)
1628         static char key_char[] = "'X'";
1629         const char *seq = NULL;
1630         int key;
1632         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1633                 if (key_table[key].value == key_value)
1634                         seq = key_table[key].name;
1636         if (seq == NULL &&
1637             key_value < 127 &&
1638             isprint(key_value)) {
1639                 key_char[1] = (char) key_value;
1640                 seq = key_char;
1641         }
1643         return seq ? seq : "(no key)";
1646 static bool
1647 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1649         const char *sep = *pos > 0 ? ", " : "";
1650         const char *keyname = get_key_name(keybinding->alias);
1652         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1655 static bool
1656 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1657                            enum keymap keymap, bool all)
1659         int i;
1661         for (i = 0; i < keybindings[keymap].size; i++) {
1662                 if (keybindings[keymap].data[i].request == request) {
1663                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1664                                 return FALSE;
1665                         if (!all)
1666                                 break;
1667                 }
1668         }
1670         return TRUE;
1673 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1675 static const char *
1676 get_keys(enum keymap keymap, enum request request, bool all)
1678         static char buf[BUFSIZ];
1679         size_t pos = 0;
1680         int i;
1682         buf[pos] = 0;
1684         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1685                 return "Too many keybindings!";
1686         if (pos > 0 && !all)
1687                 return buf;
1689         if (keymap != KEYMAP_GENERIC) {
1690                 /* Only the generic keymap includes the default keybindings when
1691                  * listing all keys. */
1692                 if (all)
1693                         return buf;
1695                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1696                         return "Too many keybindings!";
1697                 if (pos)
1698                         return buf;
1699         }
1701         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1702                 if (default_keybindings[i].request == request) {
1703                         if (!append_key(buf, &pos, &default_keybindings[i]))
1704                                 return "Too many keybindings!";
1705                         if (!all)
1706                                 return buf;
1707                 }
1708         }
1710         return buf;
1713 struct run_request {
1714         enum keymap keymap;
1715         int key;
1716         const char *argv[SIZEOF_ARG];
1717 };
1719 static struct run_request *run_request;
1720 static size_t run_requests;
1722 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1724 static enum request
1725 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1727         struct run_request *req;
1729         if (argc >= ARRAY_SIZE(req->argv) - 1)
1730                 return REQ_NONE;
1732         if (!realloc_run_requests(&run_request, run_requests, 1))
1733                 return REQ_NONE;
1735         req = &run_request[run_requests];
1736         req->keymap = keymap;
1737         req->key = key;
1738         req->argv[0] = NULL;
1740         if (!argv_copy(req->argv, argv, TRUE))
1741                 return REQ_NONE;
1743         return REQ_NONE + ++run_requests;
1746 static struct run_request *
1747 get_run_request(enum request request)
1749         if (request <= REQ_NONE)
1750                 return NULL;
1751         return &run_request[request - REQ_NONE - 1];
1754 static void
1755 add_builtin_run_requests(void)
1757         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1758         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1759         const char *commit[] = { "git", "commit", NULL };
1760         const char *gc[] = { "git", "gc", NULL };
1761         struct {
1762                 enum keymap keymap;
1763                 int key;
1764                 int argc;
1765                 const char **argv;
1766         } reqs[] = {
1767                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1768                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1769                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1770                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1771         };
1772         int i;
1774         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1775                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1777                 if (req != reqs[i].key)
1778                         continue;
1779                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1780                 if (req != REQ_NONE)
1781                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1782         }
1785 /*
1786  * User config file handling.
1787  */
1789 static int   config_lineno;
1790 static bool  config_errors;
1791 static const char *config_msg;
1793 static const struct enum_map color_map[] = {
1794 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1795         COLOR_MAP(DEFAULT),
1796         COLOR_MAP(BLACK),
1797         COLOR_MAP(BLUE),
1798         COLOR_MAP(CYAN),
1799         COLOR_MAP(GREEN),
1800         COLOR_MAP(MAGENTA),
1801         COLOR_MAP(RED),
1802         COLOR_MAP(WHITE),
1803         COLOR_MAP(YELLOW),
1804 };
1806 static const struct enum_map attr_map[] = {
1807 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1808         ATTR_MAP(NORMAL),
1809         ATTR_MAP(BLINK),
1810         ATTR_MAP(BOLD),
1811         ATTR_MAP(DIM),
1812         ATTR_MAP(REVERSE),
1813         ATTR_MAP(STANDOUT),
1814         ATTR_MAP(UNDERLINE),
1815 };
1817 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1819 static int parse_step(double *opt, const char *arg)
1821         *opt = atoi(arg);
1822         if (!strchr(arg, '%'))
1823                 return OK;
1825         /* "Shift down" so 100% and 1 does not conflict. */
1826         *opt = (*opt - 1) / 100;
1827         if (*opt >= 1.0) {
1828                 *opt = 0.99;
1829                 config_msg = "Step value larger than 100%";
1830                 return ERR;
1831         }
1832         if (*opt < 0.0) {
1833                 *opt = 1;
1834                 config_msg = "Invalid step value";
1835                 return ERR;
1836         }
1837         return OK;
1840 static int
1841 parse_int(int *opt, const char *arg, int min, int max)
1843         int value = atoi(arg);
1845         if (min <= value && value <= max) {
1846                 *opt = value;
1847                 return OK;
1848         }
1850         config_msg = "Integer value out of bound";
1851         return ERR;
1854 static bool
1855 set_color(int *color, const char *name)
1857         if (map_enum(color, color_map, name))
1858                 return TRUE;
1859         if (!prefixcmp(name, "color"))
1860                 return parse_int(color, name + 5, 0, 255) == OK;
1861         return FALSE;
1864 /* Wants: object fgcolor bgcolor [attribute] */
1865 static int
1866 option_color_command(int argc, const char *argv[])
1868         struct line_info *info;
1870         if (argc < 3) {
1871                 config_msg = "Wrong number of arguments given to color command";
1872                 return ERR;
1873         }
1875         info = get_line_info(argv[0]);
1876         if (!info) {
1877                 static const struct enum_map obsolete[] = {
1878                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1879                         ENUM_MAP("main-date",   LINE_DATE),
1880                         ENUM_MAP("main-author", LINE_AUTHOR),
1881                 };
1882                 int index;
1884                 if (!map_enum(&index, obsolete, argv[0])) {
1885                         config_msg = "Unknown color name";
1886                         return ERR;
1887                 }
1888                 info = &line_info[index];
1889         }
1891         if (!set_color(&info->fg, argv[1]) ||
1892             !set_color(&info->bg, argv[2])) {
1893                 config_msg = "Unknown color";
1894                 return ERR;
1895         }
1897         info->attr = 0;
1898         while (argc-- > 3) {
1899                 int attr;
1901                 if (!set_attribute(&attr, argv[argc])) {
1902                         config_msg = "Unknown attribute";
1903                         return ERR;
1904                 }
1905                 info->attr |= attr;
1906         }
1908         return OK;
1911 static int parse_bool(bool *opt, const char *arg)
1913         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1914                 ? TRUE : FALSE;
1915         return OK;
1918 static int parse_enum_do(unsigned int *opt, const char *arg,
1919                          const struct enum_map *map, size_t map_size)
1921         bool is_true;
1923         assert(map_size > 1);
1925         if (map_enum_do(map, map_size, (int *) opt, arg))
1926                 return OK;
1928         if (parse_bool(&is_true, arg) != OK)
1929                 return ERR;
1931         *opt = is_true ? map[1].value : map[0].value;
1932         return OK;
1935 #define parse_enum(opt, arg, map) \
1936         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1938 static int
1939 parse_string(char *opt, const char *arg, size_t optsize)
1941         int arglen = strlen(arg);
1943         switch (arg[0]) {
1944         case '\"':
1945         case '\'':
1946                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1947                         config_msg = "Unmatched quotation";
1948                         return ERR;
1949                 }
1950                 arg += 1; arglen -= 2;
1951         default:
1952                 string_ncopy_do(opt, optsize, arg, arglen);
1953                 return OK;
1954         }
1957 /* Wants: name = value */
1958 static int
1959 option_set_command(int argc, const char *argv[])
1961         if (argc != 3) {
1962                 config_msg = "Wrong number of arguments given to set command";
1963                 return ERR;
1964         }
1966         if (strcmp(argv[1], "=")) {
1967                 config_msg = "No value assigned";
1968                 return ERR;
1969         }
1971         if (!strcmp(argv[0], "show-author"))
1972                 return parse_enum(&opt_author, argv[2], author_map);
1974         if (!strcmp(argv[0], "show-date"))
1975                 return parse_enum(&opt_date, argv[2], date_map);
1977         if (!strcmp(argv[0], "show-rev-graph"))
1978                 return parse_bool(&opt_rev_graph, argv[2]);
1980         if (!strcmp(argv[0], "show-refs"))
1981                 return parse_bool(&opt_show_refs, argv[2]);
1983         if (!strcmp(argv[0], "show-line-numbers"))
1984                 return parse_bool(&opt_line_number, argv[2]);
1986         if (!strcmp(argv[0], "line-graphics"))
1987                 return parse_bool(&opt_line_graphics, argv[2]);
1989         if (!strcmp(argv[0], "line-number-interval"))
1990                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1992         if (!strcmp(argv[0], "author-width"))
1993                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1995         if (!strcmp(argv[0], "horizontal-scroll"))
1996                 return parse_step(&opt_hscroll, argv[2]);
1998         if (!strcmp(argv[0], "split-view-height"))
1999                 return parse_step(&opt_scale_split_view, argv[2]);
2001         if (!strcmp(argv[0], "tab-size"))
2002                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2004         if (!strcmp(argv[0], "commit-encoding"))
2005                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2007         config_msg = "Unknown variable name";
2008         return ERR;
2011 /* Wants: mode request key */
2012 static int
2013 option_bind_command(int argc, const char *argv[])
2015         enum request request;
2016         int keymap = -1;
2017         int key;
2019         if (argc < 3) {
2020                 config_msg = "Wrong number of arguments given to bind command";
2021                 return ERR;
2022         }
2024         if (!set_keymap(&keymap, argv[0])) {
2025                 config_msg = "Unknown key map";
2026                 return ERR;
2027         }
2029         key = get_key_value(argv[1]);
2030         if (key == ERR) {
2031                 config_msg = "Unknown key";
2032                 return ERR;
2033         }
2035         request = get_request(argv[2]);
2036         if (request == REQ_UNKNOWN) {
2037                 static const struct enum_map obsolete[] = {
2038                         ENUM_MAP("cherry-pick",         REQ_NONE),
2039                         ENUM_MAP("screen-resize",       REQ_NONE),
2040                         ENUM_MAP("tree-parent",         REQ_PARENT),
2041                 };
2042                 int alias;
2044                 if (map_enum(&alias, obsolete, argv[2])) {
2045                         if (alias != REQ_NONE)
2046                                 add_keybinding(keymap, alias, key);
2047                         config_msg = "Obsolete request name";
2048                         return ERR;
2049                 }
2050         }
2051         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2052                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2053         if (request == REQ_UNKNOWN) {
2054                 config_msg = "Unknown request name";
2055                 return ERR;
2056         }
2058         add_keybinding(keymap, request, key);
2060         return OK;
2063 static int
2064 set_option(const char *opt, char *value)
2066         const char *argv[SIZEOF_ARG];
2067         int argc = 0;
2069         if (!argv_from_string(argv, &argc, value)) {
2070                 config_msg = "Too many option arguments";
2071                 return ERR;
2072         }
2074         if (!strcmp(opt, "color"))
2075                 return option_color_command(argc, argv);
2077         if (!strcmp(opt, "set"))
2078                 return option_set_command(argc, argv);
2080         if (!strcmp(opt, "bind"))
2081                 return option_bind_command(argc, argv);
2083         config_msg = "Unknown option command";
2084         return ERR;
2087 static int
2088 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2090         int status = OK;
2092         config_lineno++;
2093         config_msg = "Internal error";
2095         /* Check for comment markers, since read_properties() will
2096          * only ensure opt and value are split at first " \t". */
2097         optlen = strcspn(opt, "#");
2098         if (optlen == 0)
2099                 return OK;
2101         if (opt[optlen] != 0) {
2102                 config_msg = "No option value";
2103                 status = ERR;
2105         }  else {
2106                 /* Look for comment endings in the value. */
2107                 size_t len = strcspn(value, "#");
2109                 if (len < valuelen) {
2110                         valuelen = len;
2111                         value[valuelen] = 0;
2112                 }
2114                 status = set_option(opt, value);
2115         }
2117         if (status == ERR) {
2118                 warn("Error on line %d, near '%.*s': %s",
2119                      config_lineno, (int) optlen, opt, config_msg);
2120                 config_errors = TRUE;
2121         }
2123         /* Always keep going if errors are encountered. */
2124         return OK;
2127 static void
2128 load_option_file(const char *path)
2130         struct io io = {};
2132         /* It's OK that the file doesn't exist. */
2133         if (!io_open(&io, "%s", path))
2134                 return;
2136         config_lineno = 0;
2137         config_errors = FALSE;
2139         if (io_load(&io, " \t", read_option) == ERR ||
2140             config_errors == TRUE)
2141                 warn("Errors while loading %s.", path);
2144 static int
2145 load_options(void)
2147         const char *home = getenv("HOME");
2148         const char *tigrc_user = getenv("TIGRC_USER");
2149         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2150         char buf[SIZEOF_STR];
2152         if (!tigrc_system)
2153                 tigrc_system = SYSCONFDIR "/tigrc";
2154         load_option_file(tigrc_system);
2156         if (!tigrc_user) {
2157                 if (!home || !string_format(buf, "%s/.tigrc", home))
2158                         return ERR;
2159                 tigrc_user = buf;
2160         }
2161         load_option_file(tigrc_user);
2163         /* Add _after_ loading config files to avoid adding run requests
2164          * that conflict with keybindings. */
2165         add_builtin_run_requests();
2167         return OK;
2171 /*
2172  * The viewer
2173  */
2175 struct view;
2176 struct view_ops;
2178 /* The display array of active views and the index of the current view. */
2179 static struct view *display[2];
2180 static unsigned int current_view;
2182 #define foreach_displayed_view(view, i) \
2183         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2185 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2187 /* Current head and commit ID */
2188 static char ref_blob[SIZEOF_REF]        = "";
2189 static char ref_commit[SIZEOF_REF]      = "HEAD";
2190 static char ref_head[SIZEOF_REF]        = "HEAD";
2191 static char ref_branch[SIZEOF_REF]      = "";
2193 enum view_type {
2194         VIEW_MAIN,
2195         VIEW_DIFF,
2196         VIEW_LOG,
2197         VIEW_TREE,
2198         VIEW_BLOB,
2199         VIEW_BLAME,
2200         VIEW_BRANCH,
2201         VIEW_HELP,
2202         VIEW_PAGER,
2203         VIEW_STATUS,
2204         VIEW_STAGE,
2205 };
2207 struct view {
2208         enum view_type type;    /* View type */
2209         const char *name;       /* View name */
2210         const char *cmd_env;    /* Command line set via environment */
2211         const char *id;         /* Points to either of ref_{head,commit,blob} */
2213         struct view_ops *ops;   /* View operations */
2215         enum keymap keymap;     /* What keymap does this view have */
2216         bool git_dir;           /* Whether the view requires a git directory. */
2218         char ref[SIZEOF_REF];   /* Hovered commit reference */
2219         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2221         int height, width;      /* The width and height of the main window */
2222         WINDOW *win;            /* The main window */
2223         WINDOW *title;          /* The title window living below the main window */
2225         /* Navigation */
2226         unsigned long offset;   /* Offset of the window top */
2227         unsigned long yoffset;  /* Offset from the window side. */
2228         unsigned long lineno;   /* Current line number */
2229         unsigned long p_offset; /* Previous offset of the window top */
2230         unsigned long p_yoffset;/* Previous offset from the window side */
2231         unsigned long p_lineno; /* Previous current line number */
2232         bool p_restore;         /* Should the previous position be restored. */
2234         /* Searching */
2235         char grep[SIZEOF_STR];  /* Search string */
2236         regex_t *regex;         /* Pre-compiled regexp */
2238         /* If non-NULL, points to the view that opened this view. If this view
2239          * is closed tig will switch back to the parent view. */
2240         struct view *parent;
2241         struct view *prev;
2243         /* Buffering */
2244         size_t lines;           /* Total number of lines */
2245         struct line *line;      /* Line index */
2246         unsigned int digits;    /* Number of digits in the lines member. */
2248         /* Drawing */
2249         struct line *curline;   /* Line currently being drawn. */
2250         enum line_type curtype; /* Attribute currently used for drawing. */
2251         unsigned long col;      /* Column when drawing. */
2252         bool has_scrolled;      /* View was scrolled. */
2254         /* Loading */
2255         struct io io;
2256         struct io *pipe;
2257         time_t start_time;
2258         time_t update_secs;
2259 };
2261 struct view_ops {
2262         /* What type of content being displayed. Used in the title bar. */
2263         const char *type;
2264         /* Default command arguments. */
2265         const char **argv;
2266         /* Open and reads in all view content. */
2267         bool (*open)(struct view *view);
2268         /* Read one line; updates view->line. */
2269         bool (*read)(struct view *view, char *data);
2270         /* Draw one line; @lineno must be < view->height. */
2271         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2272         /* Depending on view handle a special requests. */
2273         enum request (*request)(struct view *view, enum request request, struct line *line);
2274         /* Search for regexp in a line. */
2275         bool (*grep)(struct view *view, struct line *line);
2276         /* Select line */
2277         void (*select)(struct view *view, struct line *line);
2278         /* Prepare view for loading */
2279         bool (*prepare)(struct view *view);
2280 };
2282 static struct view_ops blame_ops;
2283 static struct view_ops blob_ops;
2284 static struct view_ops diff_ops;
2285 static struct view_ops help_ops;
2286 static struct view_ops log_ops;
2287 static struct view_ops main_ops;
2288 static struct view_ops pager_ops;
2289 static struct view_ops stage_ops;
2290 static struct view_ops status_ops;
2291 static struct view_ops tree_ops;
2292 static struct view_ops branch_ops;
2294 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2295         { type, name, #env, ref, ops, map, git }
2297 #define VIEW_(id, name, ops, git, ref) \
2298         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2300 static struct view views[] = {
2301         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2302         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2303         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2304         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2305         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2306         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2307         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2308         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2309         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2310         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2311         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2312 };
2314 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2316 #define foreach_view(view, i) \
2317         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2319 #define view_is_displayed(view) \
2320         (view == display[0] || view == display[1])
2322 static enum request
2323 view_request(struct view *view, enum request request)
2325         if (!view || !view->lines)
2326                 return request;
2327         return view->ops->request(view, request, &view->line[view->lineno]);
2331 /*
2332  * View drawing.
2333  */
2335 static inline void
2336 set_view_attr(struct view *view, enum line_type type)
2338         if (!view->curline->selected && view->curtype != type) {
2339                 (void) wattrset(view->win, get_line_attr(type));
2340                 wchgat(view->win, -1, 0, type, NULL);
2341                 view->curtype = type;
2342         }
2345 static int
2346 draw_chars(struct view *view, enum line_type type, const char *string,
2347            int max_len, bool use_tilde)
2349         static char out_buffer[BUFSIZ * 2];
2350         int len = 0;
2351         int col = 0;
2352         int trimmed = FALSE;
2353         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2355         if (max_len <= 0)
2356                 return 0;
2358         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2360         set_view_attr(view, type);
2361         if (len > 0) {
2362                 if (opt_iconv_out != ICONV_NONE) {
2363                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2364                         size_t inlen = len + 1;
2366                         char *outbuf = out_buffer;
2367                         size_t outlen = sizeof(out_buffer);
2369                         size_t ret;
2371                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2372                         if (ret != (size_t) -1) {
2373                                 string = out_buffer;
2374                                 len = sizeof(out_buffer) - outlen;
2375                         }
2376                 }
2378                 waddnstr(view->win, string, len);
2379         }
2380         if (trimmed && use_tilde) {
2381                 set_view_attr(view, LINE_DELIMITER);
2382                 waddch(view->win, '~');
2383                 col++;
2384         }
2386         return col;
2389 static int
2390 draw_space(struct view *view, enum line_type type, int max, int spaces)
2392         static char space[] = "                    ";
2393         int col = 0;
2395         spaces = MIN(max, spaces);
2397         while (spaces > 0) {
2398                 int len = MIN(spaces, sizeof(space) - 1);
2400                 col += draw_chars(view, type, space, len, FALSE);
2401                 spaces -= len;
2402         }
2404         return col;
2407 static bool
2408 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2410         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2411         return view->width + view->yoffset <= view->col;
2414 static bool
2415 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2417         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2418         int max = view->width + view->yoffset - view->col;
2419         int i;
2421         if (max < size)
2422                 size = max;
2424         set_view_attr(view, type);
2425         /* Using waddch() instead of waddnstr() ensures that
2426          * they'll be rendered correctly for the cursor line. */
2427         for (i = skip; i < size; i++)
2428                 waddch(view->win, graphic[i]);
2430         view->col += size;
2431         if (size < max && skip <= size)
2432                 waddch(view->win, ' ');
2433         view->col++;
2435         return view->width + view->yoffset <= view->col;
2438 static bool
2439 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2441         int max = MIN(view->width + view->yoffset - view->col, len);
2442         int col;
2444         if (text)
2445                 col = draw_chars(view, type, text, max - 1, trim);
2446         else
2447                 col = draw_space(view, type, max - 1, max - 1);
2449         view->col += col;
2450         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2451         return view->width + view->yoffset <= view->col;
2454 static bool
2455 draw_date(struct view *view, struct time *time)
2457         const char *date = mkdate(time, opt_date);
2458         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2460         return draw_field(view, LINE_DATE, date, cols, FALSE);
2463 static bool
2464 draw_author(struct view *view, const char *author)
2466         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2467         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2469         if (abbreviate && author)
2470                 author = get_author_initials(author);
2472         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2475 static bool
2476 draw_mode(struct view *view, mode_t mode)
2478         const char *str;
2480         if (S_ISDIR(mode))
2481                 str = "drwxr-xr-x";
2482         else if (S_ISLNK(mode))
2483                 str = "lrwxrwxrwx";
2484         else if (S_ISGITLINK(mode))
2485                 str = "m---------";
2486         else if (S_ISREG(mode) && mode & S_IXUSR)
2487                 str = "-rwxr-xr-x";
2488         else if (S_ISREG(mode))
2489                 str = "-rw-r--r--";
2490         else
2491                 str = "----------";
2493         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2496 static bool
2497 draw_lineno(struct view *view, unsigned int lineno)
2499         char number[10];
2500         int digits3 = view->digits < 3 ? 3 : view->digits;
2501         int max = MIN(view->width + view->yoffset - view->col, digits3);
2502         char *text = NULL;
2503         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2505         lineno += view->offset + 1;
2506         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2507                 static char fmt[] = "%1ld";
2509                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2510                 if (string_format(number, fmt, lineno))
2511                         text = number;
2512         }
2513         if (text)
2514                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2515         else
2516                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2517         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2520 static bool
2521 draw_view_line(struct view *view, unsigned int lineno)
2523         struct line *line;
2524         bool selected = (view->offset + lineno == view->lineno);
2526         assert(view_is_displayed(view));
2528         if (view->offset + lineno >= view->lines)
2529                 return FALSE;
2531         line = &view->line[view->offset + lineno];
2533         wmove(view->win, lineno, 0);
2534         if (line->cleareol)
2535                 wclrtoeol(view->win);
2536         view->col = 0;
2537         view->curline = line;
2538         view->curtype = LINE_NONE;
2539         line->selected = FALSE;
2540         line->dirty = line->cleareol = 0;
2542         if (selected) {
2543                 set_view_attr(view, LINE_CURSOR);
2544                 line->selected = TRUE;
2545                 view->ops->select(view, line);
2546         }
2548         return view->ops->draw(view, line, lineno);
2551 static void
2552 redraw_view_dirty(struct view *view)
2554         bool dirty = FALSE;
2555         int lineno;
2557         for (lineno = 0; lineno < view->height; lineno++) {
2558                 if (view->offset + lineno >= view->lines)
2559                         break;
2560                 if (!view->line[view->offset + lineno].dirty)
2561                         continue;
2562                 dirty = TRUE;
2563                 if (!draw_view_line(view, lineno))
2564                         break;
2565         }
2567         if (!dirty)
2568                 return;
2569         wnoutrefresh(view->win);
2572 static void
2573 redraw_view_from(struct view *view, int lineno)
2575         assert(0 <= lineno && lineno < view->height);
2577         for (; lineno < view->height; lineno++) {
2578                 if (!draw_view_line(view, lineno))
2579                         break;
2580         }
2582         wnoutrefresh(view->win);
2585 static void
2586 redraw_view(struct view *view)
2588         werase(view->win);
2589         redraw_view_from(view, 0);
2593 static void
2594 update_view_title(struct view *view)
2596         char buf[SIZEOF_STR];
2597         char state[SIZEOF_STR];
2598         size_t bufpos = 0, statelen = 0;
2600         assert(view_is_displayed(view));
2602         if (view->type != VIEW_STATUS && view->lines) {
2603                 unsigned int view_lines = view->offset + view->height;
2604                 unsigned int lines = view->lines
2605                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2606                                    : 0;
2608                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2609                                    view->ops->type,
2610                                    view->lineno + 1,
2611                                    view->lines,
2612                                    lines);
2614         }
2616         if (view->pipe) {
2617                 time_t secs = time(NULL) - view->start_time;
2619                 /* Three git seconds are a long time ... */
2620                 if (secs > 2)
2621                         string_format_from(state, &statelen, " loading %lds", secs);
2622         }
2624         string_format_from(buf, &bufpos, "[%s]", view->name);
2625         if (*view->ref && bufpos < view->width) {
2626                 size_t refsize = strlen(view->ref);
2627                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2629                 if (minsize < view->width)
2630                         refsize = view->width - minsize + 7;
2631                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2632         }
2634         if (statelen && bufpos < view->width) {
2635                 string_format_from(buf, &bufpos, "%s", state);
2636         }
2638         if (view == display[current_view])
2639                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2640         else
2641                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2643         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2644         wclrtoeol(view->title);
2645         wnoutrefresh(view->title);
2648 static int
2649 apply_step(double step, int value)
2651         if (step >= 1)
2652                 return (int) step;
2653         value *= step + 0.01;
2654         return value ? value : 1;
2657 static void
2658 resize_display(void)
2660         int offset, i;
2661         struct view *base = display[0];
2662         struct view *view = display[1] ? display[1] : display[0];
2664         /* Setup window dimensions */
2666         getmaxyx(stdscr, base->height, base->width);
2668         /* Make room for the status window. */
2669         base->height -= 1;
2671         if (view != base) {
2672                 /* Horizontal split. */
2673                 view->width   = base->width;
2674                 view->height  = apply_step(opt_scale_split_view, base->height);
2675                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2676                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2677                 base->height -= view->height;
2679                 /* Make room for the title bar. */
2680                 view->height -= 1;
2681         }
2683         /* Make room for the title bar. */
2684         base->height -= 1;
2686         offset = 0;
2688         foreach_displayed_view (view, i) {
2689                 if (!view->win) {
2690                         view->win = newwin(view->height, 0, offset, 0);
2691                         if (!view->win)
2692                                 die("Failed to create %s view", view->name);
2694                         scrollok(view->win, FALSE);
2696                         view->title = newwin(1, 0, offset + view->height, 0);
2697                         if (!view->title)
2698                                 die("Failed to create title window");
2700                 } else {
2701                         wresize(view->win, view->height, view->width);
2702                         mvwin(view->win,   offset, 0);
2703                         mvwin(view->title, offset + view->height, 0);
2704                 }
2706                 offset += view->height + 1;
2707         }
2710 static void
2711 redraw_display(bool clear)
2713         struct view *view;
2714         int i;
2716         foreach_displayed_view (view, i) {
2717                 if (clear)
2718                         wclear(view->win);
2719                 redraw_view(view);
2720                 update_view_title(view);
2721         }
2725 /*
2726  * Option management
2727  */
2729 static void
2730 toggle_enum_option_do(unsigned int *opt, const char *help,
2731                       const struct enum_map *map, size_t size)
2733         *opt = (*opt + 1) % size;
2734         redraw_display(FALSE);
2735         report("Displaying %s %s", enum_name(map[*opt]), help);
2738 #define toggle_enum_option(opt, help, map) \
2739         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2741 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2742 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2744 static void
2745 toggle_view_option(bool *option, const char *help)
2747         *option = !*option;
2748         redraw_display(FALSE);
2749         report("%sabling %s", *option ? "En" : "Dis", help);
2752 static void
2753 open_option_menu(void)
2755         const struct menu_item menu[] = {
2756                 { '.', "line numbers", &opt_line_number },
2757                 { 'D', "date display", &opt_date },
2758                 { 'A', "author display", &opt_author },
2759                 { 'g', "revision graph display", &opt_rev_graph },
2760                 { 'F', "reference display", &opt_show_refs },
2761                 { 0 }
2762         };
2763         int selected = 0;
2765         if (prompt_menu("Toggle option", menu, &selected)) {
2766                 if (menu[selected].data == &opt_date)
2767                         toggle_date();
2768                 else if (menu[selected].data == &opt_author)
2769                         toggle_author();
2770                 else
2771                         toggle_view_option(menu[selected].data, menu[selected].text);
2772         }
2775 static void
2776 maximize_view(struct view *view)
2778         memset(display, 0, sizeof(display));
2779         current_view = 0;
2780         display[current_view] = view;
2781         resize_display();
2782         redraw_display(FALSE);
2783         report("");
2787 /*
2788  * Navigation
2789  */
2791 static bool
2792 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2794         if (lineno >= view->lines)
2795                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2797         if (offset > lineno || offset + view->height <= lineno) {
2798                 unsigned long half = view->height / 2;
2800                 if (lineno > half)
2801                         offset = lineno - half;
2802                 else
2803                         offset = 0;
2804         }
2806         if (offset != view->offset || lineno != view->lineno) {
2807                 view->offset = offset;
2808                 view->lineno = lineno;
2809                 return TRUE;
2810         }
2812         return FALSE;
2815 /* Scrolling backend */
2816 static void
2817 do_scroll_view(struct view *view, int lines)
2819         bool redraw_current_line = FALSE;
2821         /* The rendering expects the new offset. */
2822         view->offset += lines;
2824         assert(0 <= view->offset && view->offset < view->lines);
2825         assert(lines);
2827         /* Move current line into the view. */
2828         if (view->lineno < view->offset) {
2829                 view->lineno = view->offset;
2830                 redraw_current_line = TRUE;
2831         } else if (view->lineno >= view->offset + view->height) {
2832                 view->lineno = view->offset + view->height - 1;
2833                 redraw_current_line = TRUE;
2834         }
2836         assert(view->offset <= view->lineno && view->lineno < view->lines);
2838         /* Redraw the whole screen if scrolling is pointless. */
2839         if (view->height < ABS(lines)) {
2840                 redraw_view(view);
2842         } else {
2843                 int line = lines > 0 ? view->height - lines : 0;
2844                 int end = line + ABS(lines);
2846                 scrollok(view->win, TRUE);
2847                 wscrl(view->win, lines);
2848                 scrollok(view->win, FALSE);
2850                 while (line < end && draw_view_line(view, line))
2851                         line++;
2853                 if (redraw_current_line)
2854                         draw_view_line(view, view->lineno - view->offset);
2855                 wnoutrefresh(view->win);
2856         }
2858         view->has_scrolled = TRUE;
2859         report("");
2862 /* Scroll frontend */
2863 static void
2864 scroll_view(struct view *view, enum request request)
2866         int lines = 1;
2868         assert(view_is_displayed(view));
2870         switch (request) {
2871         case REQ_SCROLL_LEFT:
2872                 if (view->yoffset == 0) {
2873                         report("Cannot scroll beyond the first column");
2874                         return;
2875                 }
2876                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2877                         view->yoffset = 0;
2878                 else
2879                         view->yoffset -= apply_step(opt_hscroll, view->width);
2880                 redraw_view_from(view, 0);
2881                 report("");
2882                 return;
2883         case REQ_SCROLL_RIGHT:
2884                 view->yoffset += apply_step(opt_hscroll, view->width);
2885                 redraw_view(view);
2886                 report("");
2887                 return;
2888         case REQ_SCROLL_PAGE_DOWN:
2889                 lines = view->height;
2890         case REQ_SCROLL_LINE_DOWN:
2891                 if (view->offset + lines > view->lines)
2892                         lines = view->lines - view->offset;
2894                 if (lines == 0 || view->offset + view->height >= view->lines) {
2895                         report("Cannot scroll beyond the last line");
2896                         return;
2897                 }
2898                 break;
2900         case REQ_SCROLL_PAGE_UP:
2901                 lines = view->height;
2902         case REQ_SCROLL_LINE_UP:
2903                 if (lines > view->offset)
2904                         lines = view->offset;
2906                 if (lines == 0) {
2907                         report("Cannot scroll beyond the first line");
2908                         return;
2909                 }
2911                 lines = -lines;
2912                 break;
2914         default:
2915                 die("request %d not handled in switch", request);
2916         }
2918         do_scroll_view(view, lines);
2921 /* Cursor moving */
2922 static void
2923 move_view(struct view *view, enum request request)
2925         int scroll_steps = 0;
2926         int steps;
2928         switch (request) {
2929         case REQ_MOVE_FIRST_LINE:
2930                 steps = -view->lineno;
2931                 break;
2933         case REQ_MOVE_LAST_LINE:
2934                 steps = view->lines - view->lineno - 1;
2935                 break;
2937         case REQ_MOVE_PAGE_UP:
2938                 steps = view->height > view->lineno
2939                       ? -view->lineno : -view->height;
2940                 break;
2942         case REQ_MOVE_PAGE_DOWN:
2943                 steps = view->lineno + view->height >= view->lines
2944                       ? view->lines - view->lineno - 1 : view->height;
2945                 break;
2947         case REQ_MOVE_UP:
2948                 steps = -1;
2949                 break;
2951         case REQ_MOVE_DOWN:
2952                 steps = 1;
2953                 break;
2955         default:
2956                 die("request %d not handled in switch", request);
2957         }
2959         if (steps <= 0 && view->lineno == 0) {
2960                 report("Cannot move beyond the first line");
2961                 return;
2963         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2964                 report("Cannot move beyond the last line");
2965                 return;
2966         }
2968         /* Move the current line */
2969         view->lineno += steps;
2970         assert(0 <= view->lineno && view->lineno < view->lines);
2972         /* Check whether the view needs to be scrolled */
2973         if (view->lineno < view->offset ||
2974             view->lineno >= view->offset + view->height) {
2975                 scroll_steps = steps;
2976                 if (steps < 0 && -steps > view->offset) {
2977                         scroll_steps = -view->offset;
2979                 } else if (steps > 0) {
2980                         if (view->lineno == view->lines - 1 &&
2981                             view->lines > view->height) {
2982                                 scroll_steps = view->lines - view->offset - 1;
2983                                 if (scroll_steps >= view->height)
2984                                         scroll_steps -= view->height - 1;
2985                         }
2986                 }
2987         }
2989         if (!view_is_displayed(view)) {
2990                 view->offset += scroll_steps;
2991                 assert(0 <= view->offset && view->offset < view->lines);
2992                 view->ops->select(view, &view->line[view->lineno]);
2993                 return;
2994         }
2996         /* Repaint the old "current" line if we be scrolling */
2997         if (ABS(steps) < view->height)
2998                 draw_view_line(view, view->lineno - steps - view->offset);
3000         if (scroll_steps) {
3001                 do_scroll_view(view, scroll_steps);
3002                 return;
3003         }
3005         /* Draw the current line */
3006         draw_view_line(view, view->lineno - view->offset);
3008         wnoutrefresh(view->win);
3009         report("");
3013 /*
3014  * Searching
3015  */
3017 static void search_view(struct view *view, enum request request);
3019 static bool
3020 grep_text(struct view *view, const char *text[])
3022         regmatch_t pmatch;
3023         size_t i;
3025         for (i = 0; text[i]; i++)
3026                 if (*text[i] &&
3027                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3028                         return TRUE;
3029         return FALSE;
3032 static void
3033 select_view_line(struct view *view, unsigned long lineno)
3035         unsigned long old_lineno = view->lineno;
3036         unsigned long old_offset = view->offset;
3038         if (goto_view_line(view, view->offset, lineno)) {
3039                 if (view_is_displayed(view)) {
3040                         if (old_offset != view->offset) {
3041                                 redraw_view(view);
3042                         } else {
3043                                 draw_view_line(view, old_lineno - view->offset);
3044                                 draw_view_line(view, view->lineno - view->offset);
3045                                 wnoutrefresh(view->win);
3046                         }
3047                 } else {
3048                         view->ops->select(view, &view->line[view->lineno]);
3049                 }
3050         }
3053 static void
3054 find_next(struct view *view, enum request request)
3056         unsigned long lineno = view->lineno;
3057         int direction;
3059         if (!*view->grep) {
3060                 if (!*opt_search)
3061                         report("No previous search");
3062                 else
3063                         search_view(view, request);
3064                 return;
3065         }
3067         switch (request) {
3068         case REQ_SEARCH:
3069         case REQ_FIND_NEXT:
3070                 direction = 1;
3071                 break;
3073         case REQ_SEARCH_BACK:
3074         case REQ_FIND_PREV:
3075                 direction = -1;
3076                 break;
3078         default:
3079                 return;
3080         }
3082         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3083                 lineno += direction;
3085         /* Note, lineno is unsigned long so will wrap around in which case it
3086          * will become bigger than view->lines. */
3087         for (; lineno < view->lines; lineno += direction) {
3088                 if (view->ops->grep(view, &view->line[lineno])) {
3089                         select_view_line(view, lineno);
3090                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3091                         return;
3092                 }
3093         }
3095         report("No match found for '%s'", view->grep);
3098 static void
3099 search_view(struct view *view, enum request request)
3101         int regex_err;
3103         if (view->regex) {
3104                 regfree(view->regex);
3105                 *view->grep = 0;
3106         } else {
3107                 view->regex = calloc(1, sizeof(*view->regex));
3108                 if (!view->regex)
3109                         return;
3110         }
3112         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3113         if (regex_err != 0) {
3114                 char buf[SIZEOF_STR] = "unknown error";
3116                 regerror(regex_err, view->regex, buf, sizeof(buf));
3117                 report("Search failed: %s", buf);
3118                 return;
3119         }
3121         string_copy(view->grep, opt_search);
3123         find_next(view, request);
3126 /*
3127  * Incremental updating
3128  */
3130 static void
3131 reset_view(struct view *view)
3133         int i;
3135         for (i = 0; i < view->lines; i++)
3136                 free(view->line[i].data);
3137         free(view->line);
3139         view->p_offset = view->offset;
3140         view->p_yoffset = view->yoffset;
3141         view->p_lineno = view->lineno;
3143         view->line = NULL;
3144         view->offset = 0;
3145         view->yoffset = 0;
3146         view->lines  = 0;
3147         view->lineno = 0;
3148         view->vid[0] = 0;
3149         view->update_secs = 0;
3152 static const char *
3153 format_arg(const char *name)
3155         static struct {
3156                 const char *name;
3157                 size_t namelen;
3158                 const char *value;
3159                 const char *value_if_empty;
3160         } vars[] = {
3161 #define FORMAT_VAR(name, value, value_if_empty) \
3162         { name, STRING_SIZE(name), value, value_if_empty }
3163                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3164                 FORMAT_VAR("%(file)",           opt_file,       ""),
3165                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3166                 FORMAT_VAR("%(head)",           ref_head,       ""),
3167                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3168                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3169                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3170         };
3171         int i;
3173         for (i = 0; i < ARRAY_SIZE(vars); i++)
3174                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3175                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3177         report("Unknown replacement: `%s`", name);
3178         return NULL;
3181 static bool
3182 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3184         char buf[SIZEOF_STR];
3185         int argc;
3186         bool noreplace = flags == FORMAT_NONE;
3188         argv_free(dst_argv);
3190         for (argc = 0; src_argv[argc]; argc++) {
3191                 const char *arg = src_argv[argc];
3192                 size_t bufpos = 0;
3194                 while (arg) {
3195                         char *next = strstr(arg, "%(");
3196                         int len = next - arg;
3197                         const char *value;
3199                         if (!next || noreplace) {
3200                                 len = strlen(arg);
3201                                 value = "";
3203                         } else {
3204                                 value = format_arg(next);
3206                                 if (!value) {
3207                                         return FALSE;
3208                                 }
3209                         }
3211                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3212                                 return FALSE;
3214                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3215                 }
3217                 dst_argv[argc] = strdup(buf);
3218                 if (!dst_argv[argc])
3219                         break;
3220         }
3222         dst_argv[argc] = NULL;
3224         return src_argv[argc] == NULL;
3227 static bool
3228 restore_view_position(struct view *view)
3230         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3231                 return FALSE;
3233         /* Changing the view position cancels the restoring. */
3234         /* FIXME: Changing back to the first line is not detected. */
3235         if (view->offset != 0 || view->lineno != 0) {
3236                 view->p_restore = FALSE;
3237                 return FALSE;
3238         }
3240         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3241             view_is_displayed(view))
3242                 werase(view->win);
3244         view->yoffset = view->p_yoffset;
3245         view->p_restore = FALSE;
3247         return TRUE;
3250 static void
3251 end_update(struct view *view, bool force)
3253         if (!view->pipe)
3254                 return;
3255         while (!view->ops->read(view, NULL))
3256                 if (!force)
3257                         return;
3258         if (force)
3259                 io_kill(view->pipe);
3260         io_done(view->pipe);
3261         view->pipe = NULL;
3264 static void
3265 setup_update(struct view *view, const char *vid)
3267         reset_view(view);
3268         string_copy_rev(view->vid, vid);
3269         view->pipe = &view->io;
3270         view->start_time = time(NULL);
3273 static bool
3274 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3276         io_init(&view->io, dir, IO_RD);
3277         return format_argv(view->io.argv, argv, replace ? FORMAT_ALL : FORMAT_NONE);
3280 static bool
3281 prepare_update(struct view *view, const char *argv[], const char *dir)
3283         if (view->pipe)
3284                 end_update(view, TRUE);
3285         return prepare_io(view, dir, argv, FALSE);
3288 static bool
3289 start_update(struct view *view, const char **argv, const char *dir)
3291         if (view->pipe)
3292                 io_done(view->pipe);
3293         return prepare_io(view, dir, argv, FALSE) &&
3294                io_start(&view->io);
3297 static bool
3298 prepare_update_file(struct view *view, const char *name)
3300         if (view->pipe)
3301                 end_update(view, TRUE);
3302         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3305 static bool
3306 begin_update(struct view *view, bool refresh)
3308         if (view->pipe)
3309                 end_update(view, TRUE);
3311         if (!refresh) {
3312                 if (view->ops->prepare) {
3313                         if (!view->ops->prepare(view))
3314                                 return FALSE;
3315                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3316                         return FALSE;
3317                 }
3319                 /* Put the current ref_* value to the view title ref
3320                  * member. This is needed by the blob view. Most other
3321                  * views sets it automatically after loading because the
3322                  * first line is a commit line. */
3323                 string_copy_rev(view->ref, view->id);
3324         }
3326         if (!io_start(&view->io))
3327                 return FALSE;
3329         setup_update(view, view->id);
3331         return TRUE;
3334 static bool
3335 update_view(struct view *view)
3337         char out_buffer[BUFSIZ * 2];
3338         char *line;
3339         /* Clear the view and redraw everything since the tree sorting
3340          * might have rearranged things. */
3341         bool redraw = view->lines == 0;
3342         bool can_read = TRUE;
3344         if (!view->pipe)
3345                 return TRUE;
3347         if (!io_can_read(view->pipe)) {
3348                 if (view->lines == 0 && view_is_displayed(view)) {
3349                         time_t secs = time(NULL) - view->start_time;
3351                         if (secs > 1 && secs > view->update_secs) {
3352                                 if (view->update_secs == 0)
3353                                         redraw_view(view);
3354                                 update_view_title(view);
3355                                 view->update_secs = secs;
3356                         }
3357                 }
3358                 return TRUE;
3359         }
3361         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3362                 if (opt_iconv_in != ICONV_NONE) {
3363                         ICONV_CONST char *inbuf = line;
3364                         size_t inlen = strlen(line) + 1;
3366                         char *outbuf = out_buffer;
3367                         size_t outlen = sizeof(out_buffer);
3369                         size_t ret;
3371                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3372                         if (ret != (size_t) -1)
3373                                 line = out_buffer;
3374                 }
3376                 if (!view->ops->read(view, line)) {
3377                         report("Allocation failure");
3378                         end_update(view, TRUE);
3379                         return FALSE;
3380                 }
3381         }
3383         {
3384                 unsigned long lines = view->lines;
3385                 int digits;
3387                 for (digits = 0; lines; digits++)
3388                         lines /= 10;
3390                 /* Keep the displayed view in sync with line number scaling. */
3391                 if (digits != view->digits) {
3392                         view->digits = digits;
3393                         if (opt_line_number || view->type == VIEW_BLAME)
3394                                 redraw = TRUE;
3395                 }
3396         }
3398         if (io_error(view->pipe)) {
3399                 report("Failed to read: %s", io_strerror(view->pipe));
3400                 end_update(view, TRUE);
3402         } else if (io_eof(view->pipe)) {
3403                 if (view_is_displayed(view))
3404                         report("");
3405                 end_update(view, FALSE);
3406         }
3408         if (restore_view_position(view))
3409                 redraw = TRUE;
3411         if (!view_is_displayed(view))
3412                 return TRUE;
3414         if (redraw)
3415                 redraw_view_from(view, 0);
3416         else
3417                 redraw_view_dirty(view);
3419         /* Update the title _after_ the redraw so that if the redraw picks up a
3420          * commit reference in view->ref it'll be available here. */
3421         update_view_title(view);
3422         return TRUE;
3425 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3427 static struct line *
3428 add_line_data(struct view *view, void *data, enum line_type type)
3430         struct line *line;
3432         if (!realloc_lines(&view->line, view->lines, 1))
3433                 return NULL;
3435         line = &view->line[view->lines++];
3436         memset(line, 0, sizeof(*line));
3437         line->type = type;
3438         line->data = data;
3439         line->dirty = 1;
3441         return line;
3444 static struct line *
3445 add_line_text(struct view *view, const char *text, enum line_type type)
3447         char *data = text ? strdup(text) : NULL;
3449         return data ? add_line_data(view, data, type) : NULL;
3452 static struct line *
3453 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3455         char buf[SIZEOF_STR];
3456         va_list args;
3458         va_start(args, fmt);
3459         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3460                 buf[0] = 0;
3461         va_end(args);
3463         return buf[0] ? add_line_text(view, buf, type) : NULL;
3466 /*
3467  * View opening
3468  */
3470 enum open_flags {
3471         OPEN_DEFAULT = 0,       /* Use default view switching. */
3472         OPEN_SPLIT = 1,         /* Split current view. */
3473         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3474         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3475         OPEN_PREPARED = 32,     /* Open already prepared command. */
3476 };
3478 static void
3479 open_view(struct view *prev, enum request request, enum open_flags flags)
3481         bool split = !!(flags & OPEN_SPLIT);
3482         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3483         bool nomaximize = !!(flags & OPEN_REFRESH);
3484         struct view *view = VIEW(request);
3485         int nviews = displayed_views();
3486         struct view *base_view = display[0];
3488         if (view == prev && nviews == 1 && !reload) {
3489                 report("Already in %s view", view->name);
3490                 return;
3491         }
3493         if (view->git_dir && !opt_git_dir[0]) {
3494                 report("The %s view is disabled in pager view", view->name);
3495                 return;
3496         }
3498         if (split) {
3499                 display[1] = view;
3500                 current_view = 1;
3501                 view->parent = prev;
3502         } else if (!nomaximize) {
3503                 /* Maximize the current view. */
3504                 memset(display, 0, sizeof(display));
3505                 current_view = 0;
3506                 display[current_view] = view;
3507         }
3509         /* No prev signals that this is the first loaded view. */
3510         if (prev && view != prev) {
3511                 view->prev = prev;
3512         }
3514         /* Resize the view when switching between split- and full-screen,
3515          * or when switching between two different full-screen views. */
3516         if (nviews != displayed_views() ||
3517             (nviews == 1 && base_view != display[0]))
3518                 resize_display();
3520         if (view->ops->open) {
3521                 if (view->pipe)
3522                         end_update(view, TRUE);
3523                 if (!view->ops->open(view)) {
3524                         report("Failed to load %s view", view->name);
3525                         return;
3526                 }
3527                 restore_view_position(view);
3529         } else if ((reload || strcmp(view->vid, view->id)) &&
3530                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3531                 report("Failed to load %s view", view->name);
3532                 return;
3533         }
3535         if (split && prev->lineno - prev->offset >= prev->height) {
3536                 /* Take the title line into account. */
3537                 int lines = prev->lineno - prev->offset - prev->height + 1;
3539                 /* Scroll the view that was split if the current line is
3540                  * outside the new limited view. */
3541                 do_scroll_view(prev, lines);
3542         }
3544         if (prev && view != prev && split && view_is_displayed(prev)) {
3545                 /* "Blur" the previous view. */
3546                 update_view_title(prev);
3547         }
3549         if (view->pipe && view->lines == 0) {
3550                 /* Clear the old view and let the incremental updating refill
3551                  * the screen. */
3552                 werase(view->win);
3553                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3554                 report("");
3555         } else if (view_is_displayed(view)) {
3556                 redraw_view(view);
3557                 report("");
3558         }
3561 static void
3562 open_external_viewer(const char *argv[], const char *dir)
3564         def_prog_mode();           /* save current tty modes */
3565         endwin();                  /* restore original tty modes */
3566         io_run_fg(argv, dir);
3567         fprintf(stderr, "Press Enter to continue");
3568         getc(opt_tty);
3569         reset_prog_mode();
3570         redraw_display(TRUE);
3573 static void
3574 open_mergetool(const char *file)
3576         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3578         open_external_viewer(mergetool_argv, opt_cdup);
3581 static void
3582 open_editor(const char *file)
3584         const char *editor_argv[] = { "vi", file, NULL };
3585         const char *editor;
3587         editor = getenv("GIT_EDITOR");
3588         if (!editor && *opt_editor)
3589                 editor = opt_editor;
3590         if (!editor)
3591                 editor = getenv("VISUAL");
3592         if (!editor)
3593                 editor = getenv("EDITOR");
3594         if (!editor)
3595                 editor = "vi";
3597         editor_argv[0] = editor;
3598         open_external_viewer(editor_argv, opt_cdup);
3601 static void
3602 open_run_request(enum request request)
3604         struct run_request *req = get_run_request(request);
3605         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3607         if (!req) {
3608                 report("Unknown run request");
3609                 return;
3610         }
3612         if (format_argv(argv, req->argv, FORMAT_ALL))
3613                 open_external_viewer(argv, NULL);
3614         argv_free(argv);
3617 /*
3618  * User request switch noodle
3619  */
3621 static int
3622 view_driver(struct view *view, enum request request)
3624         int i;
3626         if (request == REQ_NONE)
3627                 return TRUE;
3629         if (request > REQ_NONE) {
3630                 open_run_request(request);
3631                 view_request(view, REQ_REFRESH);
3632                 return TRUE;
3633         }
3635         request = view_request(view, request);
3636         if (request == REQ_NONE)
3637                 return TRUE;
3639         switch (request) {
3640         case REQ_MOVE_UP:
3641         case REQ_MOVE_DOWN:
3642         case REQ_MOVE_PAGE_UP:
3643         case REQ_MOVE_PAGE_DOWN:
3644         case REQ_MOVE_FIRST_LINE:
3645         case REQ_MOVE_LAST_LINE:
3646                 move_view(view, request);
3647                 break;
3649         case REQ_SCROLL_LEFT:
3650         case REQ_SCROLL_RIGHT:
3651         case REQ_SCROLL_LINE_DOWN:
3652         case REQ_SCROLL_LINE_UP:
3653         case REQ_SCROLL_PAGE_DOWN:
3654         case REQ_SCROLL_PAGE_UP:
3655                 scroll_view(view, request);
3656                 break;
3658         case REQ_VIEW_BLAME:
3659                 if (!opt_file[0]) {
3660                         report("No file chosen, press %s to open tree view",
3661                                get_key(view->keymap, REQ_VIEW_TREE));
3662                         break;
3663                 }
3664                 open_view(view, request, OPEN_DEFAULT);
3665                 break;
3667         case REQ_VIEW_BLOB:
3668                 if (!ref_blob[0]) {
3669                         report("No file chosen, press %s to open tree view",
3670                                get_key(view->keymap, REQ_VIEW_TREE));
3671                         break;
3672                 }
3673                 open_view(view, request, OPEN_DEFAULT);
3674                 break;
3676         case REQ_VIEW_PAGER:
3677                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3678                         report("No pager content, press %s to run command from prompt",
3679                                get_key(view->keymap, REQ_PROMPT));
3680                         break;
3681                 }
3682                 open_view(view, request, OPEN_DEFAULT);
3683                 break;
3685         case REQ_VIEW_STAGE:
3686                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3687                         report("No stage content, press %s to open the status view and choose file",
3688                                get_key(view->keymap, REQ_VIEW_STATUS));
3689                         break;
3690                 }
3691                 open_view(view, request, OPEN_DEFAULT);
3692                 break;
3694         case REQ_VIEW_STATUS:
3695                 if (opt_is_inside_work_tree == FALSE) {
3696                         report("The status view requires a working tree");
3697                         break;
3698                 }
3699                 open_view(view, request, OPEN_DEFAULT);
3700                 break;
3702         case REQ_VIEW_MAIN:
3703         case REQ_VIEW_DIFF:
3704         case REQ_VIEW_LOG:
3705         case REQ_VIEW_TREE:
3706         case REQ_VIEW_HELP:
3707         case REQ_VIEW_BRANCH:
3708                 open_view(view, request, OPEN_DEFAULT);
3709                 break;
3711         case REQ_NEXT:
3712         case REQ_PREVIOUS:
3713                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3715                 if (view->parent) {
3716                         int line;
3718                         view = view->parent;
3719                         line = view->lineno;
3720                         move_view(view, request);
3721                         if (view_is_displayed(view))
3722                                 update_view_title(view);
3723                         if (line != view->lineno)
3724                                 view_request(view, REQ_ENTER);
3725                 } else {
3726                         move_view(view, request);
3727                 }
3728                 break;
3730         case REQ_VIEW_NEXT:
3731         {
3732                 int nviews = displayed_views();
3733                 int next_view = (current_view + 1) % nviews;
3735                 if (next_view == current_view) {
3736                         report("Only one view is displayed");
3737                         break;
3738                 }
3740                 current_view = next_view;
3741                 /* Blur out the title of the previous view. */
3742                 update_view_title(view);
3743                 report("");
3744                 break;
3745         }
3746         case REQ_REFRESH:
3747                 report("Refreshing is not yet supported for the %s view", view->name);
3748                 break;
3750         case REQ_MAXIMIZE:
3751                 if (displayed_views() == 2)
3752                         maximize_view(view);
3753                 break;
3755         case REQ_OPTIONS:
3756                 open_option_menu();
3757                 break;
3759         case REQ_TOGGLE_LINENO:
3760                 toggle_view_option(&opt_line_number, "line numbers");
3761                 break;
3763         case REQ_TOGGLE_DATE:
3764                 toggle_date();
3765                 break;
3767         case REQ_TOGGLE_AUTHOR:
3768                 toggle_author();
3769                 break;
3771         case REQ_TOGGLE_REV_GRAPH:
3772                 toggle_view_option(&opt_rev_graph, "revision graph display");
3773                 break;
3775         case REQ_TOGGLE_REFS:
3776                 toggle_view_option(&opt_show_refs, "reference display");
3777                 break;
3779         case REQ_TOGGLE_SORT_FIELD:
3780         case REQ_TOGGLE_SORT_ORDER:
3781                 report("Sorting is not yet supported for the %s view", view->name);
3782                 break;
3784         case REQ_SEARCH:
3785         case REQ_SEARCH_BACK:
3786                 search_view(view, request);
3787                 break;
3789         case REQ_FIND_NEXT:
3790         case REQ_FIND_PREV:
3791                 find_next(view, request);
3792                 break;
3794         case REQ_STOP_LOADING:
3795                 foreach_view(view, i) {
3796                         if (view->pipe)
3797                                 report("Stopped loading the %s view", view->name),
3798                         end_update(view, TRUE);
3799                 }
3800                 break;
3802         case REQ_SHOW_VERSION:
3803                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3804                 return TRUE;
3806         case REQ_SCREEN_REDRAW:
3807                 redraw_display(TRUE);
3808                 break;
3810         case REQ_EDIT:
3811                 report("Nothing to edit");
3812                 break;
3814         case REQ_ENTER:
3815                 report("Nothing to enter");
3816                 break;
3818         case REQ_VIEW_CLOSE:
3819                 /* XXX: Mark closed views by letting view->prev point to the
3820                  * view itself. Parents to closed view should never be
3821                  * followed. */
3822                 if (view->prev && view->prev != view) {
3823                         maximize_view(view->prev);
3824                         view->prev = view;
3825                         break;
3826                 }
3827                 /* Fall-through */
3828         case REQ_QUIT:
3829                 return FALSE;
3831         default:
3832                 report("Unknown key, press %s for help",
3833                        get_key(view->keymap, REQ_VIEW_HELP));
3834                 return TRUE;
3835         }
3837         return TRUE;
3841 /*
3842  * View backend utilities
3843  */
3845 enum sort_field {
3846         ORDERBY_NAME,
3847         ORDERBY_DATE,
3848         ORDERBY_AUTHOR,
3849 };
3851 struct sort_state {
3852         const enum sort_field *fields;
3853         size_t size, current;
3854         bool reverse;
3855 };
3857 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3858 #define get_sort_field(state) ((state).fields[(state).current])
3859 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3861 static void
3862 sort_view(struct view *view, enum request request, struct sort_state *state,
3863           int (*compare)(const void *, const void *))
3865         switch (request) {
3866         case REQ_TOGGLE_SORT_FIELD:
3867                 state->current = (state->current + 1) % state->size;
3868                 break;
3870         case REQ_TOGGLE_SORT_ORDER:
3871                 state->reverse = !state->reverse;
3872                 break;
3873         default:
3874                 die("Not a sort request");
3875         }
3877         qsort(view->line, view->lines, sizeof(*view->line), compare);
3878         redraw_view(view);
3881 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3883 /* Small author cache to reduce memory consumption. It uses binary
3884  * search to lookup or find place to position new entries. No entries
3885  * are ever freed. */
3886 static const char *
3887 get_author(const char *name)
3889         static const char **authors;
3890         static size_t authors_size;
3891         int from = 0, to = authors_size - 1;
3893         while (from <= to) {
3894                 size_t pos = (to + from) / 2;
3895                 int cmp = strcmp(name, authors[pos]);
3897                 if (!cmp)
3898                         return authors[pos];
3900                 if (cmp < 0)
3901                         to = pos - 1;
3902                 else
3903                         from = pos + 1;
3904         }
3906         if (!realloc_authors(&authors, authors_size, 1))
3907                 return NULL;
3908         name = strdup(name);
3909         if (!name)
3910                 return NULL;
3912         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3913         authors[from] = name;
3914         authors_size++;
3916         return name;
3919 static void
3920 parse_timesec(struct time *time, const char *sec)
3922         time->sec = (time_t) atol(sec);
3925 static void
3926 parse_timezone(struct time *time, const char *zone)
3928         long tz;
3930         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3931         tz += ('0' - zone[2]) * 60 * 60;
3932         tz += ('0' - zone[3]) * 60 * 10;
3933         tz += ('0' - zone[4]) * 60;
3935         if (zone[0] == '-')
3936                 tz = -tz;
3938         time->tz = tz;
3939         time->sec -= tz;
3942 /* Parse author lines where the name may be empty:
3943  *      author  <email@address.tld> 1138474660 +0100
3944  */
3945 static void
3946 parse_author_line(char *ident, const char **author, struct time *time)
3948         char *nameend = strchr(ident, '<');
3949         char *emailend = strchr(ident, '>');
3951         if (nameend && emailend)
3952                 *nameend = *emailend = 0;
3953         ident = chomp_string(ident);
3954         if (!*ident) {
3955                 if (nameend)
3956                         ident = chomp_string(nameend + 1);
3957                 if (!*ident)
3958                         ident = "Unknown";
3959         }
3961         *author = get_author(ident);
3963         /* Parse epoch and timezone */
3964         if (emailend && emailend[1] == ' ') {
3965                 char *secs = emailend + 2;
3966                 char *zone = strchr(secs, ' ');
3968                 parse_timesec(time, secs);
3970                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3971                         parse_timezone(time, zone + 1);
3972         }
3975 static bool
3976 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3978         char rev[SIZEOF_REV];
3979         const char *revlist_argv[] = {
3980                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3981         };
3982         struct menu_item *items;
3983         char text[SIZEOF_STR];
3984         bool ok = TRUE;
3985         int i;
3987         items = calloc(*parents + 1, sizeof(*items));
3988         if (!items)
3989                 return FALSE;
3991         for (i = 0; i < *parents; i++) {
3992                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3993                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3994                     !(items[i].text = strdup(text))) {
3995                         ok = FALSE;
3996                         break;
3997                 }
3998         }
4000         if (ok) {
4001                 *parents = 0;
4002                 ok = prompt_menu("Select parent", items, parents);
4003         }
4004         for (i = 0; items[i].text; i++)
4005                 free((char *) items[i].text);
4006         free(items);
4007         return ok;
4010 static bool
4011 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4013         char buf[SIZEOF_STR * 4];
4014         const char *revlist_argv[] = {
4015                 "git", "log", "--no-color", "-1",
4016                         "--pretty=format:%P", id, "--", path, NULL
4017         };
4018         int parents;
4020         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4021             (parents = strlen(buf) / 40) < 0) {
4022                 report("Failed to get parent information");
4023                 return FALSE;
4025         } else if (parents == 0) {
4026                 if (path)
4027                         report("Path '%s' does not exist in the parent", path);
4028                 else
4029                         report("The selected commit has no parents");
4030                 return FALSE;
4031         }
4033         if (parents == 1)
4034                 parents = 0;
4035         else if (!open_commit_parent_menu(buf, &parents))
4036                 return FALSE;
4038         string_copy_rev(rev, &buf[41 * parents]);
4039         return TRUE;
4042 /*
4043  * Pager backend
4044  */
4046 static bool
4047 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4049         char text[SIZEOF_STR];
4051         if (opt_line_number && draw_lineno(view, lineno))
4052                 return TRUE;
4054         string_expand(text, sizeof(text), line->data, opt_tab_size);
4055         draw_text(view, line->type, text, TRUE);
4056         return TRUE;
4059 static bool
4060 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4062         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4063         char ref[SIZEOF_STR];
4065         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4066                 return TRUE;
4068         /* This is the only fatal call, since it can "corrupt" the buffer. */
4069         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4070                 return FALSE;
4072         return TRUE;
4075 static void
4076 add_pager_refs(struct view *view, struct line *line)
4078         char buf[SIZEOF_STR];
4079         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4080         struct ref_list *list;
4081         size_t bufpos = 0, i;
4082         const char *sep = "Refs: ";
4083         bool is_tag = FALSE;
4085         assert(line->type == LINE_COMMIT);
4087         list = get_ref_list(commit_id);
4088         if (!list) {
4089                 if (view->type == VIEW_DIFF)
4090                         goto try_add_describe_ref;
4091                 return;
4092         }
4094         for (i = 0; i < list->size; i++) {
4095                 struct ref *ref = list->refs[i];
4096                 const char *fmt = ref->tag    ? "%s[%s]" :
4097                                   ref->remote ? "%s<%s>" : "%s%s";
4099                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4100                         return;
4101                 sep = ", ";
4102                 if (ref->tag)
4103                         is_tag = TRUE;
4104         }
4106         if (!is_tag && view->type == VIEW_DIFF) {
4107 try_add_describe_ref:
4108                 /* Add <tag>-g<commit_id> "fake" reference. */
4109                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4110                         return;
4111         }
4113         if (bufpos == 0)
4114                 return;
4116         add_line_text(view, buf, LINE_PP_REFS);
4119 static bool
4120 pager_read(struct view *view, char *data)
4122         struct line *line;
4124         if (!data)
4125                 return TRUE;
4127         line = add_line_text(view, data, get_line_type(data));
4128         if (!line)
4129                 return FALSE;
4131         if (line->type == LINE_COMMIT &&
4132             (view->type == VIEW_DIFF ||
4133              view->type == VIEW_LOG))
4134                 add_pager_refs(view, line);
4136         return TRUE;
4139 static enum request
4140 pager_request(struct view *view, enum request request, struct line *line)
4142         int split = 0;
4144         if (request != REQ_ENTER)
4145                 return request;
4147         if (line->type == LINE_COMMIT &&
4148            (view->type == VIEW_LOG ||
4149             view->type == VIEW_PAGER)) {
4150                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4151                 split = 1;
4152         }
4154         /* Always scroll the view even if it was split. That way
4155          * you can use Enter to scroll through the log view and
4156          * split open each commit diff. */
4157         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4159         /* FIXME: A minor workaround. Scrolling the view will call report("")
4160          * but if we are scrolling a non-current view this won't properly
4161          * update the view title. */
4162         if (split)
4163                 update_view_title(view);
4165         return REQ_NONE;
4168 static bool
4169 pager_grep(struct view *view, struct line *line)
4171         const char *text[] = { line->data, NULL };
4173         return grep_text(view, text);
4176 static void
4177 pager_select(struct view *view, struct line *line)
4179         if (line->type == LINE_COMMIT) {
4180                 char *text = (char *)line->data + STRING_SIZE("commit ");
4182                 if (view->type != VIEW_PAGER)
4183                         string_copy_rev(view->ref, text);
4184                 string_copy_rev(ref_commit, text);
4185         }
4188 static struct view_ops pager_ops = {
4189         "line",
4190         NULL,
4191         NULL,
4192         pager_read,
4193         pager_draw,
4194         pager_request,
4195         pager_grep,
4196         pager_select,
4197 };
4199 static const char *log_argv[SIZEOF_ARG] = {
4200         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4201 };
4203 static enum request
4204 log_request(struct view *view, enum request request, struct line *line)
4206         switch (request) {
4207         case REQ_REFRESH:
4208                 load_refs();
4209                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4210                 return REQ_NONE;
4211         default:
4212                 return pager_request(view, request, line);
4213         }
4216 static struct view_ops log_ops = {
4217         "line",
4218         log_argv,
4219         NULL,
4220         pager_read,
4221         pager_draw,
4222         log_request,
4223         pager_grep,
4224         pager_select,
4225 };
4227 static const char *diff_argv[SIZEOF_ARG] = {
4228         "git", "show", "--pretty=fuller", "--no-color", "--root",
4229                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4230 };
4232 static struct view_ops diff_ops = {
4233         "line",
4234         diff_argv,
4235         NULL,
4236         pager_read,
4237         pager_draw,
4238         pager_request,
4239         pager_grep,
4240         pager_select,
4241 };
4243 /*
4244  * Help backend
4245  */
4247 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4249 static bool
4250 help_open_keymap_title(struct view *view, enum keymap keymap)
4252         struct line *line;
4254         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4255                                help_keymap_hidden[keymap] ? '+' : '-',
4256                                enum_name(keymap_table[keymap]));
4257         if (line)
4258                 line->other = keymap;
4260         return help_keymap_hidden[keymap];
4263 static void
4264 help_open_keymap(struct view *view, enum keymap keymap)
4266         const char *group = NULL;
4267         char buf[SIZEOF_STR];
4268         size_t bufpos;
4269         bool add_title = TRUE;
4270         int i;
4272         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4273                 const char *key = NULL;
4275                 if (req_info[i].request == REQ_NONE)
4276                         continue;
4278                 if (!req_info[i].request) {
4279                         group = req_info[i].help;
4280                         continue;
4281                 }
4283                 key = get_keys(keymap, req_info[i].request, TRUE);
4284                 if (!key || !*key)
4285                         continue;
4287                 if (add_title && help_open_keymap_title(view, keymap))
4288                         return;
4289                 add_title = FALSE;
4291                 if (group) {
4292                         add_line_text(view, group, LINE_HELP_GROUP);
4293                         group = NULL;
4294                 }
4296                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4297                                 enum_name(req_info[i]), req_info[i].help);
4298         }
4300         group = "External commands:";
4302         for (i = 0; i < run_requests; i++) {
4303                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4304                 const char *key;
4305                 int argc;
4307                 if (!req || req->keymap != keymap)
4308                         continue;
4310                 key = get_key_name(req->key);
4311                 if (!*key)
4312                         key = "(no key defined)";
4314                 if (add_title && help_open_keymap_title(view, keymap))
4315                         return;
4316                 if (group) {
4317                         add_line_text(view, group, LINE_HELP_GROUP);
4318                         group = NULL;
4319                 }
4321                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4322                         if (!string_format_from(buf, &bufpos, "%s%s",
4323                                                 argc ? " " : "", req->argv[argc]))
4324                                 return;
4326                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4327         }
4330 static bool
4331 help_open(struct view *view)
4333         enum keymap keymap;
4335         reset_view(view);
4336         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4337         add_line_text(view, "", LINE_DEFAULT);
4339         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4340                 help_open_keymap(view, keymap);
4342         return TRUE;
4345 static enum request
4346 help_request(struct view *view, enum request request, struct line *line)
4348         switch (request) {
4349         case REQ_ENTER:
4350                 if (line->type == LINE_HELP_KEYMAP) {
4351                         help_keymap_hidden[line->other] =
4352                                 !help_keymap_hidden[line->other];
4353                         view->p_restore = TRUE;
4354                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4355                 }
4357                 return REQ_NONE;
4358         default:
4359                 return pager_request(view, request, line);
4360         }
4363 static struct view_ops help_ops = {
4364         "line",
4365         NULL,
4366         help_open,
4367         NULL,
4368         pager_draw,
4369         help_request,
4370         pager_grep,
4371         pager_select,
4372 };
4375 /*
4376  * Tree backend
4377  */
4379 struct tree_stack_entry {
4380         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4381         unsigned long lineno;           /* Line number to restore */
4382         char *name;                     /* Position of name in opt_path */
4383 };
4385 /* The top of the path stack. */
4386 static struct tree_stack_entry *tree_stack = NULL;
4387 unsigned long tree_lineno = 0;
4389 static void
4390 pop_tree_stack_entry(void)
4392         struct tree_stack_entry *entry = tree_stack;
4394         tree_lineno = entry->lineno;
4395         entry->name[0] = 0;
4396         tree_stack = entry->prev;
4397         free(entry);
4400 static void
4401 push_tree_stack_entry(const char *name, unsigned long lineno)
4403         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4404         size_t pathlen = strlen(opt_path);
4406         if (!entry)
4407                 return;
4409         entry->prev = tree_stack;
4410         entry->name = opt_path + pathlen;
4411         tree_stack = entry;
4413         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4414                 pop_tree_stack_entry();
4415                 return;
4416         }
4418         /* Move the current line to the first tree entry. */
4419         tree_lineno = 1;
4420         entry->lineno = lineno;
4423 /* Parse output from git-ls-tree(1):
4424  *
4425  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4426  */
4428 #define SIZEOF_TREE_ATTR \
4429         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4431 #define SIZEOF_TREE_MODE \
4432         STRING_SIZE("100644 ")
4434 #define TREE_ID_OFFSET \
4435         STRING_SIZE("100644 blob ")
4437 struct tree_entry {
4438         char id[SIZEOF_REV];
4439         mode_t mode;
4440         struct time time;               /* Date from the author ident. */
4441         const char *author;             /* Author of the commit. */
4442         char name[1];
4443 };
4445 static const char *
4446 tree_path(const struct line *line)
4448         return ((struct tree_entry *) line->data)->name;
4451 static int
4452 tree_compare_entry(const struct line *line1, const struct line *line2)
4454         if (line1->type != line2->type)
4455                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4456         return strcmp(tree_path(line1), tree_path(line2));
4459 static const enum sort_field tree_sort_fields[] = {
4460         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4461 };
4462 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4464 static int
4465 tree_compare(const void *l1, const void *l2)
4467         const struct line *line1 = (const struct line *) l1;
4468         const struct line *line2 = (const struct line *) l2;
4469         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4470         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4472         if (line1->type == LINE_TREE_HEAD)
4473                 return -1;
4474         if (line2->type == LINE_TREE_HEAD)
4475                 return 1;
4477         switch (get_sort_field(tree_sort_state)) {
4478         case ORDERBY_DATE:
4479                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4481         case ORDERBY_AUTHOR:
4482                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4484         case ORDERBY_NAME:
4485         default:
4486                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4487         }
4491 static struct line *
4492 tree_entry(struct view *view, enum line_type type, const char *path,
4493            const char *mode, const char *id)
4495         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4496         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4498         if (!entry || !line) {
4499                 free(entry);
4500                 return NULL;
4501         }
4503         strncpy(entry->name, path, strlen(path));
4504         if (mode)
4505                 entry->mode = strtoul(mode, NULL, 8);
4506         if (id)
4507                 string_copy_rev(entry->id, id);
4509         return line;
4512 static bool
4513 tree_read_date(struct view *view, char *text, bool *read_date)
4515         static const char *author_name;
4516         static struct time author_time;
4518         if (!text && *read_date) {
4519                 *read_date = FALSE;
4520                 return TRUE;
4522         } else if (!text) {
4523                 char *path = *opt_path ? opt_path : ".";
4524                 /* Find next entry to process */
4525                 const char *log_file[] = {
4526                         "git", "log", "--no-color", "--pretty=raw",
4527                                 "--cc", "--raw", view->id, "--", path, NULL
4528                 };
4530                 if (!view->lines) {
4531                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4532                         report("Tree is empty");
4533                         return TRUE;
4534                 }
4536                 if (!start_update(view, log_file, opt_cdup)) {
4537                         report("Failed to load tree data");
4538                         return TRUE;
4539                 }
4541                 *read_date = TRUE;
4542                 return FALSE;
4544         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4545                 parse_author_line(text + STRING_SIZE("author "),
4546                                   &author_name, &author_time);
4548         } else if (*text == ':') {
4549                 char *pos;
4550                 size_t annotated = 1;
4551                 size_t i;
4553                 pos = strchr(text, '\t');
4554                 if (!pos)
4555                         return TRUE;
4556                 text = pos + 1;
4557                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4558                         text += strlen(opt_path);
4559                 pos = strchr(text, '/');
4560                 if (pos)
4561                         *pos = 0;
4563                 for (i = 1; i < view->lines; i++) {
4564                         struct line *line = &view->line[i];
4565                         struct tree_entry *entry = line->data;
4567                         annotated += !!entry->author;
4568                         if (entry->author || strcmp(entry->name, text))
4569                                 continue;
4571                         entry->author = author_name;
4572                         entry->time = author_time;
4573                         line->dirty = 1;
4574                         break;
4575                 }
4577                 if (annotated == view->lines)
4578                         io_kill(view->pipe);
4579         }
4580         return TRUE;
4583 static bool
4584 tree_read(struct view *view, char *text)
4586         static bool read_date = FALSE;
4587         struct tree_entry *data;
4588         struct line *entry, *line;
4589         enum line_type type;
4590         size_t textlen = text ? strlen(text) : 0;
4591         char *path = text + SIZEOF_TREE_ATTR;
4593         if (read_date || !text)
4594                 return tree_read_date(view, text, &read_date);
4596         if (textlen <= SIZEOF_TREE_ATTR)
4597                 return FALSE;
4598         if (view->lines == 0 &&
4599             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4600                 return FALSE;
4602         /* Strip the path part ... */
4603         if (*opt_path) {
4604                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4605                 size_t striplen = strlen(opt_path);
4607                 if (pathlen > striplen)
4608                         memmove(path, path + striplen,
4609                                 pathlen - striplen + 1);
4611                 /* Insert "link" to parent directory. */
4612                 if (view->lines == 1 &&
4613                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4614                         return FALSE;
4615         }
4617         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4618         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4619         if (!entry)
4620                 return FALSE;
4621         data = entry->data;
4623         /* Skip "Directory ..." and ".." line. */
4624         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4625                 if (tree_compare_entry(line, entry) <= 0)
4626                         continue;
4628                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4630                 line->data = data;
4631                 line->type = type;
4632                 for (; line <= entry; line++)
4633                         line->dirty = line->cleareol = 1;
4634                 return TRUE;
4635         }
4637         if (tree_lineno > view->lineno) {
4638                 view->lineno = tree_lineno;
4639                 tree_lineno = 0;
4640         }
4642         return TRUE;
4645 static bool
4646 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4648         struct tree_entry *entry = line->data;
4650         if (line->type == LINE_TREE_HEAD) {
4651                 if (draw_text(view, line->type, "Directory path /", TRUE))
4652                         return TRUE;
4653         } else {
4654                 if (draw_mode(view, entry->mode))
4655                         return TRUE;
4657                 if (opt_author && draw_author(view, entry->author))
4658                         return TRUE;
4660                 if (opt_date && draw_date(view, &entry->time))
4661                         return TRUE;
4662         }
4663         if (draw_text(view, line->type, entry->name, TRUE))
4664                 return TRUE;
4665         return TRUE;
4668 static void
4669 open_blob_editor(const char *id)
4671         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4672         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4673         int fd = mkstemp(file);
4675         if (fd == -1)
4676                 report("Failed to create temporary file");
4677         else if (!io_run_append(blob_argv, fd))
4678                 report("Failed to save blob data to file");
4679         else
4680                 open_editor(file);
4681         if (fd != -1)
4682                 unlink(file);
4685 static enum request
4686 tree_request(struct view *view, enum request request, struct line *line)
4688         enum open_flags flags;
4689         struct tree_entry *entry = line->data;
4691         switch (request) {
4692         case REQ_VIEW_BLAME:
4693                 if (line->type != LINE_TREE_FILE) {
4694                         report("Blame only supported for files");
4695                         return REQ_NONE;
4696                 }
4698                 string_copy(opt_ref, view->vid);
4699                 return request;
4701         case REQ_EDIT:
4702                 if (line->type != LINE_TREE_FILE) {
4703                         report("Edit only supported for files");
4704                 } else if (!is_head_commit(view->vid)) {
4705                         open_blob_editor(entry->id);
4706                 } else {
4707                         open_editor(opt_file);
4708                 }
4709                 return REQ_NONE;
4711         case REQ_TOGGLE_SORT_FIELD:
4712         case REQ_TOGGLE_SORT_ORDER:
4713                 sort_view(view, request, &tree_sort_state, tree_compare);
4714                 return REQ_NONE;
4716         case REQ_PARENT:
4717                 if (!*opt_path) {
4718                         /* quit view if at top of tree */
4719                         return REQ_VIEW_CLOSE;
4720                 }
4721                 /* fake 'cd  ..' */
4722                 line = &view->line[1];
4723                 break;
4725         case REQ_ENTER:
4726                 break;
4728         default:
4729                 return request;
4730         }
4732         /* Cleanup the stack if the tree view is at a different tree. */
4733         while (!*opt_path && tree_stack)
4734                 pop_tree_stack_entry();
4736         switch (line->type) {
4737         case LINE_TREE_DIR:
4738                 /* Depending on whether it is a subdirectory or parent link
4739                  * mangle the path buffer. */
4740                 if (line == &view->line[1] && *opt_path) {
4741                         pop_tree_stack_entry();
4743                 } else {
4744                         const char *basename = tree_path(line);
4746                         push_tree_stack_entry(basename, view->lineno);
4747                 }
4749                 /* Trees and subtrees share the same ID, so they are not not
4750                  * unique like blobs. */
4751                 flags = OPEN_RELOAD;
4752                 request = REQ_VIEW_TREE;
4753                 break;
4755         case LINE_TREE_FILE:
4756                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4757                 request = REQ_VIEW_BLOB;
4758                 break;
4760         default:
4761                 return REQ_NONE;
4762         }
4764         open_view(view, request, flags);
4765         if (request == REQ_VIEW_TREE)
4766                 view->lineno = tree_lineno;
4768         return REQ_NONE;
4771 static bool
4772 tree_grep(struct view *view, struct line *line)
4774         struct tree_entry *entry = line->data;
4775         const char *text[] = {
4776                 entry->name,
4777                 opt_author ? entry->author : "",
4778                 mkdate(&entry->time, opt_date),
4779                 NULL
4780         };
4782         return grep_text(view, text);
4785 static void
4786 tree_select(struct view *view, struct line *line)
4788         struct tree_entry *entry = line->data;
4790         if (line->type == LINE_TREE_FILE) {
4791                 string_copy_rev(ref_blob, entry->id);
4792                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4794         } else if (line->type != LINE_TREE_DIR) {
4795                 return;
4796         }
4798         string_copy_rev(view->ref, entry->id);
4801 static bool
4802 tree_prepare(struct view *view)
4804         if (view->lines == 0 && opt_prefix[0]) {
4805                 char *pos = opt_prefix;
4807                 while (pos && *pos) {
4808                         char *end = strchr(pos, '/');
4810                         if (end)
4811                                 *end = 0;
4812                         push_tree_stack_entry(pos, 0);
4813                         pos = end;
4814                         if (end) {
4815                                 *end = '/';
4816                                 pos++;
4817                         }
4818                 }
4820         } else if (strcmp(view->vid, view->id)) {
4821                 opt_path[0] = 0;
4822         }
4824         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4827 static const char *tree_argv[SIZEOF_ARG] = {
4828         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4829 };
4831 static struct view_ops tree_ops = {
4832         "file",
4833         tree_argv,
4834         NULL,
4835         tree_read,
4836         tree_draw,
4837         tree_request,
4838         tree_grep,
4839         tree_select,
4840         tree_prepare,
4841 };
4843 static bool
4844 blob_read(struct view *view, char *line)
4846         if (!line)
4847                 return TRUE;
4848         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4851 static enum request
4852 blob_request(struct view *view, enum request request, struct line *line)
4854         switch (request) {
4855         case REQ_EDIT:
4856                 open_blob_editor(view->vid);
4857                 return REQ_NONE;
4858         default:
4859                 return pager_request(view, request, line);
4860         }
4863 static const char *blob_argv[SIZEOF_ARG] = {
4864         "git", "cat-file", "blob", "%(blob)", NULL
4865 };
4867 static struct view_ops blob_ops = {
4868         "line",
4869         blob_argv,
4870         NULL,
4871         blob_read,
4872         pager_draw,
4873         blob_request,
4874         pager_grep,
4875         pager_select,
4876 };
4878 /*
4879  * Blame backend
4880  *
4881  * Loading the blame view is a two phase job:
4882  *
4883  *  1. File content is read either using opt_file from the
4884  *     filesystem or using git-cat-file.
4885  *  2. Then blame information is incrementally added by
4886  *     reading output from git-blame.
4887  */
4889 struct blame_commit {
4890         char id[SIZEOF_REV];            /* SHA1 ID. */
4891         char title[128];                /* First line of the commit message. */
4892         const char *author;             /* Author of the commit. */
4893         struct time time;               /* Date from the author ident. */
4894         char filename[128];             /* Name of file. */
4895         bool has_previous;              /* Was a "previous" line detected. */
4896 };
4898 struct blame {
4899         struct blame_commit *commit;
4900         unsigned long lineno;
4901         char text[1];
4902 };
4904 static bool
4905 blame_open(struct view *view)
4907         char path[SIZEOF_STR];
4909         if (!view->prev && *opt_prefix) {
4910                 string_copy(path, opt_file);
4911                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4912                         return FALSE;
4913         }
4915         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4916                 const char *blame_cat_file_argv[] = {
4917                         "git", "cat-file", "blob", path, NULL
4918                 };
4920                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4921                     !start_update(view, blame_cat_file_argv, opt_cdup))
4922                         return FALSE;
4923         }
4925         setup_update(view, opt_file);
4926         string_format(view->ref, "%s ...", opt_file);
4928         return TRUE;
4931 static struct blame_commit *
4932 get_blame_commit(struct view *view, const char *id)
4934         size_t i;
4936         for (i = 0; i < view->lines; i++) {
4937                 struct blame *blame = view->line[i].data;
4939                 if (!blame->commit)
4940                         continue;
4942                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4943                         return blame->commit;
4944         }
4946         {
4947                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4949                 if (commit)
4950                         string_ncopy(commit->id, id, SIZEOF_REV);
4951                 return commit;
4952         }
4955 static bool
4956 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4958         const char *pos = *posref;
4960         *posref = NULL;
4961         pos = strchr(pos + 1, ' ');
4962         if (!pos || !isdigit(pos[1]))
4963                 return FALSE;
4964         *number = atoi(pos + 1);
4965         if (*number < min || *number > max)
4966                 return FALSE;
4968         *posref = pos;
4969         return TRUE;
4972 static struct blame_commit *
4973 parse_blame_commit(struct view *view, const char *text, int *blamed)
4975         struct blame_commit *commit;
4976         struct blame *blame;
4977         const char *pos = text + SIZEOF_REV - 2;
4978         size_t orig_lineno = 0;
4979         size_t lineno;
4980         size_t group;
4982         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4983                 return NULL;
4985         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4986             !parse_number(&pos, &lineno, 1, view->lines) ||
4987             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4988                 return NULL;
4990         commit = get_blame_commit(view, text);
4991         if (!commit)
4992                 return NULL;
4994         *blamed += group;
4995         while (group--) {
4996                 struct line *line = &view->line[lineno + group - 1];
4998                 blame = line->data;
4999                 blame->commit = commit;
5000                 blame->lineno = orig_lineno + group - 1;
5001                 line->dirty = 1;
5002         }
5004         return commit;
5007 static bool
5008 blame_read_file(struct view *view, const char *line, bool *read_file)
5010         if (!line) {
5011                 const char *blame_argv[] = {
5012                         "git", "blame", "--incremental",
5013                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5014                 };
5016                 if (view->lines == 0 && !view->prev)
5017                         die("No blame exist for %s", view->vid);
5019                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5020                         report("Failed to load blame data");
5021                         return TRUE;
5022                 }
5024                 *read_file = FALSE;
5025                 return FALSE;
5027         } else {
5028                 size_t linelen = strlen(line);
5029                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5031                 if (!blame)
5032                         return FALSE;
5034                 blame->commit = NULL;
5035                 strncpy(blame->text, line, linelen);
5036                 blame->text[linelen] = 0;
5037                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5038         }
5041 static bool
5042 match_blame_header(const char *name, char **line)
5044         size_t namelen = strlen(name);
5045         bool matched = !strncmp(name, *line, namelen);
5047         if (matched)
5048                 *line += namelen;
5050         return matched;
5053 static bool
5054 blame_read(struct view *view, char *line)
5056         static struct blame_commit *commit = NULL;
5057         static int blamed = 0;
5058         static bool read_file = TRUE;
5060         if (read_file)
5061                 return blame_read_file(view, line, &read_file);
5063         if (!line) {
5064                 /* Reset all! */
5065                 commit = NULL;
5066                 blamed = 0;
5067                 read_file = TRUE;
5068                 string_format(view->ref, "%s", view->vid);
5069                 if (view_is_displayed(view)) {
5070                         update_view_title(view);
5071                         redraw_view_from(view, 0);
5072                 }
5073                 return TRUE;
5074         }
5076         if (!commit) {
5077                 commit = parse_blame_commit(view, line, &blamed);
5078                 string_format(view->ref, "%s %2d%%", view->vid,
5079                               view->lines ? blamed * 100 / view->lines : 0);
5081         } else if (match_blame_header("author ", &line)) {
5082                 commit->author = get_author(line);
5084         } else if (match_blame_header("author-time ", &line)) {
5085                 parse_timesec(&commit->time, line);
5087         } else if (match_blame_header("author-tz ", &line)) {
5088                 parse_timezone(&commit->time, line);
5090         } else if (match_blame_header("summary ", &line)) {
5091                 string_ncopy(commit->title, line, strlen(line));
5093         } else if (match_blame_header("previous ", &line)) {
5094                 commit->has_previous = TRUE;
5096         } else if (match_blame_header("filename ", &line)) {
5097                 string_ncopy(commit->filename, line, strlen(line));
5098                 commit = NULL;
5099         }
5101         return TRUE;
5104 static bool
5105 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5107         struct blame *blame = line->data;
5108         struct time *time = NULL;
5109         const char *id = NULL, *author = NULL;
5110         char text[SIZEOF_STR];
5112         if (blame->commit && *blame->commit->filename) {
5113                 id = blame->commit->id;
5114                 author = blame->commit->author;
5115                 time = &blame->commit->time;
5116         }
5118         if (opt_date && draw_date(view, time))
5119                 return TRUE;
5121         if (opt_author && draw_author(view, author))
5122                 return TRUE;
5124         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5125                 return TRUE;
5127         if (draw_lineno(view, lineno))
5128                 return TRUE;
5130         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5131         draw_text(view, LINE_DEFAULT, text, TRUE);
5132         return TRUE;
5135 static bool
5136 check_blame_commit(struct blame *blame, bool check_null_id)
5138         if (!blame->commit)
5139                 report("Commit data not loaded yet");
5140         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5141                 report("No commit exist for the selected line");
5142         else
5143                 return TRUE;
5144         return FALSE;
5147 static void
5148 setup_blame_parent_line(struct view *view, struct blame *blame)
5150         const char *diff_tree_argv[] = {
5151                 "git", "diff-tree", "-U0", blame->commit->id,
5152                         "--", blame->commit->filename, NULL
5153         };
5154         struct io io = {};
5155         int parent_lineno = -1;
5156         int blamed_lineno = -1;
5157         char *line;
5159         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5160                 return;
5162         while ((line = io_get(&io, '\n', TRUE))) {
5163                 if (*line == '@') {
5164                         char *pos = strchr(line, '+');
5166                         parent_lineno = atoi(line + 4);
5167                         if (pos)
5168                                 blamed_lineno = atoi(pos + 1);
5170                 } else if (*line == '+' && parent_lineno != -1) {
5171                         if (blame->lineno == blamed_lineno - 1 &&
5172                             !strcmp(blame->text, line + 1)) {
5173                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5174                                 break;
5175                         }
5176                         blamed_lineno++;
5177                 }
5178         }
5180         io_done(&io);
5183 static enum request
5184 blame_request(struct view *view, enum request request, struct line *line)
5186         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5187         struct blame *blame = line->data;
5189         switch (request) {
5190         case REQ_VIEW_BLAME:
5191                 if (check_blame_commit(blame, TRUE)) {
5192                         string_copy(opt_ref, blame->commit->id);
5193                         string_copy(opt_file, blame->commit->filename);
5194                         if (blame->lineno)
5195                                 view->lineno = blame->lineno;
5196                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5197                 }
5198                 break;
5200         case REQ_PARENT:
5201                 if (check_blame_commit(blame, TRUE) &&
5202                     select_commit_parent(blame->commit->id, opt_ref,
5203                                          blame->commit->filename)) {
5204                         string_copy(opt_file, blame->commit->filename);
5205                         setup_blame_parent_line(view, blame);
5206                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5207                 }
5208                 break;
5210         case REQ_ENTER:
5211                 if (!check_blame_commit(blame, FALSE))
5212                         break;
5214                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5215                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5216                         break;
5218                 if (!strcmp(blame->commit->id, NULL_ID)) {
5219                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5220                         const char *diff_index_argv[] = {
5221                                 "git", "diff-index", "--root", "--patch-with-stat",
5222                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5223                         };
5225                         if (!blame->commit->has_previous) {
5226                                 diff_index_argv[1] = "diff";
5227                                 diff_index_argv[2] = "--no-color";
5228                                 diff_index_argv[6] = "--";
5229                                 diff_index_argv[7] = "/dev/null";
5230                         }
5232                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5233                                 report("Failed to allocate diff command");
5234                                 break;
5235                         }
5236                         flags |= OPEN_PREPARED;
5237                 }
5239                 open_view(view, REQ_VIEW_DIFF, flags);
5240                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5241                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5242                 break;
5244         default:
5245                 return request;
5246         }
5248         return REQ_NONE;
5251 static bool
5252 blame_grep(struct view *view, struct line *line)
5254         struct blame *blame = line->data;
5255         struct blame_commit *commit = blame->commit;
5256         const char *text[] = {
5257                 blame->text,
5258                 commit ? commit->title : "",
5259                 commit ? commit->id : "",
5260                 commit && opt_author ? commit->author : "",
5261                 commit ? mkdate(&commit->time, opt_date) : "",
5262                 NULL
5263         };
5265         return grep_text(view, text);
5268 static void
5269 blame_select(struct view *view, struct line *line)
5271         struct blame *blame = line->data;
5272         struct blame_commit *commit = blame->commit;
5274         if (!commit)
5275                 return;
5277         if (!strcmp(commit->id, NULL_ID))
5278                 string_ncopy(ref_commit, "HEAD", 4);
5279         else
5280                 string_copy_rev(ref_commit, commit->id);
5283 static struct view_ops blame_ops = {
5284         "line",
5285         NULL,
5286         blame_open,
5287         blame_read,
5288         blame_draw,
5289         blame_request,
5290         blame_grep,
5291         blame_select,
5292 };
5294 /*
5295  * Branch backend
5296  */
5298 struct branch {
5299         const char *author;             /* Author of the last commit. */
5300         struct time time;               /* Date of the last activity. */
5301         const struct ref *ref;          /* Name and commit ID information. */
5302 };
5304 static const struct ref branch_all;
5306 static const enum sort_field branch_sort_fields[] = {
5307         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5308 };
5309 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5311 static int
5312 branch_compare(const void *l1, const void *l2)
5314         const struct branch *branch1 = ((const struct line *) l1)->data;
5315         const struct branch *branch2 = ((const struct line *) l2)->data;
5317         switch (get_sort_field(branch_sort_state)) {
5318         case ORDERBY_DATE:
5319                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5321         case ORDERBY_AUTHOR:
5322                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5324         case ORDERBY_NAME:
5325         default:
5326                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5327         }
5330 static bool
5331 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5333         struct branch *branch = line->data;
5334         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5336         if (opt_date && draw_date(view, &branch->time))
5337                 return TRUE;
5339         if (opt_author && draw_author(view, branch->author))
5340                 return TRUE;
5342         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5343         return TRUE;
5346 static enum request
5347 branch_request(struct view *view, enum request request, struct line *line)
5349         struct branch *branch = line->data;
5351         switch (request) {
5352         case REQ_REFRESH:
5353                 load_refs();
5354                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5355                 return REQ_NONE;
5357         case REQ_TOGGLE_SORT_FIELD:
5358         case REQ_TOGGLE_SORT_ORDER:
5359                 sort_view(view, request, &branch_sort_state, branch_compare);
5360                 return REQ_NONE;
5362         case REQ_ENTER:
5363                 if (branch->ref == &branch_all) {
5364                         const char *all_branches_argv[] = {
5365                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5366                                       "--topo-order", "--all", NULL
5367                         };
5368                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5370                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5371                                 report("Failed to load view of all branches");
5372                                 return REQ_NONE;
5373                         }
5374                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5375                 } else {
5376                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5377                 }
5378                 return REQ_NONE;
5380         default:
5381                 return request;
5382         }
5385 static bool
5386 branch_read(struct view *view, char *line)
5388         static char id[SIZEOF_REV];
5389         struct branch *reference;
5390         size_t i;
5392         if (!line)
5393                 return TRUE;
5395         switch (get_line_type(line)) {
5396         case LINE_COMMIT:
5397                 string_copy_rev(id, line + STRING_SIZE("commit "));
5398                 return TRUE;
5400         case LINE_AUTHOR:
5401                 for (i = 0, reference = NULL; i < view->lines; i++) {
5402                         struct branch *branch = view->line[i].data;
5404                         if (strcmp(branch->ref->id, id))
5405                                 continue;
5407                         view->line[i].dirty = TRUE;
5408                         if (reference) {
5409                                 branch->author = reference->author;
5410                                 branch->time = reference->time;
5411                                 continue;
5412                         }
5414                         parse_author_line(line + STRING_SIZE("author "),
5415                                           &branch->author, &branch->time);
5416                         reference = branch;
5417                 }
5418                 return TRUE;
5420         default:
5421                 return TRUE;
5422         }
5426 static bool
5427 branch_open_visitor(void *data, const struct ref *ref)
5429         struct view *view = data;
5430         struct branch *branch;
5432         if (ref->tag || ref->ltag || ref->remote)
5433                 return TRUE;
5435         branch = calloc(1, sizeof(*branch));
5436         if (!branch)
5437                 return FALSE;
5439         branch->ref = ref;
5440         return !!add_line_data(view, branch, LINE_DEFAULT);
5443 static bool
5444 branch_open(struct view *view)
5446         const char *branch_log[] = {
5447                 "git", "log", "--no-color", "--pretty=raw",
5448                         "--simplify-by-decoration", "--all", NULL
5449         };
5451         if (!start_update(view, branch_log, NULL)) {
5452                 report("Failed to load branch data");
5453                 return TRUE;
5454         }
5456         setup_update(view, view->id);
5457         branch_open_visitor(view, &branch_all);
5458         foreach_ref(branch_open_visitor, view);
5459         view->p_restore = TRUE;
5461         return TRUE;
5464 static bool
5465 branch_grep(struct view *view, struct line *line)
5467         struct branch *branch = line->data;
5468         const char *text[] = {
5469                 branch->ref->name,
5470                 branch->author,
5471                 NULL
5472         };
5474         return grep_text(view, text);
5477 static void
5478 branch_select(struct view *view, struct line *line)
5480         struct branch *branch = line->data;
5482         string_copy_rev(view->ref, branch->ref->id);
5483         string_copy_rev(ref_commit, branch->ref->id);
5484         string_copy_rev(ref_head, branch->ref->id);
5485         string_copy_rev(ref_branch, branch->ref->name);
5488 static struct view_ops branch_ops = {
5489         "branch",
5490         NULL,
5491         branch_open,
5492         branch_read,
5493         branch_draw,
5494         branch_request,
5495         branch_grep,
5496         branch_select,
5497 };
5499 /*
5500  * Status backend
5501  */
5503 struct status {
5504         char status;
5505         struct {
5506                 mode_t mode;
5507                 char rev[SIZEOF_REV];
5508                 char name[SIZEOF_STR];
5509         } old;
5510         struct {
5511                 mode_t mode;
5512                 char rev[SIZEOF_REV];
5513                 char name[SIZEOF_STR];
5514         } new;
5515 };
5517 static char status_onbranch[SIZEOF_STR];
5518 static struct status stage_status;
5519 static enum line_type stage_line_type;
5520 static size_t stage_chunks;
5521 static int *stage_chunk;
5523 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5525 /* This should work even for the "On branch" line. */
5526 static inline bool
5527 status_has_none(struct view *view, struct line *line)
5529         return line < view->line + view->lines && !line[1].data;
5532 /* Get fields from the diff line:
5533  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5534  */
5535 static inline bool
5536 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5538         const char *old_mode = buf +  1;
5539         const char *new_mode = buf +  8;
5540         const char *old_rev  = buf + 15;
5541         const char *new_rev  = buf + 56;
5542         const char *status   = buf + 97;
5544         if (bufsize < 98 ||
5545             old_mode[-1] != ':' ||
5546             new_mode[-1] != ' ' ||
5547             old_rev[-1]  != ' ' ||
5548             new_rev[-1]  != ' ' ||
5549             status[-1]   != ' ')
5550                 return FALSE;
5552         file->status = *status;
5554         string_copy_rev(file->old.rev, old_rev);
5555         string_copy_rev(file->new.rev, new_rev);
5557         file->old.mode = strtoul(old_mode, NULL, 8);
5558         file->new.mode = strtoul(new_mode, NULL, 8);
5560         file->old.name[0] = file->new.name[0] = 0;
5562         return TRUE;
5565 static bool
5566 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5568         struct status *unmerged = NULL;
5569         char *buf;
5570         struct io io = {};
5572         if (!io_run(&io, argv, opt_cdup, IO_RD))
5573                 return FALSE;
5575         add_line_data(view, NULL, type);
5577         while ((buf = io_get(&io, 0, TRUE))) {
5578                 struct status *file = unmerged;
5580                 if (!file) {
5581                         file = calloc(1, sizeof(*file));
5582                         if (!file || !add_line_data(view, file, type))
5583                                 goto error_out;
5584                 }
5586                 /* Parse diff info part. */
5587                 if (status) {
5588                         file->status = status;
5589                         if (status == 'A')
5590                                 string_copy(file->old.rev, NULL_ID);
5592                 } else if (!file->status || file == unmerged) {
5593                         if (!status_get_diff(file, buf, strlen(buf)))
5594                                 goto error_out;
5596                         buf = io_get(&io, 0, TRUE);
5597                         if (!buf)
5598                                 break;
5600                         /* Collapse all modified entries that follow an
5601                          * associated unmerged entry. */
5602                         if (unmerged == file) {
5603                                 unmerged->status = 'U';
5604                                 unmerged = NULL;
5605                         } else if (file->status == 'U') {
5606                                 unmerged = file;
5607                         }
5608                 }
5610                 /* Grab the old name for rename/copy. */
5611                 if (!*file->old.name &&
5612                     (file->status == 'R' || file->status == 'C')) {
5613                         string_ncopy(file->old.name, buf, strlen(buf));
5615                         buf = io_get(&io, 0, TRUE);
5616                         if (!buf)
5617                                 break;
5618                 }
5620                 /* git-ls-files just delivers a NUL separated list of
5621                  * file names similar to the second half of the
5622                  * git-diff-* output. */
5623                 string_ncopy(file->new.name, buf, strlen(buf));
5624                 if (!*file->old.name)
5625                         string_copy(file->old.name, file->new.name);
5626                 file = NULL;
5627         }
5629         if (io_error(&io)) {
5630 error_out:
5631                 io_done(&io);
5632                 return FALSE;
5633         }
5635         if (!view->line[view->lines - 1].data)
5636                 add_line_data(view, NULL, LINE_STAT_NONE);
5638         io_done(&io);
5639         return TRUE;
5642 /* Don't show unmerged entries in the staged section. */
5643 static const char *status_diff_index_argv[] = {
5644         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5645                              "--cached", "-M", "HEAD", NULL
5646 };
5648 static const char *status_diff_files_argv[] = {
5649         "git", "diff-files", "-z", NULL
5650 };
5652 static const char *status_list_other_argv[] = {
5653         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5654 };
5656 static const char *status_list_no_head_argv[] = {
5657         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5658 };
5660 static const char *update_index_argv[] = {
5661         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5662 };
5664 /* Restore the previous line number to stay in the context or select a
5665  * line with something that can be updated. */
5666 static void
5667 status_restore(struct view *view)
5669         if (view->p_lineno >= view->lines)
5670                 view->p_lineno = view->lines - 1;
5671         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5672                 view->p_lineno++;
5673         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5674                 view->p_lineno--;
5676         /* If the above fails, always skip the "On branch" line. */
5677         if (view->p_lineno < view->lines)
5678                 view->lineno = view->p_lineno;
5679         else
5680                 view->lineno = 1;
5682         if (view->lineno < view->offset)
5683                 view->offset = view->lineno;
5684         else if (view->offset + view->height <= view->lineno)
5685                 view->offset = view->lineno - view->height + 1;
5687         view->p_restore = FALSE;
5690 static void
5691 status_update_onbranch(void)
5693         static const char *paths[][2] = {
5694                 { "rebase-apply/rebasing",      "Rebasing" },
5695                 { "rebase-apply/applying",      "Applying mailbox" },
5696                 { "rebase-apply/",              "Rebasing mailbox" },
5697                 { "rebase-merge/interactive",   "Interactive rebase" },
5698                 { "rebase-merge/",              "Rebase merge" },
5699                 { "MERGE_HEAD",                 "Merging" },
5700                 { "BISECT_LOG",                 "Bisecting" },
5701                 { "HEAD",                       "On branch" },
5702         };
5703         char buf[SIZEOF_STR];
5704         struct stat stat;
5705         int i;
5707         if (is_initial_commit()) {
5708                 string_copy(status_onbranch, "Initial commit");
5709                 return;
5710         }
5712         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5713                 char *head = opt_head;
5715                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5716                     lstat(buf, &stat) < 0)
5717                         continue;
5719                 if (!*opt_head) {
5720                         struct io io = {};
5722                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5723                             io_read_buf(&io, buf, sizeof(buf))) {
5724                                 head = buf;
5725                                 if (!prefixcmp(head, "refs/heads/"))
5726                                         head += STRING_SIZE("refs/heads/");
5727                         }
5728                 }
5730                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5731                         string_copy(status_onbranch, opt_head);
5732                 return;
5733         }
5735         string_copy(status_onbranch, "Not currently on any branch");
5738 /* First parse staged info using git-diff-index(1), then parse unstaged
5739  * info using git-diff-files(1), and finally untracked files using
5740  * git-ls-files(1). */
5741 static bool
5742 status_open(struct view *view)
5744         reset_view(view);
5746         add_line_data(view, NULL, LINE_STAT_HEAD);
5747         status_update_onbranch();
5749         io_run_bg(update_index_argv);
5751         if (is_initial_commit()) {
5752                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5753                         return FALSE;
5754         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5755                 return FALSE;
5756         }
5758         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5759             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5760                 return FALSE;
5762         /* Restore the exact position or use the specialized restore
5763          * mode? */
5764         if (!view->p_restore)
5765                 status_restore(view);
5766         return TRUE;
5769 static bool
5770 status_draw(struct view *view, struct line *line, unsigned int lineno)
5772         struct status *status = line->data;
5773         enum line_type type;
5774         const char *text;
5776         if (!status) {
5777                 switch (line->type) {
5778                 case LINE_STAT_STAGED:
5779                         type = LINE_STAT_SECTION;
5780                         text = "Changes to be committed:";
5781                         break;
5783                 case LINE_STAT_UNSTAGED:
5784                         type = LINE_STAT_SECTION;
5785                         text = "Changed but not updated:";
5786                         break;
5788                 case LINE_STAT_UNTRACKED:
5789                         type = LINE_STAT_SECTION;
5790                         text = "Untracked files:";
5791                         break;
5793                 case LINE_STAT_NONE:
5794                         type = LINE_DEFAULT;
5795                         text = "  (no files)";
5796                         break;
5798                 case LINE_STAT_HEAD:
5799                         type = LINE_STAT_HEAD;
5800                         text = status_onbranch;
5801                         break;
5803                 default:
5804                         return FALSE;
5805                 }
5806         } else {
5807                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5809                 buf[0] = status->status;
5810                 if (draw_text(view, line->type, buf, TRUE))
5811                         return TRUE;
5812                 type = LINE_DEFAULT;
5813                 text = status->new.name;
5814         }
5816         draw_text(view, type, text, TRUE);
5817         return TRUE;
5820 static enum request
5821 status_load_error(struct view *view, struct view *stage, const char *path)
5823         if (displayed_views() == 2 || display[current_view] != view)
5824                 maximize_view(view);
5825         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5826         return REQ_NONE;
5829 static enum request
5830 status_enter(struct view *view, struct line *line)
5832         struct status *status = line->data;
5833         const char *oldpath = status ? status->old.name : NULL;
5834         /* Diffs for unmerged entries are empty when passing the new
5835          * path, so leave it empty. */
5836         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5837         const char *info;
5838         enum open_flags split;
5839         struct view *stage = VIEW(REQ_VIEW_STAGE);
5841         if (line->type == LINE_STAT_NONE ||
5842             (!status && line[1].type == LINE_STAT_NONE)) {
5843                 report("No file to diff");
5844                 return REQ_NONE;
5845         }
5847         switch (line->type) {
5848         case LINE_STAT_STAGED:
5849                 if (is_initial_commit()) {
5850                         const char *no_head_diff_argv[] = {
5851                                 "git", "diff", "--no-color", "--patch-with-stat",
5852                                         "--", "/dev/null", newpath, NULL
5853                         };
5855                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5856                                 return status_load_error(view, stage, newpath);
5857                 } else {
5858                         const char *index_show_argv[] = {
5859                                 "git", "diff-index", "--root", "--patch-with-stat",
5860                                         "-C", "-M", "--cached", "HEAD", "--",
5861                                         oldpath, newpath, NULL
5862                         };
5864                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5865                                 return status_load_error(view, stage, newpath);
5866                 }
5868                 if (status)
5869                         info = "Staged changes to %s";
5870                 else
5871                         info = "Staged changes";
5872                 break;
5874         case LINE_STAT_UNSTAGED:
5875         {
5876                 const char *files_show_argv[] = {
5877                         "git", "diff-files", "--root", "--patch-with-stat",
5878                                 "-C", "-M", "--", oldpath, newpath, NULL
5879                 };
5881                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5882                         return status_load_error(view, stage, newpath);
5883                 if (status)
5884                         info = "Unstaged changes to %s";
5885                 else
5886                         info = "Unstaged changes";
5887                 break;
5888         }
5889         case LINE_STAT_UNTRACKED:
5890                 if (!newpath) {
5891                         report("No file to show");
5892                         return REQ_NONE;
5893                 }
5895                 if (!suffixcmp(status->new.name, -1, "/")) {
5896                         report("Cannot display a directory");
5897                         return REQ_NONE;
5898                 }
5900                 if (!prepare_update_file(stage, newpath))
5901                         return status_load_error(view, stage, newpath);
5902                 info = "Untracked file %s";
5903                 break;
5905         case LINE_STAT_HEAD:
5906                 return REQ_NONE;
5908         default:
5909                 die("line type %d not handled in switch", line->type);
5910         }
5912         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5913         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5914         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5915                 if (status) {
5916                         stage_status = *status;
5917                 } else {
5918                         memset(&stage_status, 0, sizeof(stage_status));
5919                 }
5921                 stage_line_type = line->type;
5922                 stage_chunks = 0;
5923                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5924         }
5926         return REQ_NONE;
5929 static bool
5930 status_exists(struct status *status, enum line_type type)
5932         struct view *view = VIEW(REQ_VIEW_STATUS);
5933         unsigned long lineno;
5935         for (lineno = 0; lineno < view->lines; lineno++) {
5936                 struct line *line = &view->line[lineno];
5937                 struct status *pos = line->data;
5939                 if (line->type != type)
5940                         continue;
5941                 if (!pos && (!status || !status->status) && line[1].data) {
5942                         select_view_line(view, lineno);
5943                         return TRUE;
5944                 }
5945                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5946                         select_view_line(view, lineno);
5947                         return TRUE;
5948                 }
5949         }
5951         return FALSE;
5955 static bool
5956 status_update_prepare(struct io *io, enum line_type type)
5958         const char *staged_argv[] = {
5959                 "git", "update-index", "-z", "--index-info", NULL
5960         };
5961         const char *others_argv[] = {
5962                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5963         };
5965         switch (type) {
5966         case LINE_STAT_STAGED:
5967                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5969         case LINE_STAT_UNSTAGED:
5970         case LINE_STAT_UNTRACKED:
5971                 return io_run(io, others_argv, opt_cdup, IO_WR);
5973         default:
5974                 die("line type %d not handled in switch", type);
5975                 return FALSE;
5976         }
5979 static bool
5980 status_update_write(struct io *io, struct status *status, enum line_type type)
5982         char buf[SIZEOF_STR];
5983         size_t bufsize = 0;
5985         switch (type) {
5986         case LINE_STAT_STAGED:
5987                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5988                                         status->old.mode,
5989                                         status->old.rev,
5990                                         status->old.name, 0))
5991                         return FALSE;
5992                 break;
5994         case LINE_STAT_UNSTAGED:
5995         case LINE_STAT_UNTRACKED:
5996                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5997                         return FALSE;
5998                 break;
6000         default:
6001                 die("line type %d not handled in switch", type);
6002         }
6004         return io_write(io, buf, bufsize);
6007 static bool
6008 status_update_file(struct status *status, enum line_type type)
6010         struct io io = {};
6011         bool result;
6013         if (!status_update_prepare(&io, type))
6014                 return FALSE;
6016         result = status_update_write(&io, status, type);
6017         return io_done(&io) && result;
6020 static bool
6021 status_update_files(struct view *view, struct line *line)
6023         char buf[sizeof(view->ref)];
6024         struct io io = {};
6025         bool result = TRUE;
6026         struct line *pos = view->line + view->lines;
6027         int files = 0;
6028         int file, done;
6029         int cursor_y = -1, cursor_x = -1;
6031         if (!status_update_prepare(&io, line->type))
6032                 return FALSE;
6034         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6035                 files++;
6037         string_copy(buf, view->ref);
6038         getsyx(cursor_y, cursor_x);
6039         for (file = 0, done = 5; result && file < files; line++, file++) {
6040                 int almost_done = file * 100 / files;
6042                 if (almost_done > done) {
6043                         done = almost_done;
6044                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6045                                       file, files, done);
6046                         update_view_title(view);
6047                         setsyx(cursor_y, cursor_x);
6048                         doupdate();
6049                 }
6050                 result = status_update_write(&io, line->data, line->type);
6051         }
6052         string_copy(view->ref, buf);
6054         return io_done(&io) && result;
6057 static bool
6058 status_update(struct view *view)
6060         struct line *line = &view->line[view->lineno];
6062         assert(view->lines);
6064         if (!line->data) {
6065                 /* This should work even for the "On branch" line. */
6066                 if (line < view->line + view->lines && !line[1].data) {
6067                         report("Nothing to update");
6068                         return FALSE;
6069                 }
6071                 if (!status_update_files(view, line + 1)) {
6072                         report("Failed to update file status");
6073                         return FALSE;
6074                 }
6076         } else if (!status_update_file(line->data, line->type)) {
6077                 report("Failed to update file status");
6078                 return FALSE;
6079         }
6081         return TRUE;
6084 static bool
6085 status_revert(struct status *status, enum line_type type, bool has_none)
6087         if (!status || type != LINE_STAT_UNSTAGED) {
6088                 if (type == LINE_STAT_STAGED) {
6089                         report("Cannot revert changes to staged files");
6090                 } else if (type == LINE_STAT_UNTRACKED) {
6091                         report("Cannot revert changes to untracked files");
6092                 } else if (has_none) {
6093                         report("Nothing to revert");
6094                 } else {
6095                         report("Cannot revert changes to multiple files");
6096                 }
6098         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6099                 char mode[10] = "100644";
6100                 const char *reset_argv[] = {
6101                         "git", "update-index", "--cacheinfo", mode,
6102                                 status->old.rev, status->old.name, NULL
6103                 };
6104                 const char *checkout_argv[] = {
6105                         "git", "checkout", "--", status->old.name, NULL
6106                 };
6108                 if (status->status == 'U') {
6109                         string_format(mode, "%5o", status->old.mode);
6111                         if (status->old.mode == 0 && status->new.mode == 0) {
6112                                 reset_argv[2] = "--force-remove";
6113                                 reset_argv[3] = status->old.name;
6114                                 reset_argv[4] = NULL;
6115                         }
6117                         if (!io_run_fg(reset_argv, opt_cdup))
6118                                 return FALSE;
6119                         if (status->old.mode == 0 && status->new.mode == 0)
6120                                 return TRUE;
6121                 }
6123                 return io_run_fg(checkout_argv, opt_cdup);
6124         }
6126         return FALSE;
6129 static enum request
6130 status_request(struct view *view, enum request request, struct line *line)
6132         struct status *status = line->data;
6134         switch (request) {
6135         case REQ_STATUS_UPDATE:
6136                 if (!status_update(view))
6137                         return REQ_NONE;
6138                 break;
6140         case REQ_STATUS_REVERT:
6141                 if (!status_revert(status, line->type, status_has_none(view, line)))
6142                         return REQ_NONE;
6143                 break;
6145         case REQ_STATUS_MERGE:
6146                 if (!status || status->status != 'U') {
6147                         report("Merging only possible for files with unmerged status ('U').");
6148                         return REQ_NONE;
6149                 }
6150                 open_mergetool(status->new.name);
6151                 break;
6153         case REQ_EDIT:
6154                 if (!status)
6155                         return request;
6156                 if (status->status == 'D') {
6157                         report("File has been deleted.");
6158                         return REQ_NONE;
6159                 }
6161                 open_editor(status->new.name);
6162                 break;
6164         case REQ_VIEW_BLAME:
6165                 if (status)
6166                         opt_ref[0] = 0;
6167                 return request;
6169         case REQ_ENTER:
6170                 /* After returning the status view has been split to
6171                  * show the stage view. No further reloading is
6172                  * necessary. */
6173                 return status_enter(view, line);
6175         case REQ_REFRESH:
6176                 /* Simply reload the view. */
6177                 break;
6179         default:
6180                 return request;
6181         }
6183         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6185         return REQ_NONE;
6188 static void
6189 status_select(struct view *view, struct line *line)
6191         struct status *status = line->data;
6192         char file[SIZEOF_STR] = "all files";
6193         const char *text;
6194         const char *key;
6196         if (status && !string_format(file, "'%s'", status->new.name))
6197                 return;
6199         if (!status && line[1].type == LINE_STAT_NONE)
6200                 line++;
6202         switch (line->type) {
6203         case LINE_STAT_STAGED:
6204                 text = "Press %s to unstage %s for commit";
6205                 break;
6207         case LINE_STAT_UNSTAGED:
6208                 text = "Press %s to stage %s for commit";
6209                 break;
6211         case LINE_STAT_UNTRACKED:
6212                 text = "Press %s to stage %s for addition";
6213                 break;
6215         case LINE_STAT_HEAD:
6216         case LINE_STAT_NONE:
6217                 text = "Nothing to update";
6218                 break;
6220         default:
6221                 die("line type %d not handled in switch", line->type);
6222         }
6224         if (status && status->status == 'U') {
6225                 text = "Press %s to resolve conflict in %s";
6226                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6228         } else {
6229                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6230         }
6232         string_format(view->ref, text, key, file);
6233         if (status)
6234                 string_copy(opt_file, status->new.name);
6237 static bool
6238 status_grep(struct view *view, struct line *line)
6240         struct status *status = line->data;
6242         if (status) {
6243                 const char buf[2] = { status->status, 0 };
6244                 const char *text[] = { status->new.name, buf, NULL };
6246                 return grep_text(view, text);
6247         }
6249         return FALSE;
6252 static struct view_ops status_ops = {
6253         "file",
6254         NULL,
6255         status_open,
6256         NULL,
6257         status_draw,
6258         status_request,
6259         status_grep,
6260         status_select,
6261 };
6264 static bool
6265 stage_diff_write(struct io *io, struct line *line, struct line *end)
6267         while (line < end) {
6268                 if (!io_write(io, line->data, strlen(line->data)) ||
6269                     !io_write(io, "\n", 1))
6270                         return FALSE;
6271                 line++;
6272                 if (line->type == LINE_DIFF_CHUNK ||
6273                     line->type == LINE_DIFF_HEADER)
6274                         break;
6275         }
6277         return TRUE;
6280 static struct line *
6281 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6283         for (; view->line < line; line--)
6284                 if (line->type == type)
6285                         return line;
6287         return NULL;
6290 static bool
6291 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6293         const char *apply_argv[SIZEOF_ARG] = {
6294                 "git", "apply", "--whitespace=nowarn", NULL
6295         };
6296         struct line *diff_hdr;
6297         struct io io = {};
6298         int argc = 3;
6300         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6301         if (!diff_hdr)
6302                 return FALSE;
6304         if (!revert)
6305                 apply_argv[argc++] = "--cached";
6306         if (revert || stage_line_type == LINE_STAT_STAGED)
6307                 apply_argv[argc++] = "-R";
6308         apply_argv[argc++] = "-";
6309         apply_argv[argc++] = NULL;
6310         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6311                 return FALSE;
6313         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6314             !stage_diff_write(&io, chunk, view->line + view->lines))
6315                 chunk = NULL;
6317         io_done(&io);
6318         io_run_bg(update_index_argv);
6320         return chunk ? TRUE : FALSE;
6323 static bool
6324 stage_update(struct view *view, struct line *line)
6326         struct line *chunk = NULL;
6328         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6329                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6331         if (chunk) {
6332                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6333                         report("Failed to apply chunk");
6334                         return FALSE;
6335                 }
6337         } else if (!stage_status.status) {
6338                 view = VIEW(REQ_VIEW_STATUS);
6340                 for (line = view->line; line < view->line + view->lines; line++)
6341                         if (line->type == stage_line_type)
6342                                 break;
6344                 if (!status_update_files(view, line + 1)) {
6345                         report("Failed to update files");
6346                         return FALSE;
6347                 }
6349         } else if (!status_update_file(&stage_status, stage_line_type)) {
6350                 report("Failed to update file");
6351                 return FALSE;
6352         }
6354         return TRUE;
6357 static bool
6358 stage_revert(struct view *view, struct line *line)
6360         struct line *chunk = NULL;
6362         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6363                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6365         if (chunk) {
6366                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6367                         return FALSE;
6369                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6370                         report("Failed to revert chunk");
6371                         return FALSE;
6372                 }
6373                 return TRUE;
6375         } else {
6376                 return status_revert(stage_status.status ? &stage_status : NULL,
6377                                      stage_line_type, FALSE);
6378         }
6382 static void
6383 stage_next(struct view *view, struct line *line)
6385         int i;
6387         if (!stage_chunks) {
6388                 for (line = view->line; line < view->line + view->lines; line++) {
6389                         if (line->type != LINE_DIFF_CHUNK)
6390                                 continue;
6392                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6393                                 report("Allocation failure");
6394                                 return;
6395                         }
6397                         stage_chunk[stage_chunks++] = line - view->line;
6398                 }
6399         }
6401         for (i = 0; i < stage_chunks; i++) {
6402                 if (stage_chunk[i] > view->lineno) {
6403                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6404                         report("Chunk %d of %d", i + 1, stage_chunks);
6405                         return;
6406                 }
6407         }
6409         report("No next chunk found");
6412 static enum request
6413 stage_request(struct view *view, enum request request, struct line *line)
6415         switch (request) {
6416         case REQ_STATUS_UPDATE:
6417                 if (!stage_update(view, line))
6418                         return REQ_NONE;
6419                 break;
6421         case REQ_STATUS_REVERT:
6422                 if (!stage_revert(view, line))
6423                         return REQ_NONE;
6424                 break;
6426         case REQ_STAGE_NEXT:
6427                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6428                         report("File is untracked; press %s to add",
6429                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6430                         return REQ_NONE;
6431                 }
6432                 stage_next(view, line);
6433                 return REQ_NONE;
6435         case REQ_EDIT:
6436                 if (!stage_status.new.name[0])
6437                         return request;
6438                 if (stage_status.status == 'D') {
6439                         report("File has been deleted.");
6440                         return REQ_NONE;
6441                 }
6443                 open_editor(stage_status.new.name);
6444                 break;
6446         case REQ_REFRESH:
6447                 /* Reload everything ... */
6448                 break;
6450         case REQ_VIEW_BLAME:
6451                 if (stage_status.new.name[0]) {
6452                         string_copy(opt_file, stage_status.new.name);
6453                         opt_ref[0] = 0;
6454                 }
6455                 return request;
6457         case REQ_ENTER:
6458                 return pager_request(view, request, line);
6460         default:
6461                 return request;
6462         }
6464         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6465         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6467         /* Check whether the staged entry still exists, and close the
6468          * stage view if it doesn't. */
6469         if (!status_exists(&stage_status, stage_line_type)) {
6470                 status_restore(VIEW(REQ_VIEW_STATUS));
6471                 return REQ_VIEW_CLOSE;
6472         }
6474         if (stage_line_type == LINE_STAT_UNTRACKED) {
6475                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6476                         report("Cannot display a directory");
6477                         return REQ_NONE;
6478                 }
6480                 if (!prepare_update_file(view, stage_status.new.name)) {
6481                         report("Failed to open file: %s", strerror(errno));
6482                         return REQ_NONE;
6483                 }
6484         }
6485         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6487         return REQ_NONE;
6490 static struct view_ops stage_ops = {
6491         "line",
6492         NULL,
6493         NULL,
6494         pager_read,
6495         pager_draw,
6496         stage_request,
6497         pager_grep,
6498         pager_select,
6499 };
6502 /*
6503  * Revision graph
6504  */
6506 struct commit {
6507         char id[SIZEOF_REV];            /* SHA1 ID. */
6508         char title[128];                /* First line of the commit message. */
6509         const char *author;             /* Author of the commit. */
6510         struct time time;               /* Date from the author ident. */
6511         struct ref_list *refs;          /* Repository references. */
6512         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6513         size_t graph_size;              /* The width of the graph array. */
6514         bool has_parents;               /* Rewritten --parents seen. */
6515 };
6517 /* Size of rev graph with no  "padding" columns */
6518 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6520 struct rev_graph {
6521         struct rev_graph *prev, *next, *parents;
6522         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6523         size_t size;
6524         struct commit *commit;
6525         size_t pos;
6526         unsigned int boundary:1;
6527 };
6529 /* Parents of the commit being visualized. */
6530 static struct rev_graph graph_parents[4];
6532 /* The current stack of revisions on the graph. */
6533 static struct rev_graph graph_stacks[4] = {
6534         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6535         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6536         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6537         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6538 };
6540 static inline bool
6541 graph_parent_is_merge(struct rev_graph *graph)
6543         return graph->parents->size > 1;
6546 static inline void
6547 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6549         struct commit *commit = graph->commit;
6551         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6552                 commit->graph[commit->graph_size++] = symbol;
6555 static void
6556 clear_rev_graph(struct rev_graph *graph)
6558         graph->boundary = 0;
6559         graph->size = graph->pos = 0;
6560         graph->commit = NULL;
6561         memset(graph->parents, 0, sizeof(*graph->parents));
6564 static void
6565 done_rev_graph(struct rev_graph *graph)
6567         if (graph_parent_is_merge(graph) &&
6568             graph->pos < graph->size - 1 &&
6569             graph->next->size == graph->size + graph->parents->size - 1) {
6570                 size_t i = graph->pos + graph->parents->size - 1;
6572                 graph->commit->graph_size = i * 2;
6573                 while (i < graph->next->size - 1) {
6574                         append_to_rev_graph(graph, ' ');
6575                         append_to_rev_graph(graph, '\\');
6576                         i++;
6577                 }
6578         }
6580         clear_rev_graph(graph);
6583 static void
6584 push_rev_graph(struct rev_graph *graph, const char *parent)
6586         int i;
6588         /* "Collapse" duplicate parents lines.
6589          *
6590          * FIXME: This needs to also update update the drawn graph but
6591          * for now it just serves as a method for pruning graph lines. */
6592         for (i = 0; i < graph->size; i++)
6593                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6594                         return;
6596         if (graph->size < SIZEOF_REVITEMS) {
6597                 string_copy_rev(graph->rev[graph->size++], parent);
6598         }
6601 static chtype
6602 get_rev_graph_symbol(struct rev_graph *graph)
6604         chtype symbol;
6606         if (graph->boundary)
6607                 symbol = REVGRAPH_BOUND;
6608         else if (graph->parents->size == 0)
6609                 symbol = REVGRAPH_INIT;
6610         else if (graph_parent_is_merge(graph))
6611                 symbol = REVGRAPH_MERGE;
6612         else if (graph->pos >= graph->size)
6613                 symbol = REVGRAPH_BRANCH;
6614         else
6615                 symbol = REVGRAPH_COMMIT;
6617         return symbol;
6620 static void
6621 draw_rev_graph(struct rev_graph *graph)
6623         struct rev_filler {
6624                 chtype separator, line;
6625         };
6626         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6627         static struct rev_filler fillers[] = {
6628                 { ' ',  '|' },
6629                 { '`',  '.' },
6630                 { '\'', ' ' },
6631                 { '/',  ' ' },
6632         };
6633         chtype symbol = get_rev_graph_symbol(graph);
6634         struct rev_filler *filler;
6635         size_t i;
6637         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6638         filler = &fillers[DEFAULT];
6640         for (i = 0; i < graph->pos; i++) {
6641                 append_to_rev_graph(graph, filler->line);
6642                 if (graph_parent_is_merge(graph->prev) &&
6643                     graph->prev->pos == i)
6644                         filler = &fillers[RSHARP];
6646                 append_to_rev_graph(graph, filler->separator);
6647         }
6649         /* Place the symbol for this revision. */
6650         append_to_rev_graph(graph, symbol);
6652         if (graph->prev->size > graph->size)
6653                 filler = &fillers[RDIAG];
6654         else
6655                 filler = &fillers[DEFAULT];
6657         i++;
6659         for (; i < graph->size; i++) {
6660                 append_to_rev_graph(graph, filler->separator);
6661                 append_to_rev_graph(graph, filler->line);
6662                 if (graph_parent_is_merge(graph->prev) &&
6663                     i < graph->prev->pos + graph->parents->size)
6664                         filler = &fillers[RSHARP];
6665                 if (graph->prev->size > graph->size)
6666                         filler = &fillers[LDIAG];
6667         }
6669         if (graph->prev->size > graph->size) {
6670                 append_to_rev_graph(graph, filler->separator);
6671                 if (filler->line != ' ')
6672                         append_to_rev_graph(graph, filler->line);
6673         }
6676 /* Prepare the next rev graph */
6677 static void
6678 prepare_rev_graph(struct rev_graph *graph)
6680         size_t i;
6682         /* First, traverse all lines of revisions up to the active one. */
6683         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6684                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6685                         break;
6687                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6688         }
6690         /* Interleave the new revision parent(s). */
6691         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6692                 push_rev_graph(graph->next, graph->parents->rev[i]);
6694         /* Lastly, put any remaining revisions. */
6695         for (i = graph->pos + 1; i < graph->size; i++)
6696                 push_rev_graph(graph->next, graph->rev[i]);
6699 static void
6700 update_rev_graph(struct view *view, struct rev_graph *graph)
6702         /* If this is the finalizing update ... */
6703         if (graph->commit)
6704                 prepare_rev_graph(graph);
6706         /* Graph visualization needs a one rev look-ahead,
6707          * so the first update doesn't visualize anything. */
6708         if (!graph->prev->commit)
6709                 return;
6711         if (view->lines > 2)
6712                 view->line[view->lines - 3].dirty = 1;
6713         if (view->lines > 1)
6714                 view->line[view->lines - 2].dirty = 1;
6715         draw_rev_graph(graph->prev);
6716         done_rev_graph(graph->prev->prev);
6720 /*
6721  * Main view backend
6722  */
6724 static const char *main_argv[SIZEOF_ARG] = {
6725         "git", "log", "--no-color", "--pretty=raw", "--parents",
6726                       "--topo-order", "%(head)", NULL
6727 };
6729 static bool
6730 main_draw(struct view *view, struct line *line, unsigned int lineno)
6732         struct commit *commit = line->data;
6734         if (!commit->author)
6735                 return FALSE;
6737         if (opt_date && draw_date(view, &commit->time))
6738                 return TRUE;
6740         if (opt_author && draw_author(view, commit->author))
6741                 return TRUE;
6743         if (opt_rev_graph && commit->graph_size &&
6744             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6745                 return TRUE;
6747         if (opt_show_refs && commit->refs) {
6748                 size_t i;
6750                 for (i = 0; i < commit->refs->size; i++) {
6751                         struct ref *ref = commit->refs->refs[i];
6752                         enum line_type type;
6754                         if (ref->head)
6755                                 type = LINE_MAIN_HEAD;
6756                         else if (ref->ltag)
6757                                 type = LINE_MAIN_LOCAL_TAG;
6758                         else if (ref->tag)
6759                                 type = LINE_MAIN_TAG;
6760                         else if (ref->tracked)
6761                                 type = LINE_MAIN_TRACKED;
6762                         else if (ref->remote)
6763                                 type = LINE_MAIN_REMOTE;
6764                         else
6765                                 type = LINE_MAIN_REF;
6767                         if (draw_text(view, type, "[", TRUE) ||
6768                             draw_text(view, type, ref->name, TRUE) ||
6769                             draw_text(view, type, "]", TRUE))
6770                                 return TRUE;
6772                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6773                                 return TRUE;
6774                 }
6775         }
6777         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6778         return TRUE;
6781 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6782 static bool
6783 main_read(struct view *view, char *line)
6785         static struct rev_graph *graph = graph_stacks;
6786         enum line_type type;
6787         struct commit *commit;
6789         if (!line) {
6790                 int i;
6792                 if (!view->lines && !view->prev)
6793                         die("No revisions match the given arguments.");
6794                 if (view->lines > 0) {
6795                         commit = view->line[view->lines - 1].data;
6796                         view->line[view->lines - 1].dirty = 1;
6797                         if (!commit->author) {
6798                                 view->lines--;
6799                                 free(commit);
6800                                 graph->commit = NULL;
6801                         }
6802                 }
6803                 update_rev_graph(view, graph);
6805                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6806                         clear_rev_graph(&graph_stacks[i]);
6807                 return TRUE;
6808         }
6810         type = get_line_type(line);
6811         if (type == LINE_COMMIT) {
6812                 commit = calloc(1, sizeof(struct commit));
6813                 if (!commit)
6814                         return FALSE;
6816                 line += STRING_SIZE("commit ");
6817                 if (*line == '-') {
6818                         graph->boundary = 1;
6819                         line++;
6820                 }
6822                 string_copy_rev(commit->id, line);
6823                 commit->refs = get_ref_list(commit->id);
6824                 graph->commit = commit;
6825                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6827                 while ((line = strchr(line, ' '))) {
6828                         line++;
6829                         push_rev_graph(graph->parents, line);
6830                         commit->has_parents = TRUE;
6831                 }
6832                 return TRUE;
6833         }
6835         if (!view->lines)
6836                 return TRUE;
6837         commit = view->line[view->lines - 1].data;
6839         switch (type) {
6840         case LINE_PARENT:
6841                 if (commit->has_parents)
6842                         break;
6843                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6844                 break;
6846         case LINE_AUTHOR:
6847                 parse_author_line(line + STRING_SIZE("author "),
6848                                   &commit->author, &commit->time);
6849                 update_rev_graph(view, graph);
6850                 graph = graph->next;
6851                 break;
6853         default:
6854                 /* Fill in the commit title if it has not already been set. */
6855                 if (commit->title[0])
6856                         break;
6858                 /* Require titles to start with a non-space character at the
6859                  * offset used by git log. */
6860                 if (strncmp(line, "    ", 4))
6861                         break;
6862                 line += 4;
6863                 /* Well, if the title starts with a whitespace character,
6864                  * try to be forgiving.  Otherwise we end up with no title. */
6865                 while (isspace(*line))
6866                         line++;
6867                 if (*line == '\0')
6868                         break;
6869                 /* FIXME: More graceful handling of titles; append "..." to
6870                  * shortened titles, etc. */
6872                 string_expand(commit->title, sizeof(commit->title), line, 1);
6873                 view->line[view->lines - 1].dirty = 1;
6874         }
6876         return TRUE;
6879 static enum request
6880 main_request(struct view *view, enum request request, struct line *line)
6882         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6884         switch (request) {
6885         case REQ_ENTER:
6886                 open_view(view, REQ_VIEW_DIFF, flags);
6887                 break;
6888         case REQ_REFRESH:
6889                 load_refs();
6890                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6891                 break;
6892         default:
6893                 return request;
6894         }
6896         return REQ_NONE;
6899 static bool
6900 grep_refs(struct ref_list *list, regex_t *regex)
6902         regmatch_t pmatch;
6903         size_t i;
6905         if (!opt_show_refs || !list)
6906                 return FALSE;
6908         for (i = 0; i < list->size; i++) {
6909                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6910                         return TRUE;
6911         }
6913         return FALSE;
6916 static bool
6917 main_grep(struct view *view, struct line *line)
6919         struct commit *commit = line->data;
6920         const char *text[] = {
6921                 commit->title,
6922                 opt_author ? commit->author : "",
6923                 mkdate(&commit->time, opt_date),
6924                 NULL
6925         };
6927         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6930 static void
6931 main_select(struct view *view, struct line *line)
6933         struct commit *commit = line->data;
6935         string_copy_rev(view->ref, commit->id);
6936         string_copy_rev(ref_commit, view->ref);
6939 static struct view_ops main_ops = {
6940         "commit",
6941         main_argv,
6942         NULL,
6943         main_read,
6944         main_draw,
6945         main_request,
6946         main_grep,
6947         main_select,
6948 };
6951 /*
6952  * Status management
6953  */
6955 /* Whether or not the curses interface has been initialized. */
6956 static bool cursed = FALSE;
6958 /* Terminal hacks and workarounds. */
6959 static bool use_scroll_redrawwin;
6960 static bool use_scroll_status_wclear;
6962 /* The status window is used for polling keystrokes. */
6963 static WINDOW *status_win;
6965 /* Reading from the prompt? */
6966 static bool input_mode = FALSE;
6968 static bool status_empty = FALSE;
6970 /* Update status and title window. */
6971 static void
6972 report(const char *msg, ...)
6974         struct view *view = display[current_view];
6976         if (input_mode)
6977                 return;
6979         if (!view) {
6980                 char buf[SIZEOF_STR];
6981                 va_list args;
6983                 va_start(args, msg);
6984                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6985                         buf[sizeof(buf) - 1] = 0;
6986                         buf[sizeof(buf) - 2] = '.';
6987                         buf[sizeof(buf) - 3] = '.';
6988                         buf[sizeof(buf) - 4] = '.';
6989                 }
6990                 va_end(args);
6991                 die("%s", buf);
6992         }
6994         if (!status_empty || *msg) {
6995                 va_list args;
6997                 va_start(args, msg);
6999                 wmove(status_win, 0, 0);
7000                 if (view->has_scrolled && use_scroll_status_wclear)
7001                         wclear(status_win);
7002                 if (*msg) {
7003                         vwprintw(status_win, msg, args);
7004                         status_empty = FALSE;
7005                 } else {
7006                         status_empty = TRUE;
7007                 }
7008                 wclrtoeol(status_win);
7009                 wnoutrefresh(status_win);
7011                 va_end(args);
7012         }
7014         update_view_title(view);
7017 static void
7018 init_display(void)
7020         const char *term;
7021         int x, y;
7023         /* Initialize the curses library */
7024         if (isatty(STDIN_FILENO)) {
7025                 cursed = !!initscr();
7026                 opt_tty = stdin;
7027         } else {
7028                 /* Leave stdin and stdout alone when acting as a pager. */
7029                 opt_tty = fopen("/dev/tty", "r+");
7030                 if (!opt_tty)
7031                         die("Failed to open /dev/tty");
7032                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7033         }
7035         if (!cursed)
7036                 die("Failed to initialize curses");
7038         nonl();         /* Disable conversion and detect newlines from input. */
7039         cbreak();       /* Take input chars one at a time, no wait for \n */
7040         noecho();       /* Don't echo input */
7041         leaveok(stdscr, FALSE);
7043         if (has_colors())
7044                 init_colors();
7046         getmaxyx(stdscr, y, x);
7047         status_win = newwin(1, 0, y - 1, 0);
7048         if (!status_win)
7049                 die("Failed to create status window");
7051         /* Enable keyboard mapping */
7052         keypad(status_win, TRUE);
7053         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7055         TABSIZE = opt_tab_size;
7057         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7058         if (term && !strcmp(term, "gnome-terminal")) {
7059                 /* In the gnome-terminal-emulator, the message from
7060                  * scrolling up one line when impossible followed by
7061                  * scrolling down one line causes corruption of the
7062                  * status line. This is fixed by calling wclear. */
7063                 use_scroll_status_wclear = TRUE;
7064                 use_scroll_redrawwin = FALSE;
7066         } else if (term && !strcmp(term, "xrvt-xpm")) {
7067                 /* No problems with full optimizations in xrvt-(unicode)
7068                  * and aterm. */
7069                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7071         } else {
7072                 /* When scrolling in (u)xterm the last line in the
7073                  * scrolling direction will update slowly. */
7074                 use_scroll_redrawwin = TRUE;
7075                 use_scroll_status_wclear = FALSE;
7076         }
7079 static int
7080 get_input(int prompt_position)
7082         struct view *view;
7083         int i, key, cursor_y, cursor_x;
7084         bool loading = FALSE;
7086         if (prompt_position)
7087                 input_mode = TRUE;
7089         while (TRUE) {
7090                 foreach_view (view, i) {
7091                         update_view(view);
7092                         if (view_is_displayed(view) && view->has_scrolled &&
7093                             use_scroll_redrawwin)
7094                                 redrawwin(view->win);
7095                         view->has_scrolled = FALSE;
7096                         if (view->pipe)
7097                                 loading = TRUE;
7098                 }
7100                 /* Update the cursor position. */
7101                 if (prompt_position) {
7102                         getbegyx(status_win, cursor_y, cursor_x);
7103                         cursor_x = prompt_position;
7104                 } else {
7105                         view = display[current_view];
7106                         getbegyx(view->win, cursor_y, cursor_x);
7107                         cursor_x = view->width - 1;
7108                         cursor_y += view->lineno - view->offset;
7109                 }
7110                 setsyx(cursor_y, cursor_x);
7112                 /* Refresh, accept single keystroke of input */
7113                 doupdate();
7114                 nodelay(status_win, loading);
7115                 key = wgetch(status_win);
7117                 /* wgetch() with nodelay() enabled returns ERR when
7118                  * there's no input. */
7119                 if (key == ERR) {
7121                 } else if (key == KEY_RESIZE) {
7122                         int height, width;
7124                         getmaxyx(stdscr, height, width);
7126                         wresize(status_win, 1, width);
7127                         mvwin(status_win, height - 1, 0);
7128                         wnoutrefresh(status_win);
7129                         resize_display();
7130                         redraw_display(TRUE);
7132                 } else {
7133                         input_mode = FALSE;
7134                         return key;
7135                 }
7136         }
7139 static char *
7140 prompt_input(const char *prompt, input_handler handler, void *data)
7142         enum input_status status = INPUT_OK;
7143         static char buf[SIZEOF_STR];
7144         size_t pos = 0;
7146         buf[pos] = 0;
7148         while (status == INPUT_OK || status == INPUT_SKIP) {
7149                 int key;
7151                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7152                 wclrtoeol(status_win);
7154                 key = get_input(pos + 1);
7155                 switch (key) {
7156                 case KEY_RETURN:
7157                 case KEY_ENTER:
7158                 case '\n':
7159                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7160                         break;
7162                 case KEY_BACKSPACE:
7163                         if (pos > 0)
7164                                 buf[--pos] = 0;
7165                         else
7166                                 status = INPUT_CANCEL;
7167                         break;
7169                 case KEY_ESC:
7170                         status = INPUT_CANCEL;
7171                         break;
7173                 default:
7174                         if (pos >= sizeof(buf)) {
7175                                 report("Input string too long");
7176                                 return NULL;
7177                         }
7179                         status = handler(data, buf, key);
7180                         if (status == INPUT_OK)
7181                                 buf[pos++] = (char) key;
7182                 }
7183         }
7185         /* Clear the status window */
7186         status_empty = FALSE;
7187         report("");
7189         if (status == INPUT_CANCEL)
7190                 return NULL;
7192         buf[pos++] = 0;
7194         return buf;
7197 static enum input_status
7198 prompt_yesno_handler(void *data, char *buf, int c)
7200         if (c == 'y' || c == 'Y')
7201                 return INPUT_STOP;
7202         if (c == 'n' || c == 'N')
7203                 return INPUT_CANCEL;
7204         return INPUT_SKIP;
7207 static bool
7208 prompt_yesno(const char *prompt)
7210         char prompt2[SIZEOF_STR];
7212         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7213                 return FALSE;
7215         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7218 static enum input_status
7219 read_prompt_handler(void *data, char *buf, int c)
7221         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7224 static char *
7225 read_prompt(const char *prompt)
7227         return prompt_input(prompt, read_prompt_handler, NULL);
7230 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7232         enum input_status status = INPUT_OK;
7233         int size = 0;
7235         while (items[size].text)
7236                 size++;
7238         while (status == INPUT_OK) {
7239                 const struct menu_item *item = &items[*selected];
7240                 int key;
7241                 int i;
7243                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7244                           prompt, *selected + 1, size);
7245                 if (item->hotkey)
7246                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7247                 wprintw(status_win, "%s", item->text);
7248                 wclrtoeol(status_win);
7250                 key = get_input(COLS - 1);
7251                 switch (key) {
7252                 case KEY_RETURN:
7253                 case KEY_ENTER:
7254                 case '\n':
7255                         status = INPUT_STOP;
7256                         break;
7258                 case KEY_LEFT:
7259                 case KEY_UP:
7260                         *selected = *selected - 1;
7261                         if (*selected < 0)
7262                                 *selected = size - 1;
7263                         break;
7265                 case KEY_RIGHT:
7266                 case KEY_DOWN:
7267                         *selected = (*selected + 1) % size;
7268                         break;
7270                 case KEY_ESC:
7271                         status = INPUT_CANCEL;
7272                         break;
7274                 default:
7275                         for (i = 0; items[i].text; i++)
7276                                 if (items[i].hotkey == key) {
7277                                         *selected = i;
7278                                         status = INPUT_STOP;
7279                                         break;
7280                                 }
7281                 }
7282         }
7284         /* Clear the status window */
7285         status_empty = FALSE;
7286         report("");
7288         return status != INPUT_CANCEL;
7291 /*
7292  * Repository properties
7293  */
7295 static struct ref **refs = NULL;
7296 static size_t refs_size = 0;
7297 static struct ref *refs_head = NULL;
7299 static struct ref_list **ref_lists = NULL;
7300 static size_t ref_lists_size = 0;
7302 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7303 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7304 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7306 static int
7307 compare_refs(const void *ref1_, const void *ref2_)
7309         const struct ref *ref1 = *(const struct ref **)ref1_;
7310         const struct ref *ref2 = *(const struct ref **)ref2_;
7312         if (ref1->tag != ref2->tag)
7313                 return ref2->tag - ref1->tag;
7314         if (ref1->ltag != ref2->ltag)
7315                 return ref2->ltag - ref2->ltag;
7316         if (ref1->head != ref2->head)
7317                 return ref2->head - ref1->head;
7318         if (ref1->tracked != ref2->tracked)
7319                 return ref2->tracked - ref1->tracked;
7320         if (ref1->remote != ref2->remote)
7321                 return ref2->remote - ref1->remote;
7322         return strcmp(ref1->name, ref2->name);
7325 static void
7326 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7328         size_t i;
7330         for (i = 0; i < refs_size; i++)
7331                 if (!visitor(data, refs[i]))
7332                         break;
7335 static struct ref *
7336 get_ref_head()
7338         return refs_head;
7341 static struct ref_list *
7342 get_ref_list(const char *id)
7344         struct ref_list *list;
7345         size_t i;
7347         for (i = 0; i < ref_lists_size; i++)
7348                 if (!strcmp(id, ref_lists[i]->id))
7349                         return ref_lists[i];
7351         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7352                 return NULL;
7353         list = calloc(1, sizeof(*list));
7354         if (!list)
7355                 return NULL;
7357         for (i = 0; i < refs_size; i++) {
7358                 if (!strcmp(id, refs[i]->id) &&
7359                     realloc_refs_list(&list->refs, list->size, 1))
7360                         list->refs[list->size++] = refs[i];
7361         }
7363         if (!list->refs) {
7364                 free(list);
7365                 return NULL;
7366         }
7368         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7369         ref_lists[ref_lists_size++] = list;
7370         return list;
7373 static int
7374 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7376         struct ref *ref = NULL;
7377         bool tag = FALSE;
7378         bool ltag = FALSE;
7379         bool remote = FALSE;
7380         bool tracked = FALSE;
7381         bool head = FALSE;
7382         int from = 0, to = refs_size - 1;
7384         if (!prefixcmp(name, "refs/tags/")) {
7385                 if (!suffixcmp(name, namelen, "^{}")) {
7386                         namelen -= 3;
7387                         name[namelen] = 0;
7388                 } else {
7389                         ltag = TRUE;
7390                 }
7392                 tag = TRUE;
7393                 namelen -= STRING_SIZE("refs/tags/");
7394                 name    += STRING_SIZE("refs/tags/");
7396         } else if (!prefixcmp(name, "refs/remotes/")) {
7397                 remote = TRUE;
7398                 namelen -= STRING_SIZE("refs/remotes/");
7399                 name    += STRING_SIZE("refs/remotes/");
7400                 tracked  = !strcmp(opt_remote, name);
7402         } else if (!prefixcmp(name, "refs/heads/")) {
7403                 namelen -= STRING_SIZE("refs/heads/");
7404                 name    += STRING_SIZE("refs/heads/");
7405                 if (!strncmp(opt_head, name, namelen))
7406                         return OK;
7408         } else if (!strcmp(name, "HEAD")) {
7409                 head     = TRUE;
7410                 if (*opt_head) {
7411                         namelen  = strlen(opt_head);
7412                         name     = opt_head;
7413                 }
7414         }
7416         /* If we are reloading or it's an annotated tag, replace the
7417          * previous SHA1 with the resolved commit id; relies on the fact
7418          * git-ls-remote lists the commit id of an annotated tag right
7419          * before the commit id it points to. */
7420         while (from <= to) {
7421                 size_t pos = (to + from) / 2;
7422                 int cmp = strcmp(name, refs[pos]->name);
7424                 if (!cmp) {
7425                         ref = refs[pos];
7426                         break;
7427                 }
7429                 if (cmp < 0)
7430                         to = pos - 1;
7431                 else
7432                         from = pos + 1;
7433         }
7435         if (!ref) {
7436                 if (!realloc_refs(&refs, refs_size, 1))
7437                         return ERR;
7438                 ref = calloc(1, sizeof(*ref) + namelen);
7439                 if (!ref)
7440                         return ERR;
7441                 memmove(refs + from + 1, refs + from,
7442                         (refs_size - from) * sizeof(*refs));
7443                 refs[from] = ref;
7444                 strncpy(ref->name, name, namelen);
7445                 refs_size++;
7446         }
7448         ref->head = head;
7449         ref->tag = tag;
7450         ref->ltag = ltag;
7451         ref->remote = remote;
7452         ref->tracked = tracked;
7453         string_copy_rev(ref->id, id);
7455         if (head)
7456                 refs_head = ref;
7457         return OK;
7460 static int
7461 load_refs(void)
7463         const char *head_argv[] = {
7464                 "git", "symbolic-ref", "HEAD", NULL
7465         };
7466         static const char *ls_remote_argv[SIZEOF_ARG] = {
7467                 "git", "ls-remote", opt_git_dir, NULL
7468         };
7469         static bool init = FALSE;
7470         size_t i;
7472         if (!init) {
7473                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7474                         die("TIG_LS_REMOTE contains too many arguments");
7475                 init = TRUE;
7476         }
7478         if (!*opt_git_dir)
7479                 return OK;
7481         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7482             !prefixcmp(opt_head, "refs/heads/")) {
7483                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7485                 memmove(opt_head, offset, strlen(offset) + 1);
7486         }
7488         refs_head = NULL;
7489         for (i = 0; i < refs_size; i++)
7490                 refs[i]->id[0] = 0;
7492         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7493                 return ERR;
7495         /* Update the ref lists to reflect changes. */
7496         for (i = 0; i < ref_lists_size; i++) {
7497                 struct ref_list *list = ref_lists[i];
7498                 size_t old, new;
7500                 for (old = new = 0; old < list->size; old++)
7501                         if (!strcmp(list->id, list->refs[old]->id))
7502                                 list->refs[new++] = list->refs[old];
7503                 list->size = new;
7504         }
7506         return OK;
7509 static void
7510 set_remote_branch(const char *name, const char *value, size_t valuelen)
7512         if (!strcmp(name, ".remote")) {
7513                 string_ncopy(opt_remote, value, valuelen);
7515         } else if (*opt_remote && !strcmp(name, ".merge")) {
7516                 size_t from = strlen(opt_remote);
7518                 if (!prefixcmp(value, "refs/heads/"))
7519                         value += STRING_SIZE("refs/heads/");
7521                 if (!string_format_from(opt_remote, &from, "/%s", value))
7522                         opt_remote[0] = 0;
7523         }
7526 static void
7527 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7529         const char *argv[SIZEOF_ARG] = { name, "=" };
7530         int argc = 1 + (cmd == option_set_command);
7531         int error = ERR;
7533         if (!argv_from_string(argv, &argc, value))
7534                 config_msg = "Too many option arguments";
7535         else
7536                 error = cmd(argc, argv);
7538         if (error == ERR)
7539                 warn("Option 'tig.%s': %s", name, config_msg);
7542 static bool
7543 set_environment_variable(const char *name, const char *value)
7545         size_t len = strlen(name) + 1 + strlen(value) + 1;
7546         char *env = malloc(len);
7548         if (env &&
7549             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7550             putenv(env) == 0)
7551                 return TRUE;
7552         free(env);
7553         return FALSE;
7556 static void
7557 set_work_tree(const char *value)
7559         char cwd[SIZEOF_STR];
7561         if (!getcwd(cwd, sizeof(cwd)))
7562                 die("Failed to get cwd path: %s", strerror(errno));
7563         if (chdir(opt_git_dir) < 0)
7564                 die("Failed to chdir(%s): %s", strerror(errno));
7565         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7566                 die("Failed to get git path: %s", strerror(errno));
7567         if (chdir(cwd) < 0)
7568                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7569         if (chdir(value) < 0)
7570                 die("Failed to chdir(%s): %s", value, strerror(errno));
7571         if (!getcwd(cwd, sizeof(cwd)))
7572                 die("Failed to get cwd path: %s", strerror(errno));
7573         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7574                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7575         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7576                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7577         opt_is_inside_work_tree = TRUE;
7580 static int
7581 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7583         if (!strcmp(name, "i18n.commitencoding"))
7584                 string_ncopy(opt_encoding, value, valuelen);
7586         else if (!strcmp(name, "core.editor"))
7587                 string_ncopy(opt_editor, value, valuelen);
7589         else if (!strcmp(name, "core.worktree"))
7590                 set_work_tree(value);
7592         else if (!prefixcmp(name, "tig.color."))
7593                 set_repo_config_option(name + 10, value, option_color_command);
7595         else if (!prefixcmp(name, "tig.bind."))
7596                 set_repo_config_option(name + 9, value, option_bind_command);
7598         else if (!prefixcmp(name, "tig."))
7599                 set_repo_config_option(name + 4, value, option_set_command);
7601         else if (*opt_head && !prefixcmp(name, "branch.") &&
7602                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7603                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7605         return OK;
7608 static int
7609 load_git_config(void)
7611         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7613         return io_run_load(config_list_argv, "=", read_repo_config_option);
7616 static int
7617 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7619         if (!opt_git_dir[0]) {
7620                 string_ncopy(opt_git_dir, name, namelen);
7622         } else if (opt_is_inside_work_tree == -1) {
7623                 /* This can be 3 different values depending on the
7624                  * version of git being used. If git-rev-parse does not
7625                  * understand --is-inside-work-tree it will simply echo
7626                  * the option else either "true" or "false" is printed.
7627                  * Default to true for the unknown case. */
7628                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7630         } else if (*name == '.') {
7631                 string_ncopy(opt_cdup, name, namelen);
7633         } else {
7634                 string_ncopy(opt_prefix, name, namelen);
7635         }
7637         return OK;
7640 static int
7641 load_repo_info(void)
7643         const char *rev_parse_argv[] = {
7644                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7645                         "--show-cdup", "--show-prefix", NULL
7646         };
7648         return io_run_load(rev_parse_argv, "=", read_repo_info);
7652 /*
7653  * Main
7654  */
7656 static const char usage[] =
7657 "tig " TIG_VERSION " (" __DATE__ ")\n"
7658 "\n"
7659 "Usage: tig        [options] [revs] [--] [paths]\n"
7660 "   or: tig show   [options] [revs] [--] [paths]\n"
7661 "   or: tig blame  [rev] path\n"
7662 "   or: tig status\n"
7663 "   or: tig <      [git command output]\n"
7664 "\n"
7665 "Options:\n"
7666 "  -v, --version   Show version and exit\n"
7667 "  -h, --help      Show help message and exit";
7669 static void __NORETURN
7670 quit(int sig)
7672         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7673         if (cursed)
7674                 endwin();
7675         exit(0);
7678 static void __NORETURN
7679 die(const char *err, ...)
7681         va_list args;
7683         endwin();
7685         va_start(args, err);
7686         fputs("tig: ", stderr);
7687         vfprintf(stderr, err, args);
7688         fputs("\n", stderr);
7689         va_end(args);
7691         exit(1);
7694 static void
7695 warn(const char *msg, ...)
7697         va_list args;
7699         va_start(args, msg);
7700         fputs("tig warning: ", stderr);
7701         vfprintf(stderr, msg, args);
7702         fputs("\n", stderr);
7703         va_end(args);
7706 static enum request
7707 parse_options(int argc, const char *argv[])
7709         enum request request = REQ_VIEW_MAIN;
7710         const char *subcommand;
7711         bool seen_dashdash = FALSE;
7712         /* XXX: This is vulnerable to the user overriding options
7713          * required for the main view parser. */
7714         const char *custom_argv[SIZEOF_ARG] = {
7715                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7716                         "--topo-order", NULL
7717         };
7718         int i, j = 6;
7720         if (!isatty(STDIN_FILENO)) {
7721                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7722                 return REQ_VIEW_PAGER;
7723         }
7725         if (argc <= 1)
7726                 return REQ_NONE;
7728         subcommand = argv[1];
7729         if (!strcmp(subcommand, "status")) {
7730                 if (argc > 2)
7731                         warn("ignoring arguments after `%s'", subcommand);
7732                 return REQ_VIEW_STATUS;
7734         } else if (!strcmp(subcommand, "blame")) {
7735                 if (argc <= 2 || argc > 4)
7736                         die("invalid number of options to blame\n\n%s", usage);
7738                 i = 2;
7739                 if (argc == 4) {
7740                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7741                         i++;
7742                 }
7744                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7745                 return REQ_VIEW_BLAME;
7747         } else if (!strcmp(subcommand, "show")) {
7748                 request = REQ_VIEW_DIFF;
7750         } else {
7751                 subcommand = NULL;
7752         }
7754         if (subcommand) {
7755                 custom_argv[1] = subcommand;
7756                 j = 2;
7757         }
7759         for (i = 1 + !!subcommand; i < argc; i++) {
7760                 const char *opt = argv[i];
7762                 if (seen_dashdash || !strcmp(opt, "--")) {
7763                         seen_dashdash = TRUE;
7765                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7766                         printf("tig version %s\n", TIG_VERSION);
7767                         quit(0);
7769                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7770                         printf("%s\n", usage);
7771                         quit(0);
7772                 }
7774                 custom_argv[j++] = opt;
7775                 if (j >= ARRAY_SIZE(custom_argv))
7776                         die("command too long");
7777         }
7779         if (!prepare_update(VIEW(request), custom_argv, NULL))
7780                 die("Failed to format arguments");
7782         return request;
7785 int
7786 main(int argc, const char *argv[])
7788         const char *codeset = "UTF-8";
7789         enum request request = parse_options(argc, argv);
7790         struct view *view;
7791         size_t i;
7793         signal(SIGINT, quit);
7794         signal(SIGPIPE, SIG_IGN);
7796         if (setlocale(LC_ALL, "")) {
7797                 codeset = nl_langinfo(CODESET);
7798         }
7800         if (load_repo_info() == ERR)
7801                 die("Failed to load repo info.");
7803         if (load_options() == ERR)
7804                 die("Failed to load user config.");
7806         if (load_git_config() == ERR)
7807                 die("Failed to load repo config.");
7809         /* Require a git repository unless when running in pager mode. */
7810         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7811                 die("Not a git repository");
7813         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7814                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7815                 if (opt_iconv_in == ICONV_NONE)
7816                         die("Failed to initialize character set conversion");
7817         }
7819         if (codeset && strcmp(codeset, "UTF-8")) {
7820                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7821                 if (opt_iconv_out == ICONV_NONE)
7822                         die("Failed to initialize character set conversion");
7823         }
7825         if (load_refs() == ERR)
7826                 die("Failed to load refs.");
7828         foreach_view (view, i)
7829                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7830                         die("Too many arguments in the `%s` environment variable",
7831                             view->cmd_env);
7833         init_display();
7835         if (request != REQ_NONE)
7836                 open_view(NULL, request, OPEN_PREPARED);
7837         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7839         while (view_driver(display[current_view], request)) {
7840                 int key = get_input(0);
7842                 view = display[current_view];
7843                 request = get_keybinding(view->keymap, key);
7845                 /* Some low-level request handling. This keeps access to
7846                  * status_win restricted. */
7847                 switch (request) {
7848                 case REQ_NONE:
7849                         report("Unknown key, press %s for help",
7850                                get_key(view->keymap, REQ_VIEW_HELP));
7851                         break;
7852                 case REQ_PROMPT:
7853                 {
7854                         char *cmd = read_prompt(":");
7856                         if (cmd && isdigit(*cmd)) {
7857                                 int lineno = view->lineno + 1;
7859                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7860                                         select_view_line(view, lineno - 1);
7861                                         report("");
7862                                 } else {
7863                                         report("Unable to parse '%s' as a line number", cmd);
7864                                 }
7866                         } else if (cmd) {
7867                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7868                                 const char *argv[SIZEOF_ARG] = { "git" };
7869                                 int argc = 1;
7871                                 /* When running random commands, initially show the
7872                                  * command in the title. However, it maybe later be
7873                                  * overwritten if a commit line is selected. */
7874                                 string_ncopy(next->ref, cmd, strlen(cmd));
7876                                 if (!argv_from_string(argv, &argc, cmd)) {
7877                                         report("Too many arguments");
7878                                 } else if (!prepare_update(next, argv, NULL)) {
7879                                         report("Failed to format command");
7880                                 } else {
7881                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7882                                 }
7883                         }
7885                         request = REQ_NONE;
7886                         break;
7887                 }
7888                 case REQ_SEARCH:
7889                 case REQ_SEARCH_BACK:
7890                 {
7891                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7892                         char *search = read_prompt(prompt);
7894                         if (search)
7895                                 string_ncopy(opt_search, search, strlen(search));
7896                         else if (*opt_search)
7897                                 request = request == REQ_SEARCH ?
7898                                         REQ_FIND_NEXT :
7899                                         REQ_FIND_PREV;
7900                         else
7901                                 request = REQ_NONE;
7902                         break;
7903                 }
7904                 default:
7905                         break;
7906                 }
7907         }
7909         quit(0);
7911         return 0;