Code

argv: remove now unused FORMAT_DASH
[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_(RELATIVE), \
523         DATE_(SHORT)
525 enum date {
526 #define DATE_(name) DATE_##name
527         DATE_INFO
528 #undef  DATE_
529 };
531 static const struct enum_map date_map[] = {
532 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
533         DATE_INFO
534 #undef  DATE_
535 };
537 struct time {
538         time_t sec;
539         int tz;
540 };
542 static inline int timecmp(const struct time *t1, const struct time *t2)
544         return t1->sec - t2->sec;
547 static const char *
548 mkdate(const struct time *time, enum date date)
550         static char buf[DATE_COLS + 1];
551         static const struct enum_map reldate[] = {
552                 { "second", 1,                  60 * 2 },
553                 { "minute", 60,                 60 * 60 * 2 },
554                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
555                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
556                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
557                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
558         };
559         struct tm tm;
561         if (!date || !time || !time->sec)
562                 return "";
564         if (date == DATE_RELATIVE) {
565                 struct timeval now;
566                 time_t date = time->sec + time->tz;
567                 time_t seconds;
568                 int i;
570                 gettimeofday(&now, NULL);
571                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
572                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
573                         if (seconds >= reldate[i].value)
574                                 continue;
576                         seconds /= reldate[i].namelen;
577                         if (!string_format(buf, "%ld %s%s %s",
578                                            seconds, reldate[i].name,
579                                            seconds > 1 ? "s" : "",
580                                            now.tv_sec >= date ? "ago" : "ahead"))
581                                 break;
582                         return buf;
583                 }
584         }
586         gmtime_r(&time->sec, &tm);
587         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
591 #define AUTHOR_VALUES \
592         AUTHOR_(NO), \
593         AUTHOR_(FULL), \
594         AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598         AUTHOR_VALUES,
599 #undef  AUTHOR_
600         AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605         AUTHOR_VALUES
606 #undef  AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
612         static char initials[AUTHOR_COLS * 6 + 1];
613         size_t pos = 0;
614         const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618         memset(initials, 0, sizeof(initials));
619         while (author < end) {
620                 unsigned char bytes;
621                 size_t i;
623                 while (is_initial_sep(*author))
624                         author++;
626                 bytes = utf8_char_length(author, end);
627                 if (bytes < sizeof(initials) - 1 - pos) {
628                         while (bytes--) {
629                                 initials[pos++] = *author++;
630                         }
631                 }
633                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634                         if (i < sizeof(initials) - 1)
635                                 initials[i++] = *author;
636                 }
638                 initials[i++] = 0;
639         }
641         return initials;
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
648         int valuelen;
650         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651                 bool advance = cmd[valuelen] != 0;
653                 cmd[valuelen] = 0;
654                 argv[(*argc)++] = chomp_string(cmd);
655                 cmd = chomp_string(cmd + valuelen + advance);
656         }
658         if (*argc < SIZEOF_ARG)
659                 argv[*argc] = NULL;
660         return *argc < SIZEOF_ARG;
663 static bool
664 argv_from_env(const char **argv, const char *name)
666         char *env = argv ? getenv(name) : NULL;
667         int argc = 0;
669         if (env && *env)
670                 env = strdup(env);
671         return !env || argv_from_string(argv, &argc, env);
675 /*
676  * Executing external commands.
677  */
679 enum io_type {
680         IO_FD,                  /* File descriptor based IO. */
681         IO_BG,                  /* Execute command in the background. */
682         IO_FG,                  /* Execute command with same std{in,out,err}. */
683         IO_RD,                  /* Read only fork+exec IO. */
684         IO_WR,                  /* Write only fork+exec IO. */
685         IO_AP,                  /* Append fork+exec output to file. */
686 };
688 struct io {
689         enum io_type type;      /* The requested type of pipe. */
690         const char *dir;        /* Directory from which to execute. */
691         pid_t pid;              /* PID of spawned process. */
692         int pipe;               /* Pipe end for reading or writing. */
693         int error;              /* Error status. */
694         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
695         char *buf;              /* Read buffer. */
696         size_t bufalloc;        /* Allocated buffer size. */
697         size_t bufsize;         /* Buffer content size. */
698         char *bufpos;           /* Current buffer position. */
699         unsigned int eof:1;     /* Has end of file been reached. */
700 };
702 static void
703 io_reset(struct io *io)
705         io->pipe = -1;
706         io->pid = 0;
707         io->buf = io->bufpos = NULL;
708         io->bufalloc = io->bufsize = 0;
709         io->error = 0;
710         io->eof = 0;
713 static void
714 io_init(struct io *io, const char *dir, enum io_type type)
716         io_reset(io);
717         io->type = type;
718         io->dir = dir;
721 static bool
722 io_format(struct io *io, const char *dir, enum io_type type,
723           const char *argv[], enum format_flags flags)
725         io_init(io, dir, type);
726         return format_argv(io->argv, argv, flags);
729 static bool
730 io_open(struct io *io, const char *fmt, ...)
732         char name[SIZEOF_STR] = "";
733         bool fits;
734         va_list args;
736         io_init(io, NULL, IO_FD);
738         va_start(args, fmt);
739         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
740         va_end(args);
742         if (!fits) {
743                 io->error = ENAMETOOLONG;
744                 return FALSE;
745         }
746         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
747         if (io->pipe == -1)
748                 io->error = errno;
749         return io->pipe != -1;
752 static bool
753 io_kill(struct io *io)
755         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
758 static bool
759 io_done(struct io *io)
761         pid_t pid = io->pid;
763         if (io->pipe != -1)
764                 close(io->pipe);
765         free(io->buf);
766         io_reset(io);
768         while (pid > 0) {
769                 int status;
770                 pid_t waiting = waitpid(pid, &status, 0);
772                 if (waiting < 0) {
773                         if (errno == EINTR)
774                                 continue;
775                         io->error = errno;
776                         return FALSE;
777                 }
779                 return waiting == pid &&
780                        !WIFSIGNALED(status) &&
781                        WIFEXITED(status) &&
782                        !WEXITSTATUS(status);
783         }
785         return TRUE;
788 static bool
789 io_start(struct io *io)
791         int pipefds[2] = { -1, -1 };
793         if (io->type == IO_FD)
794                 return TRUE;
796         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
797                 io->error = errno;
798                 return FALSE;
799         } else if (io->type == IO_AP) {
800                 pipefds[1] = io->pipe;
801         }
803         if ((io->pid = fork())) {
804                 if (io->pid == -1)
805                         io->error = errno;
806                 if (pipefds[!(io->type == IO_WR)] != -1)
807                         close(pipefds[!(io->type == IO_WR)]);
808                 if (io->pid != -1) {
809                         io->pipe = pipefds[!!(io->type == IO_WR)];
810                         return TRUE;
811                 }
813         } else {
814                 if (io->type != IO_FG) {
815                         int devnull = open("/dev/null", O_RDWR);
816                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
817                         int writefd = (io->type == IO_RD || io->type == IO_AP)
818                                                         ? pipefds[1] : devnull;
820                         dup2(readfd,  STDIN_FILENO);
821                         dup2(writefd, STDOUT_FILENO);
822                         dup2(devnull, STDERR_FILENO);
824                         close(devnull);
825                         if (pipefds[0] != -1)
826                                 close(pipefds[0]);
827                         if (pipefds[1] != -1)
828                                 close(pipefds[1]);
829                 }
831                 if (io->dir && *io->dir && chdir(io->dir) == -1)
832                         exit(errno);
834                 execvp(io->argv[0], (char *const*) io->argv);
835                 exit(errno);
836         }
838         if (pipefds[!!(io->type == IO_WR)] != -1)
839                 close(pipefds[!!(io->type == IO_WR)]);
840         return FALSE;
843 static bool
844 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
846         io_init(io, dir, type);
847         if (!format_argv(io->argv, argv, FORMAT_NONE))
848                 return FALSE;
849         return io_start(io);
852 static int
853 io_complete(struct io *io)
855         return io_start(io) && io_done(io);
858 static int
859 io_run_bg(const char **argv)
861         struct io io = {};
863         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
864                 return FALSE;
865         return io_complete(&io);
868 static bool
869 io_run_fg(const char **argv, const char *dir)
871         struct io io = {};
873         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
874                 return FALSE;
875         return io_complete(&io);
878 static bool
879 io_run_append(const char **argv, enum format_flags flags, int fd)
881         struct io io = {};
883         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
884                 close(fd);
885                 return FALSE;
886         }
888         io.pipe = fd;
889         return io_complete(&io);
892 static bool
893 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
895         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
898 static bool
899 io_eof(struct io *io)
901         return io->eof;
904 static int
905 io_error(struct io *io)
907         return io->error;
910 static char *
911 io_strerror(struct io *io)
913         return strerror(io->error);
916 static bool
917 io_can_read(struct io *io)
919         struct timeval tv = { 0, 500 };
920         fd_set fds;
922         FD_ZERO(&fds);
923         FD_SET(io->pipe, &fds);
925         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
928 static ssize_t
929 io_read(struct io *io, void *buf, size_t bufsize)
931         do {
932                 ssize_t readsize = read(io->pipe, buf, bufsize);
934                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
935                         continue;
936                 else if (readsize == -1)
937                         io->error = errno;
938                 else if (readsize == 0)
939                         io->eof = 1;
940                 return readsize;
941         } while (1);
944 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
946 static char *
947 io_get(struct io *io, int c, bool can_read)
949         char *eol;
950         ssize_t readsize;
952         while (TRUE) {
953                 if (io->bufsize > 0) {
954                         eol = memchr(io->bufpos, c, io->bufsize);
955                         if (eol) {
956                                 char *line = io->bufpos;
958                                 *eol = 0;
959                                 io->bufpos = eol + 1;
960                                 io->bufsize -= io->bufpos - line;
961                                 return line;
962                         }
963                 }
965                 if (io_eof(io)) {
966                         if (io->bufsize) {
967                                 io->bufpos[io->bufsize] = 0;
968                                 io->bufsize = 0;
969                                 return io->bufpos;
970                         }
971                         return NULL;
972                 }
974                 if (!can_read)
975                         return NULL;
977                 if (io->bufsize > 0 && io->bufpos > io->buf)
978                         memmove(io->buf, io->bufpos, io->bufsize);
980                 if (io->bufalloc == io->bufsize) {
981                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
982                                 return NULL;
983                         io->bufalloc += BUFSIZ;
984                 }
986                 io->bufpos = io->buf;
987                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
988                 if (io_error(io))
989                         return NULL;
990                 io->bufsize += readsize;
991         }
994 static bool
995 io_write(struct io *io, const void *buf, size_t bufsize)
997         size_t written = 0;
999         while (!io_error(io) && written < bufsize) {
1000                 ssize_t size;
1002                 size = write(io->pipe, buf + written, bufsize - written);
1003                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1004                         continue;
1005                 else if (size == -1)
1006                         io->error = errno;
1007                 else
1008                         written += size;
1009         }
1011         return written == bufsize;
1014 static bool
1015 io_read_buf(struct io *io, char buf[], size_t bufsize)
1017         char *result = io_get(io, '\n', TRUE);
1019         if (result) {
1020                 result = chomp_string(result);
1021                 string_ncopy_do(buf, bufsize, result, strlen(result));
1022         }
1024         return io_done(io) && result;
1027 static bool
1028 io_run_buf(const char **argv, char buf[], size_t bufsize)
1030         struct io io = {};
1032         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1033             && io_read_buf(&io, buf, bufsize);
1036 static int
1037 io_load(struct io *io, const char *separators,
1038         int (*read_property)(char *, size_t, char *, size_t))
1040         char *name;
1041         int state = OK;
1043         if (!io_start(io))
1044                 return ERR;
1046         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1047                 char *value;
1048                 size_t namelen;
1049                 size_t valuelen;
1051                 name = chomp_string(name);
1052                 namelen = strcspn(name, separators);
1054                 if (name[namelen]) {
1055                         name[namelen] = 0;
1056                         value = chomp_string(name + namelen + 1);
1057                         valuelen = strlen(value);
1059                 } else {
1060                         value = "";
1061                         valuelen = 0;
1062                 }
1064                 state = read_property(name, namelen, value, valuelen);
1065         }
1067         if (state != ERR && io_error(io))
1068                 state = ERR;
1069         io_done(io);
1071         return state;
1074 static int
1075 io_run_load(const char **argv, const char *separators,
1076             int (*read_property)(char *, size_t, char *, size_t))
1078         struct io io = {};
1080         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1081                 ? io_load(&io, separators, read_property) : ERR;
1085 /*
1086  * User requests
1087  */
1089 #define REQ_INFO \
1090         /* XXX: Keep the view request first and in sync with views[]. */ \
1091         REQ_GROUP("View switching") \
1092         REQ_(VIEW_MAIN,         "Show main view"), \
1093         REQ_(VIEW_DIFF,         "Show diff view"), \
1094         REQ_(VIEW_LOG,          "Show log view"), \
1095         REQ_(VIEW_TREE,         "Show tree view"), \
1096         REQ_(VIEW_BLOB,         "Show blob view"), \
1097         REQ_(VIEW_BLAME,        "Show blame view"), \
1098         REQ_(VIEW_BRANCH,       "Show branch view"), \
1099         REQ_(VIEW_HELP,         "Show help page"), \
1100         REQ_(VIEW_PAGER,        "Show pager view"), \
1101         REQ_(VIEW_STATUS,       "Show status view"), \
1102         REQ_(VIEW_STAGE,        "Show stage view"), \
1103         \
1104         REQ_GROUP("View manipulation") \
1105         REQ_(ENTER,             "Enter current line and scroll"), \
1106         REQ_(NEXT,              "Move to next"), \
1107         REQ_(PREVIOUS,          "Move to previous"), \
1108         REQ_(PARENT,            "Move to parent"), \
1109         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1110         REQ_(REFRESH,           "Reload and refresh"), \
1111         REQ_(MAXIMIZE,          "Maximize the current view"), \
1112         REQ_(VIEW_CLOSE,        "Close the current view"), \
1113         REQ_(QUIT,              "Close all views and quit"), \
1114         \
1115         REQ_GROUP("View specific requests") \
1116         REQ_(STATUS_UPDATE,     "Update file status"), \
1117         REQ_(STATUS_REVERT,     "Revert file changes"), \
1118         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1119         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1120         \
1121         REQ_GROUP("Cursor navigation") \
1122         REQ_(MOVE_UP,           "Move cursor one line up"), \
1123         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1124         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1125         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1126         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1127         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1128         \
1129         REQ_GROUP("Scrolling") \
1130         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1131         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1132         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1133         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1134         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1135         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1136         \
1137         REQ_GROUP("Searching") \
1138         REQ_(SEARCH,            "Search the view"), \
1139         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1140         REQ_(FIND_NEXT,         "Find next search match"), \
1141         REQ_(FIND_PREV,         "Find previous search match"), \
1142         \
1143         REQ_GROUP("Option manipulation") \
1144         REQ_(OPTIONS,           "Open option menu"), \
1145         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1146         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1147         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1149         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1150         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1151         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1153         \
1154         REQ_GROUP("Misc") \
1155         REQ_(PROMPT,            "Bring up the prompt"), \
1156         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1157         REQ_(SHOW_VERSION,      "Show version information"), \
1158         REQ_(STOP_LOADING,      "Stop all loading views"), \
1159         REQ_(EDIT,              "Open in editor"), \
1160         REQ_(NONE,              "Do nothing")
1163 /* User action requests. */
1164 enum request {
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168         /* Offset all requests to avoid conflicts with ncurses getch values. */
1169         REQ_OFFSET = KEY_MAX + 1,
1170         REQ_INFO
1172 #undef  REQ_GROUP
1173 #undef  REQ_
1174 };
1176 struct request_info {
1177         enum request request;
1178         const char *name;
1179         int namelen;
1180         const char *help;
1181 };
1183 static const struct request_info req_info[] = {
1184 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1185 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1186         REQ_INFO
1187 #undef  REQ_GROUP
1188 #undef  REQ_
1189 };
1191 static enum request
1192 get_request(const char *name)
1194         int namelen = strlen(name);
1195         int i;
1197         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1198                 if (enum_equals(req_info[i], name, namelen))
1199                         return req_info[i].request;
1201         return REQ_NONE;
1205 /*
1206  * Options
1207  */
1209 /* Option and state variables. */
1210 static enum date opt_date               = DATE_DEFAULT;
1211 static enum author opt_author           = AUTHOR_DEFAULT;
1212 static bool opt_line_number             = FALSE;
1213 static bool opt_line_graphics           = TRUE;
1214 static bool opt_rev_graph               = FALSE;
1215 static bool opt_show_refs               = TRUE;
1216 static int opt_num_interval             = 5;
1217 static double opt_hscroll               = 0.50;
1218 static double opt_scale_split_view      = 2.0 / 3.0;
1219 static int opt_tab_size                 = 8;
1220 static int opt_author_cols              = AUTHOR_COLS;
1221 static char opt_path[SIZEOF_STR]        = "";
1222 static char opt_file[SIZEOF_STR]        = "";
1223 static char opt_ref[SIZEOF_REF]         = "";
1224 static char opt_head[SIZEOF_REF]        = "";
1225 static char opt_remote[SIZEOF_REF]      = "";
1226 static char opt_encoding[20]            = "UTF-8";
1227 static iconv_t opt_iconv_in             = ICONV_NONE;
1228 static iconv_t opt_iconv_out            = ICONV_NONE;
1229 static char opt_search[SIZEOF_STR]      = "";
1230 static char opt_cdup[SIZEOF_STR]        = "";
1231 static char opt_prefix[SIZEOF_STR]      = "";
1232 static char opt_git_dir[SIZEOF_STR]     = "";
1233 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1234 static char opt_editor[SIZEOF_STR]      = "";
1235 static FILE *opt_tty                    = NULL;
1237 #define is_initial_commit()     (!get_ref_head())
1238 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1241 /*
1242  * Line-oriented content detection.
1243  */
1245 #define LINE_INFO \
1246 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1247 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1248 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1249 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1250 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1251 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1252 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1253 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1254 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1255 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1260 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1261 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1262 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1263 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1264 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1265 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1266 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1267 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1268 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1269 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1270 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1271 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1272 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1273 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1275 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1276 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1277 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1278 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1279 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1280 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1281 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1282 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1283 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1284 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1285 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1286 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1287 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1288 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1289 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1290 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1291 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1292 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1293 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1294 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1295 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1296 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1297 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1298 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1299 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1301 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1302 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1304 enum line_type {
1305 #define LINE(type, line, fg, bg, attr) \
1306         LINE_##type
1307         LINE_INFO,
1308         LINE_NONE
1309 #undef  LINE
1310 };
1312 struct line_info {
1313         const char *name;       /* Option name. */
1314         int namelen;            /* Size of option name. */
1315         const char *line;       /* The start of line to match. */
1316         int linelen;            /* Size of string to match. */
1317         int fg, bg, attr;       /* Color and text attributes for the lines. */
1318 };
1320 static struct line_info line_info[] = {
1321 #define LINE(type, line, fg, bg, attr) \
1322         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1323         LINE_INFO
1324 #undef  LINE
1325 };
1327 static enum line_type
1328 get_line_type(const char *line)
1330         int linelen = strlen(line);
1331         enum line_type type;
1333         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1334                 /* Case insensitive search matches Signed-off-by lines better. */
1335                 if (linelen >= line_info[type].linelen &&
1336                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1337                         return type;
1339         return LINE_DEFAULT;
1342 static inline int
1343 get_line_attr(enum line_type type)
1345         assert(type < ARRAY_SIZE(line_info));
1346         return COLOR_PAIR(type) | line_info[type].attr;
1349 static struct line_info *
1350 get_line_info(const char *name)
1352         size_t namelen = strlen(name);
1353         enum line_type type;
1355         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1356                 if (enum_equals(line_info[type], name, namelen))
1357                         return &line_info[type];
1359         return NULL;
1362 static void
1363 init_colors(void)
1365         int default_bg = line_info[LINE_DEFAULT].bg;
1366         int default_fg = line_info[LINE_DEFAULT].fg;
1367         enum line_type type;
1369         start_color();
1371         if (assume_default_colors(default_fg, default_bg) == ERR) {
1372                 default_bg = COLOR_BLACK;
1373                 default_fg = COLOR_WHITE;
1374         }
1376         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1377                 struct line_info *info = &line_info[type];
1378                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1379                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1381                 init_pair(type, fg, bg);
1382         }
1385 struct line {
1386         enum line_type type;
1388         /* State flags */
1389         unsigned int selected:1;
1390         unsigned int dirty:1;
1391         unsigned int cleareol:1;
1392         unsigned int other:16;
1394         void *data;             /* User data */
1395 };
1398 /*
1399  * Keys
1400  */
1402 struct keybinding {
1403         int alias;
1404         enum request request;
1405 };
1407 static const struct keybinding default_keybindings[] = {
1408         /* View switching */
1409         { 'm',          REQ_VIEW_MAIN },
1410         { 'd',          REQ_VIEW_DIFF },
1411         { 'l',          REQ_VIEW_LOG },
1412         { 't',          REQ_VIEW_TREE },
1413         { 'f',          REQ_VIEW_BLOB },
1414         { 'B',          REQ_VIEW_BLAME },
1415         { 'H',          REQ_VIEW_BRANCH },
1416         { 'p',          REQ_VIEW_PAGER },
1417         { 'h',          REQ_VIEW_HELP },
1418         { 'S',          REQ_VIEW_STATUS },
1419         { 'c',          REQ_VIEW_STAGE },
1421         /* View manipulation */
1422         { 'q',          REQ_VIEW_CLOSE },
1423         { KEY_TAB,      REQ_VIEW_NEXT },
1424         { KEY_RETURN,   REQ_ENTER },
1425         { KEY_UP,       REQ_PREVIOUS },
1426         { KEY_DOWN,     REQ_NEXT },
1427         { 'R',          REQ_REFRESH },
1428         { KEY_F(5),     REQ_REFRESH },
1429         { 'O',          REQ_MAXIMIZE },
1431         /* Cursor navigation */
1432         { 'k',          REQ_MOVE_UP },
1433         { 'j',          REQ_MOVE_DOWN },
1434         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1435         { KEY_END,      REQ_MOVE_LAST_LINE },
1436         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1437         { ' ',          REQ_MOVE_PAGE_DOWN },
1438         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1439         { 'b',          REQ_MOVE_PAGE_UP },
1440         { '-',          REQ_MOVE_PAGE_UP },
1442         /* Scrolling */
1443         { KEY_LEFT,     REQ_SCROLL_LEFT },
1444         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1445         { KEY_IC,       REQ_SCROLL_LINE_UP },
1446         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1447         { 'w',          REQ_SCROLL_PAGE_UP },
1448         { 's',          REQ_SCROLL_PAGE_DOWN },
1450         /* Searching */
1451         { '/',          REQ_SEARCH },
1452         { '?',          REQ_SEARCH_BACK },
1453         { 'n',          REQ_FIND_NEXT },
1454         { 'N',          REQ_FIND_PREV },
1456         /* Misc */
1457         { 'Q',          REQ_QUIT },
1458         { 'z',          REQ_STOP_LOADING },
1459         { 'v',          REQ_SHOW_VERSION },
1460         { 'r',          REQ_SCREEN_REDRAW },
1461         { 'o',          REQ_OPTIONS },
1462         { '.',          REQ_TOGGLE_LINENO },
1463         { 'D',          REQ_TOGGLE_DATE },
1464         { 'A',          REQ_TOGGLE_AUTHOR },
1465         { 'g',          REQ_TOGGLE_REV_GRAPH },
1466         { 'F',          REQ_TOGGLE_REFS },
1467         { 'I',          REQ_TOGGLE_SORT_ORDER },
1468         { 'i',          REQ_TOGGLE_SORT_FIELD },
1469         { ':',          REQ_PROMPT },
1470         { 'u',          REQ_STATUS_UPDATE },
1471         { '!',          REQ_STATUS_REVERT },
1472         { 'M',          REQ_STATUS_MERGE },
1473         { '@',          REQ_STAGE_NEXT },
1474         { ',',          REQ_PARENT },
1475         { 'e',          REQ_EDIT },
1476 };
1478 #define KEYMAP_INFO \
1479         KEYMAP_(GENERIC), \
1480         KEYMAP_(MAIN), \
1481         KEYMAP_(DIFF), \
1482         KEYMAP_(LOG), \
1483         KEYMAP_(TREE), \
1484         KEYMAP_(BLOB), \
1485         KEYMAP_(BLAME), \
1486         KEYMAP_(BRANCH), \
1487         KEYMAP_(PAGER), \
1488         KEYMAP_(HELP), \
1489         KEYMAP_(STATUS), \
1490         KEYMAP_(STAGE)
1492 enum keymap {
1493 #define KEYMAP_(name) KEYMAP_##name
1494         KEYMAP_INFO
1495 #undef  KEYMAP_
1496 };
1498 static const struct enum_map keymap_table[] = {
1499 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1500         KEYMAP_INFO
1501 #undef  KEYMAP_
1502 };
1504 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1506 struct keybinding_table {
1507         struct keybinding *data;
1508         size_t size;
1509 };
1511 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1513 static void
1514 add_keybinding(enum keymap keymap, enum request request, int key)
1516         struct keybinding_table *table = &keybindings[keymap];
1518         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1519         if (!table->data)
1520                 die("Failed to allocate keybinding");
1521         table->data[table->size].alias = key;
1522         table->data[table->size++].request = request;
1525 /* Looks for a key binding first in the given map, then in the generic map, and
1526  * lastly in the default keybindings. */
1527 static enum request
1528 get_keybinding(enum keymap keymap, int key)
1530         size_t i;
1532         for (i = 0; i < keybindings[keymap].size; i++)
1533                 if (keybindings[keymap].data[i].alias == key)
1534                         return keybindings[keymap].data[i].request;
1536         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1537                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1538                         return keybindings[KEYMAP_GENERIC].data[i].request;
1540         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1541                 if (default_keybindings[i].alias == key)
1542                         return default_keybindings[i].request;
1544         return (enum request) key;
1548 struct key {
1549         const char *name;
1550         int value;
1551 };
1553 static const struct key key_table[] = {
1554         { "Enter",      KEY_RETURN },
1555         { "Space",      ' ' },
1556         { "Backspace",  KEY_BACKSPACE },
1557         { "Tab",        KEY_TAB },
1558         { "Escape",     KEY_ESC },
1559         { "Left",       KEY_LEFT },
1560         { "Right",      KEY_RIGHT },
1561         { "Up",         KEY_UP },
1562         { "Down",       KEY_DOWN },
1563         { "Insert",     KEY_IC },
1564         { "Delete",     KEY_DC },
1565         { "Hash",       '#' },
1566         { "Home",       KEY_HOME },
1567         { "End",        KEY_END },
1568         { "PageUp",     KEY_PPAGE },
1569         { "PageDown",   KEY_NPAGE },
1570         { "F1",         KEY_F(1) },
1571         { "F2",         KEY_F(2) },
1572         { "F3",         KEY_F(3) },
1573         { "F4",         KEY_F(4) },
1574         { "F5",         KEY_F(5) },
1575         { "F6",         KEY_F(6) },
1576         { "F7",         KEY_F(7) },
1577         { "F8",         KEY_F(8) },
1578         { "F9",         KEY_F(9) },
1579         { "F10",        KEY_F(10) },
1580         { "F11",        KEY_F(11) },
1581         { "F12",        KEY_F(12) },
1582 };
1584 static int
1585 get_key_value(const char *name)
1587         int i;
1589         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1590                 if (!strcasecmp(key_table[i].name, name))
1591                         return key_table[i].value;
1593         if (strlen(name) == 1 && isprint(*name))
1594                 return (int) *name;
1596         return ERR;
1599 static const char *
1600 get_key_name(int key_value)
1602         static char key_char[] = "'X'";
1603         const char *seq = NULL;
1604         int key;
1606         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1607                 if (key_table[key].value == key_value)
1608                         seq = key_table[key].name;
1610         if (seq == NULL &&
1611             key_value < 127 &&
1612             isprint(key_value)) {
1613                 key_char[1] = (char) key_value;
1614                 seq = key_char;
1615         }
1617         return seq ? seq : "(no key)";
1620 static bool
1621 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1623         const char *sep = *pos > 0 ? ", " : "";
1624         const char *keyname = get_key_name(keybinding->alias);
1626         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1629 static bool
1630 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1631                            enum keymap keymap, bool all)
1633         int i;
1635         for (i = 0; i < keybindings[keymap].size; i++) {
1636                 if (keybindings[keymap].data[i].request == request) {
1637                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1638                                 return FALSE;
1639                         if (!all)
1640                                 break;
1641                 }
1642         }
1644         return TRUE;
1647 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1649 static const char *
1650 get_keys(enum keymap keymap, enum request request, bool all)
1652         static char buf[BUFSIZ];
1653         size_t pos = 0;
1654         int i;
1656         buf[pos] = 0;
1658         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1659                 return "Too many keybindings!";
1660         if (pos > 0 && !all)
1661                 return buf;
1663         if (keymap != KEYMAP_GENERIC) {
1664                 /* Only the generic keymap includes the default keybindings when
1665                  * listing all keys. */
1666                 if (all)
1667                         return buf;
1669                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1670                         return "Too many keybindings!";
1671                 if (pos)
1672                         return buf;
1673         }
1675         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1676                 if (default_keybindings[i].request == request) {
1677                         if (!append_key(buf, &pos, &default_keybindings[i]))
1678                                 return "Too many keybindings!";
1679                         if (!all)
1680                                 return buf;
1681                 }
1682         }
1684         return buf;
1687 struct run_request {
1688         enum keymap keymap;
1689         int key;
1690         const char *argv[SIZEOF_ARG];
1691 };
1693 static struct run_request *run_request;
1694 static size_t run_requests;
1696 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1698 static enum request
1699 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1701         struct run_request *req;
1703         if (argc >= ARRAY_SIZE(req->argv) - 1)
1704                 return REQ_NONE;
1706         if (!realloc_run_requests(&run_request, run_requests, 1))
1707                 return REQ_NONE;
1709         req = &run_request[run_requests];
1710         req->keymap = keymap;
1711         req->key = key;
1712         req->argv[0] = NULL;
1714         if (!format_argv(req->argv, argv, FORMAT_NONE))
1715                 return REQ_NONE;
1717         return REQ_NONE + ++run_requests;
1720 static struct run_request *
1721 get_run_request(enum request request)
1723         if (request <= REQ_NONE)
1724                 return NULL;
1725         return &run_request[request - REQ_NONE - 1];
1728 static void
1729 add_builtin_run_requests(void)
1731         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1732         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1733         const char *commit[] = { "git", "commit", NULL };
1734         const char *gc[] = { "git", "gc", NULL };
1735         struct {
1736                 enum keymap keymap;
1737                 int key;
1738                 int argc;
1739                 const char **argv;
1740         } reqs[] = {
1741                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1742                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1743                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1744                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1745         };
1746         int i;
1748         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1749                 enum request req;
1751                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1752                 if (req != REQ_NONE)
1753                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1754         }
1757 /*
1758  * User config file handling.
1759  */
1761 static int   config_lineno;
1762 static bool  config_errors;
1763 static const char *config_msg;
1765 static const struct enum_map color_map[] = {
1766 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1767         COLOR_MAP(DEFAULT),
1768         COLOR_MAP(BLACK),
1769         COLOR_MAP(BLUE),
1770         COLOR_MAP(CYAN),
1771         COLOR_MAP(GREEN),
1772         COLOR_MAP(MAGENTA),
1773         COLOR_MAP(RED),
1774         COLOR_MAP(WHITE),
1775         COLOR_MAP(YELLOW),
1776 };
1778 static const struct enum_map attr_map[] = {
1779 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1780         ATTR_MAP(NORMAL),
1781         ATTR_MAP(BLINK),
1782         ATTR_MAP(BOLD),
1783         ATTR_MAP(DIM),
1784         ATTR_MAP(REVERSE),
1785         ATTR_MAP(STANDOUT),
1786         ATTR_MAP(UNDERLINE),
1787 };
1789 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1791 static int parse_step(double *opt, const char *arg)
1793         *opt = atoi(arg);
1794         if (!strchr(arg, '%'))
1795                 return OK;
1797         /* "Shift down" so 100% and 1 does not conflict. */
1798         *opt = (*opt - 1) / 100;
1799         if (*opt >= 1.0) {
1800                 *opt = 0.99;
1801                 config_msg = "Step value larger than 100%";
1802                 return ERR;
1803         }
1804         if (*opt < 0.0) {
1805                 *opt = 1;
1806                 config_msg = "Invalid step value";
1807                 return ERR;
1808         }
1809         return OK;
1812 static int
1813 parse_int(int *opt, const char *arg, int min, int max)
1815         int value = atoi(arg);
1817         if (min <= value && value <= max) {
1818                 *opt = value;
1819                 return OK;
1820         }
1822         config_msg = "Integer value out of bound";
1823         return ERR;
1826 static bool
1827 set_color(int *color, const char *name)
1829         if (map_enum(color, color_map, name))
1830                 return TRUE;
1831         if (!prefixcmp(name, "color"))
1832                 return parse_int(color, name + 5, 0, 255) == OK;
1833         return FALSE;
1836 /* Wants: object fgcolor bgcolor [attribute] */
1837 static int
1838 option_color_command(int argc, const char *argv[])
1840         struct line_info *info;
1842         if (argc < 3) {
1843                 config_msg = "Wrong number of arguments given to color command";
1844                 return ERR;
1845         }
1847         info = get_line_info(argv[0]);
1848         if (!info) {
1849                 static const struct enum_map obsolete[] = {
1850                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1851                         ENUM_MAP("main-date",   LINE_DATE),
1852                         ENUM_MAP("main-author", LINE_AUTHOR),
1853                 };
1854                 int index;
1856                 if (!map_enum(&index, obsolete, argv[0])) {
1857                         config_msg = "Unknown color name";
1858                         return ERR;
1859                 }
1860                 info = &line_info[index];
1861         }
1863         if (!set_color(&info->fg, argv[1]) ||
1864             !set_color(&info->bg, argv[2])) {
1865                 config_msg = "Unknown color";
1866                 return ERR;
1867         }
1869         info->attr = 0;
1870         while (argc-- > 3) {
1871                 int attr;
1873                 if (!set_attribute(&attr, argv[argc])) {
1874                         config_msg = "Unknown attribute";
1875                         return ERR;
1876                 }
1877                 info->attr |= attr;
1878         }
1880         return OK;
1883 static int parse_bool(bool *opt, const char *arg)
1885         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1886                 ? TRUE : FALSE;
1887         return OK;
1890 static int parse_enum_do(unsigned int *opt, const char *arg,
1891                          const struct enum_map *map, size_t map_size)
1893         bool is_true;
1895         assert(map_size > 1);
1897         if (map_enum_do(map, map_size, (int *) opt, arg))
1898                 return OK;
1900         if (parse_bool(&is_true, arg) != OK)
1901                 return ERR;
1903         *opt = is_true ? map[1].value : map[0].value;
1904         return OK;
1907 #define parse_enum(opt, arg, map) \
1908         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1910 static int
1911 parse_string(char *opt, const char *arg, size_t optsize)
1913         int arglen = strlen(arg);
1915         switch (arg[0]) {
1916         case '\"':
1917         case '\'':
1918                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1919                         config_msg = "Unmatched quotation";
1920                         return ERR;
1921                 }
1922                 arg += 1; arglen -= 2;
1923         default:
1924                 string_ncopy_do(opt, optsize, arg, arglen);
1925                 return OK;
1926         }
1929 /* Wants: name = value */
1930 static int
1931 option_set_command(int argc, const char *argv[])
1933         if (argc != 3) {
1934                 config_msg = "Wrong number of arguments given to set command";
1935                 return ERR;
1936         }
1938         if (strcmp(argv[1], "=")) {
1939                 config_msg = "No value assigned";
1940                 return ERR;
1941         }
1943         if (!strcmp(argv[0], "show-author"))
1944                 return parse_enum(&opt_author, argv[2], author_map);
1946         if (!strcmp(argv[0], "show-date"))
1947                 return parse_enum(&opt_date, argv[2], date_map);
1949         if (!strcmp(argv[0], "show-rev-graph"))
1950                 return parse_bool(&opt_rev_graph, argv[2]);
1952         if (!strcmp(argv[0], "show-refs"))
1953                 return parse_bool(&opt_show_refs, argv[2]);
1955         if (!strcmp(argv[0], "show-line-numbers"))
1956                 return parse_bool(&opt_line_number, argv[2]);
1958         if (!strcmp(argv[0], "line-graphics"))
1959                 return parse_bool(&opt_line_graphics, argv[2]);
1961         if (!strcmp(argv[0], "line-number-interval"))
1962                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1964         if (!strcmp(argv[0], "author-width"))
1965                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1967         if (!strcmp(argv[0], "horizontal-scroll"))
1968                 return parse_step(&opt_hscroll, argv[2]);
1970         if (!strcmp(argv[0], "split-view-height"))
1971                 return parse_step(&opt_scale_split_view, argv[2]);
1973         if (!strcmp(argv[0], "tab-size"))
1974                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1976         if (!strcmp(argv[0], "commit-encoding"))
1977                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1979         config_msg = "Unknown variable name";
1980         return ERR;
1983 /* Wants: mode request key */
1984 static int
1985 option_bind_command(int argc, const char *argv[])
1987         enum request request;
1988         int keymap = -1;
1989         int key;
1991         if (argc < 3) {
1992                 config_msg = "Wrong number of arguments given to bind command";
1993                 return ERR;
1994         }
1996         if (set_keymap(&keymap, argv[0]) == ERR) {
1997                 config_msg = "Unknown key map";
1998                 return ERR;
1999         }
2001         key = get_key_value(argv[1]);
2002         if (key == ERR) {
2003                 config_msg = "Unknown key";
2004                 return ERR;
2005         }
2007         request = get_request(argv[2]);
2008         if (request == REQ_NONE) {
2009                 static const struct enum_map obsolete[] = {
2010                         ENUM_MAP("cherry-pick",         REQ_NONE),
2011                         ENUM_MAP("screen-resize",       REQ_NONE),
2012                         ENUM_MAP("tree-parent",         REQ_PARENT),
2013                 };
2014                 int alias;
2016                 if (map_enum(&alias, obsolete, argv[2])) {
2017                         if (alias != REQ_NONE)
2018                                 add_keybinding(keymap, alias, key);
2019                         config_msg = "Obsolete request name";
2020                         return ERR;
2021                 }
2022         }
2023         if (request == REQ_NONE && *argv[2]++ == '!')
2024                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2025         if (request == REQ_NONE) {
2026                 config_msg = "Unknown request name";
2027                 return ERR;
2028         }
2030         add_keybinding(keymap, request, key);
2032         return OK;
2035 static int
2036 set_option(const char *opt, char *value)
2038         const char *argv[SIZEOF_ARG];
2039         int argc = 0;
2041         if (!argv_from_string(argv, &argc, value)) {
2042                 config_msg = "Too many option arguments";
2043                 return ERR;
2044         }
2046         if (!strcmp(opt, "color"))
2047                 return option_color_command(argc, argv);
2049         if (!strcmp(opt, "set"))
2050                 return option_set_command(argc, argv);
2052         if (!strcmp(opt, "bind"))
2053                 return option_bind_command(argc, argv);
2055         config_msg = "Unknown option command";
2056         return ERR;
2059 static int
2060 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2062         int status = OK;
2064         config_lineno++;
2065         config_msg = "Internal error";
2067         /* Check for comment markers, since read_properties() will
2068          * only ensure opt and value are split at first " \t". */
2069         optlen = strcspn(opt, "#");
2070         if (optlen == 0)
2071                 return OK;
2073         if (opt[optlen] != 0) {
2074                 config_msg = "No option value";
2075                 status = ERR;
2077         }  else {
2078                 /* Look for comment endings in the value. */
2079                 size_t len = strcspn(value, "#");
2081                 if (len < valuelen) {
2082                         valuelen = len;
2083                         value[valuelen] = 0;
2084                 }
2086                 status = set_option(opt, value);
2087         }
2089         if (status == ERR) {
2090                 warn("Error on line %d, near '%.*s': %s",
2091                      config_lineno, (int) optlen, opt, config_msg);
2092                 config_errors = TRUE;
2093         }
2095         /* Always keep going if errors are encountered. */
2096         return OK;
2099 static void
2100 load_option_file(const char *path)
2102         struct io io = {};
2104         /* It's OK that the file doesn't exist. */
2105         if (!io_open(&io, "%s", path))
2106                 return;
2108         config_lineno = 0;
2109         config_errors = FALSE;
2111         if (io_load(&io, " \t", read_option) == ERR ||
2112             config_errors == TRUE)
2113                 warn("Errors while loading %s.", path);
2116 static int
2117 load_options(void)
2119         const char *home = getenv("HOME");
2120         const char *tigrc_user = getenv("TIGRC_USER");
2121         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2122         char buf[SIZEOF_STR];
2124         add_builtin_run_requests();
2126         if (!tigrc_system)
2127                 tigrc_system = SYSCONFDIR "/tigrc";
2128         load_option_file(tigrc_system);
2130         if (!tigrc_user) {
2131                 if (!home || !string_format(buf, "%s/.tigrc", home))
2132                         return ERR;
2133                 tigrc_user = buf;
2134         }
2135         load_option_file(tigrc_user);
2137         return OK;
2141 /*
2142  * The viewer
2143  */
2145 struct view;
2146 struct view_ops;
2148 /* The display array of active views and the index of the current view. */
2149 static struct view *display[2];
2150 static unsigned int current_view;
2152 #define foreach_displayed_view(view, i) \
2153         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2155 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2157 /* Current head and commit ID */
2158 static char ref_blob[SIZEOF_REF]        = "";
2159 static char ref_commit[SIZEOF_REF]      = "HEAD";
2160 static char ref_head[SIZEOF_REF]        = "HEAD";
2161 static char ref_branch[SIZEOF_REF]      = "";
2163 struct view {
2164         const char *name;       /* View name */
2165         const char *cmd_env;    /* Command line set via environment */
2166         const char *id;         /* Points to either of ref_{head,commit,blob} */
2168         struct view_ops *ops;   /* View operations */
2170         enum keymap keymap;     /* What keymap does this view have */
2171         bool git_dir;           /* Whether the view requires a git directory. */
2173         char ref[SIZEOF_REF];   /* Hovered commit reference */
2174         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2176         int height, width;      /* The width and height of the main window */
2177         WINDOW *win;            /* The main window */
2178         WINDOW *title;          /* The title window living below the main window */
2180         /* Navigation */
2181         unsigned long offset;   /* Offset of the window top */
2182         unsigned long yoffset;  /* Offset from the window side. */
2183         unsigned long lineno;   /* Current line number */
2184         unsigned long p_offset; /* Previous offset of the window top */
2185         unsigned long p_yoffset;/* Previous offset from the window side */
2186         unsigned long p_lineno; /* Previous current line number */
2187         bool p_restore;         /* Should the previous position be restored. */
2189         /* Searching */
2190         char grep[SIZEOF_STR];  /* Search string */
2191         regex_t *regex;         /* Pre-compiled regexp */
2193         /* If non-NULL, points to the view that opened this view. If this view
2194          * is closed tig will switch back to the parent view. */
2195         struct view *parent;
2197         /* Buffering */
2198         size_t lines;           /* Total number of lines */
2199         struct line *line;      /* Line index */
2200         unsigned int digits;    /* Number of digits in the lines member. */
2202         /* Drawing */
2203         struct line *curline;   /* Line currently being drawn. */
2204         enum line_type curtype; /* Attribute currently used for drawing. */
2205         unsigned long col;      /* Column when drawing. */
2206         bool has_scrolled;      /* View was scrolled. */
2208         /* Loading */
2209         struct io io;
2210         struct io *pipe;
2211         time_t start_time;
2212         time_t update_secs;
2213 };
2215 struct view_ops {
2216         /* What type of content being displayed. Used in the title bar. */
2217         const char *type;
2218         /* Default command arguments. */
2219         const char **argv;
2220         /* Open and reads in all view content. */
2221         bool (*open)(struct view *view);
2222         /* Read one line; updates view->line. */
2223         bool (*read)(struct view *view, char *data);
2224         /* Draw one line; @lineno must be < view->height. */
2225         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2226         /* Depending on view handle a special requests. */
2227         enum request (*request)(struct view *view, enum request request, struct line *line);
2228         /* Search for regexp in a line. */
2229         bool (*grep)(struct view *view, struct line *line);
2230         /* Select line */
2231         void (*select)(struct view *view, struct line *line);
2232         /* Prepare view for loading */
2233         bool (*prepare)(struct view *view);
2234 };
2236 static struct view_ops blame_ops;
2237 static struct view_ops blob_ops;
2238 static struct view_ops diff_ops;
2239 static struct view_ops help_ops;
2240 static struct view_ops log_ops;
2241 static struct view_ops main_ops;
2242 static struct view_ops pager_ops;
2243 static struct view_ops stage_ops;
2244 static struct view_ops status_ops;
2245 static struct view_ops tree_ops;
2246 static struct view_ops branch_ops;
2248 #define VIEW_STR(name, env, ref, ops, map, git) \
2249         { name, #env, ref, ops, map, git }
2251 #define VIEW_(id, name, ops, git, ref) \
2252         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2255 static struct view views[] = {
2256         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2257         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2258         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2259         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2260         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2261         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2262         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2263         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2264         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2265         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2266         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2267 };
2269 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2270 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2272 #define foreach_view(view, i) \
2273         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2275 #define view_is_displayed(view) \
2276         (view == display[0] || view == display[1])
2279 static inline void
2280 set_view_attr(struct view *view, enum line_type type)
2282         if (!view->curline->selected && view->curtype != type) {
2283                 (void) wattrset(view->win, get_line_attr(type));
2284                 wchgat(view->win, -1, 0, type, NULL);
2285                 view->curtype = type;
2286         }
2289 static int
2290 draw_chars(struct view *view, enum line_type type, const char *string,
2291            int max_len, bool use_tilde)
2293         static char out_buffer[BUFSIZ * 2];
2294         int len = 0;
2295         int col = 0;
2296         int trimmed = FALSE;
2297         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2299         if (max_len <= 0)
2300                 return 0;
2302         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2304         set_view_attr(view, type);
2305         if (len > 0) {
2306                 if (opt_iconv_out != ICONV_NONE) {
2307                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2308                         size_t inlen = len + 1;
2310                         char *outbuf = out_buffer;
2311                         size_t outlen = sizeof(out_buffer);
2313                         size_t ret;
2315                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2316                         if (ret != (size_t) -1) {
2317                                 string = out_buffer;
2318                                 len = sizeof(out_buffer) - outlen;
2319                         }
2320                 }
2322                 waddnstr(view->win, string, len);
2323         }
2324         if (trimmed && use_tilde) {
2325                 set_view_attr(view, LINE_DELIMITER);
2326                 waddch(view->win, '~');
2327                 col++;
2328         }
2330         return col;
2333 static int
2334 draw_space(struct view *view, enum line_type type, int max, int spaces)
2336         static char space[] = "                    ";
2337         int col = 0;
2339         spaces = MIN(max, spaces);
2341         while (spaces > 0) {
2342                 int len = MIN(spaces, sizeof(space) - 1);
2344                 col += draw_chars(view, type, space, len, FALSE);
2345                 spaces -= len;
2346         }
2348         return col;
2351 static bool
2352 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2354         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2355         return view->width + view->yoffset <= view->col;
2358 static bool
2359 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2361         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2362         int max = view->width + view->yoffset - view->col;
2363         int i;
2365         if (max < size)
2366                 size = max;
2368         set_view_attr(view, type);
2369         /* Using waddch() instead of waddnstr() ensures that
2370          * they'll be rendered correctly for the cursor line. */
2371         for (i = skip; i < size; i++)
2372                 waddch(view->win, graphic[i]);
2374         view->col += size;
2375         if (size < max && skip <= size)
2376                 waddch(view->win, ' ');
2377         view->col++;
2379         return view->width + view->yoffset <= view->col;
2382 static bool
2383 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2385         int max = MIN(view->width + view->yoffset - view->col, len);
2386         int col;
2388         if (text)
2389                 col = draw_chars(view, type, text, max - 1, trim);
2390         else
2391                 col = draw_space(view, type, max - 1, max - 1);
2393         view->col += col;
2394         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2395         return view->width + view->yoffset <= view->col;
2398 static bool
2399 draw_date(struct view *view, struct time *time)
2401         const char *date = mkdate(time, opt_date);
2402         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2404         return draw_field(view, LINE_DATE, date, cols, FALSE);
2407 static bool
2408 draw_author(struct view *view, const char *author)
2410         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2411         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2413         if (abbreviate && author)
2414                 author = get_author_initials(author);
2416         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2419 static bool
2420 draw_mode(struct view *view, mode_t mode)
2422         const char *str;
2424         if (S_ISDIR(mode))
2425                 str = "drwxr-xr-x";
2426         else if (S_ISLNK(mode))
2427                 str = "lrwxrwxrwx";
2428         else if (S_ISGITLINK(mode))
2429                 str = "m---------";
2430         else if (S_ISREG(mode) && mode & S_IXUSR)
2431                 str = "-rwxr-xr-x";
2432         else if (S_ISREG(mode))
2433                 str = "-rw-r--r--";
2434         else
2435                 str = "----------";
2437         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2440 static bool
2441 draw_lineno(struct view *view, unsigned int lineno)
2443         char number[10];
2444         int digits3 = view->digits < 3 ? 3 : view->digits;
2445         int max = MIN(view->width + view->yoffset - view->col, digits3);
2446         char *text = NULL;
2447         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2449         lineno += view->offset + 1;
2450         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2451                 static char fmt[] = "%1ld";
2453                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2454                 if (string_format(number, fmt, lineno))
2455                         text = number;
2456         }
2457         if (text)
2458                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2459         else
2460                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2461         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2464 static bool
2465 draw_view_line(struct view *view, unsigned int lineno)
2467         struct line *line;
2468         bool selected = (view->offset + lineno == view->lineno);
2470         assert(view_is_displayed(view));
2472         if (view->offset + lineno >= view->lines)
2473                 return FALSE;
2475         line = &view->line[view->offset + lineno];
2477         wmove(view->win, lineno, 0);
2478         if (line->cleareol)
2479                 wclrtoeol(view->win);
2480         view->col = 0;
2481         view->curline = line;
2482         view->curtype = LINE_NONE;
2483         line->selected = FALSE;
2484         line->dirty = line->cleareol = 0;
2486         if (selected) {
2487                 set_view_attr(view, LINE_CURSOR);
2488                 line->selected = TRUE;
2489                 view->ops->select(view, line);
2490         }
2492         return view->ops->draw(view, line, lineno);
2495 static void
2496 redraw_view_dirty(struct view *view)
2498         bool dirty = FALSE;
2499         int lineno;
2501         for (lineno = 0; lineno < view->height; lineno++) {
2502                 if (view->offset + lineno >= view->lines)
2503                         break;
2504                 if (!view->line[view->offset + lineno].dirty)
2505                         continue;
2506                 dirty = TRUE;
2507                 if (!draw_view_line(view, lineno))
2508                         break;
2509         }
2511         if (!dirty)
2512                 return;
2513         wnoutrefresh(view->win);
2516 static void
2517 redraw_view_from(struct view *view, int lineno)
2519         assert(0 <= lineno && lineno < view->height);
2521         for (; lineno < view->height; lineno++) {
2522                 if (!draw_view_line(view, lineno))
2523                         break;
2524         }
2526         wnoutrefresh(view->win);
2529 static void
2530 redraw_view(struct view *view)
2532         werase(view->win);
2533         redraw_view_from(view, 0);
2537 static void
2538 update_view_title(struct view *view)
2540         char buf[SIZEOF_STR];
2541         char state[SIZEOF_STR];
2542         size_t bufpos = 0, statelen = 0;
2544         assert(view_is_displayed(view));
2546         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2547                 unsigned int view_lines = view->offset + view->height;
2548                 unsigned int lines = view->lines
2549                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2550                                    : 0;
2552                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2553                                    view->ops->type,
2554                                    view->lineno + 1,
2555                                    view->lines,
2556                                    lines);
2558         }
2560         if (view->pipe) {
2561                 time_t secs = time(NULL) - view->start_time;
2563                 /* Three git seconds are a long time ... */
2564                 if (secs > 2)
2565                         string_format_from(state, &statelen, " loading %lds", secs);
2566         }
2568         string_format_from(buf, &bufpos, "[%s]", view->name);
2569         if (*view->ref && bufpos < view->width) {
2570                 size_t refsize = strlen(view->ref);
2571                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2573                 if (minsize < view->width)
2574                         refsize = view->width - minsize + 7;
2575                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2576         }
2578         if (statelen && bufpos < view->width) {
2579                 string_format_from(buf, &bufpos, "%s", state);
2580         }
2582         if (view == display[current_view])
2583                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2584         else
2585                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2587         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2588         wclrtoeol(view->title);
2589         wnoutrefresh(view->title);
2592 static int
2593 apply_step(double step, int value)
2595         if (step >= 1)
2596                 return (int) step;
2597         value *= step + 0.01;
2598         return value ? value : 1;
2601 static void
2602 resize_display(void)
2604         int offset, i;
2605         struct view *base = display[0];
2606         struct view *view = display[1] ? display[1] : display[0];
2608         /* Setup window dimensions */
2610         getmaxyx(stdscr, base->height, base->width);
2612         /* Make room for the status window. */
2613         base->height -= 1;
2615         if (view != base) {
2616                 /* Horizontal split. */
2617                 view->width   = base->width;
2618                 view->height  = apply_step(opt_scale_split_view, base->height);
2619                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2620                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2621                 base->height -= view->height;
2623                 /* Make room for the title bar. */
2624                 view->height -= 1;
2625         }
2627         /* Make room for the title bar. */
2628         base->height -= 1;
2630         offset = 0;
2632         foreach_displayed_view (view, i) {
2633                 if (!view->win) {
2634                         view->win = newwin(view->height, 0, offset, 0);
2635                         if (!view->win)
2636                                 die("Failed to create %s view", view->name);
2638                         scrollok(view->win, FALSE);
2640                         view->title = newwin(1, 0, offset + view->height, 0);
2641                         if (!view->title)
2642                                 die("Failed to create title window");
2644                 } else {
2645                         wresize(view->win, view->height, view->width);
2646                         mvwin(view->win,   offset, 0);
2647                         mvwin(view->title, offset + view->height, 0);
2648                 }
2650                 offset += view->height + 1;
2651         }
2654 static void
2655 redraw_display(bool clear)
2657         struct view *view;
2658         int i;
2660         foreach_displayed_view (view, i) {
2661                 if (clear)
2662                         wclear(view->win);
2663                 redraw_view(view);
2664                 update_view_title(view);
2665         }
2668 static void
2669 toggle_enum_option_do(unsigned int *opt, const char *help,
2670                       const struct enum_map *map, size_t size)
2672         *opt = (*opt + 1) % size;
2673         redraw_display(FALSE);
2674         report("Displaying %s %s", enum_name(map[*opt]), help);
2677 #define toggle_enum_option(opt, help, map) \
2678         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2680 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2681 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2683 static void
2684 toggle_view_option(bool *option, const char *help)
2686         *option = !*option;
2687         redraw_display(FALSE);
2688         report("%sabling %s", *option ? "En" : "Dis", help);
2691 static void
2692 open_option_menu(void)
2694         const struct menu_item menu[] = {
2695                 { '.', "line numbers", &opt_line_number },
2696                 { 'D', "date display", &opt_date },
2697                 { 'A', "author display", &opt_author },
2698                 { 'g', "revision graph display", &opt_rev_graph },
2699                 { 'F', "reference display", &opt_show_refs },
2700                 { 0 }
2701         };
2702         int selected = 0;
2704         if (prompt_menu("Toggle option", menu, &selected)) {
2705                 if (menu[selected].data == &opt_date)
2706                         toggle_date();
2707                 else if (menu[selected].data == &opt_author)
2708                         toggle_author();
2709                 else
2710                         toggle_view_option(menu[selected].data, menu[selected].text);
2711         }
2714 static void
2715 maximize_view(struct view *view)
2717         memset(display, 0, sizeof(display));
2718         current_view = 0;
2719         display[current_view] = view;
2720         resize_display();
2721         redraw_display(FALSE);
2722         report("");
2726 /*
2727  * Navigation
2728  */
2730 static bool
2731 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2733         if (lineno >= view->lines)
2734                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2736         if (offset > lineno || offset + view->height <= lineno) {
2737                 unsigned long half = view->height / 2;
2739                 if (lineno > half)
2740                         offset = lineno - half;
2741                 else
2742                         offset = 0;
2743         }
2745         if (offset != view->offset || lineno != view->lineno) {
2746                 view->offset = offset;
2747                 view->lineno = lineno;
2748                 return TRUE;
2749         }
2751         return FALSE;
2754 /* Scrolling backend */
2755 static void
2756 do_scroll_view(struct view *view, int lines)
2758         bool redraw_current_line = FALSE;
2760         /* The rendering expects the new offset. */
2761         view->offset += lines;
2763         assert(0 <= view->offset && view->offset < view->lines);
2764         assert(lines);
2766         /* Move current line into the view. */
2767         if (view->lineno < view->offset) {
2768                 view->lineno = view->offset;
2769                 redraw_current_line = TRUE;
2770         } else if (view->lineno >= view->offset + view->height) {
2771                 view->lineno = view->offset + view->height - 1;
2772                 redraw_current_line = TRUE;
2773         }
2775         assert(view->offset <= view->lineno && view->lineno < view->lines);
2777         /* Redraw the whole screen if scrolling is pointless. */
2778         if (view->height < ABS(lines)) {
2779                 redraw_view(view);
2781         } else {
2782                 int line = lines > 0 ? view->height - lines : 0;
2783                 int end = line + ABS(lines);
2785                 scrollok(view->win, TRUE);
2786                 wscrl(view->win, lines);
2787                 scrollok(view->win, FALSE);
2789                 while (line < end && draw_view_line(view, line))
2790                         line++;
2792                 if (redraw_current_line)
2793                         draw_view_line(view, view->lineno - view->offset);
2794                 wnoutrefresh(view->win);
2795         }
2797         view->has_scrolled = TRUE;
2798         report("");
2801 /* Scroll frontend */
2802 static void
2803 scroll_view(struct view *view, enum request request)
2805         int lines = 1;
2807         assert(view_is_displayed(view));
2809         switch (request) {
2810         case REQ_SCROLL_LEFT:
2811                 if (view->yoffset == 0) {
2812                         report("Cannot scroll beyond the first column");
2813                         return;
2814                 }
2815                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2816                         view->yoffset = 0;
2817                 else
2818                         view->yoffset -= apply_step(opt_hscroll, view->width);
2819                 redraw_view_from(view, 0);
2820                 report("");
2821                 return;
2822         case REQ_SCROLL_RIGHT:
2823                 view->yoffset += apply_step(opt_hscroll, view->width);
2824                 redraw_view(view);
2825                 report("");
2826                 return;
2827         case REQ_SCROLL_PAGE_DOWN:
2828                 lines = view->height;
2829         case REQ_SCROLL_LINE_DOWN:
2830                 if (view->offset + lines > view->lines)
2831                         lines = view->lines - view->offset;
2833                 if (lines == 0 || view->offset + view->height >= view->lines) {
2834                         report("Cannot scroll beyond the last line");
2835                         return;
2836                 }
2837                 break;
2839         case REQ_SCROLL_PAGE_UP:
2840                 lines = view->height;
2841         case REQ_SCROLL_LINE_UP:
2842                 if (lines > view->offset)
2843                         lines = view->offset;
2845                 if (lines == 0) {
2846                         report("Cannot scroll beyond the first line");
2847                         return;
2848                 }
2850                 lines = -lines;
2851                 break;
2853         default:
2854                 die("request %d not handled in switch", request);
2855         }
2857         do_scroll_view(view, lines);
2860 /* Cursor moving */
2861 static void
2862 move_view(struct view *view, enum request request)
2864         int scroll_steps = 0;
2865         int steps;
2867         switch (request) {
2868         case REQ_MOVE_FIRST_LINE:
2869                 steps = -view->lineno;
2870                 break;
2872         case REQ_MOVE_LAST_LINE:
2873                 steps = view->lines - view->lineno - 1;
2874                 break;
2876         case REQ_MOVE_PAGE_UP:
2877                 steps = view->height > view->lineno
2878                       ? -view->lineno : -view->height;
2879                 break;
2881         case REQ_MOVE_PAGE_DOWN:
2882                 steps = view->lineno + view->height >= view->lines
2883                       ? view->lines - view->lineno - 1 : view->height;
2884                 break;
2886         case REQ_MOVE_UP:
2887                 steps = -1;
2888                 break;
2890         case REQ_MOVE_DOWN:
2891                 steps = 1;
2892                 break;
2894         default:
2895                 die("request %d not handled in switch", request);
2896         }
2898         if (steps <= 0 && view->lineno == 0) {
2899                 report("Cannot move beyond the first line");
2900                 return;
2902         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2903                 report("Cannot move beyond the last line");
2904                 return;
2905         }
2907         /* Move the current line */
2908         view->lineno += steps;
2909         assert(0 <= view->lineno && view->lineno < view->lines);
2911         /* Check whether the view needs to be scrolled */
2912         if (view->lineno < view->offset ||
2913             view->lineno >= view->offset + view->height) {
2914                 scroll_steps = steps;
2915                 if (steps < 0 && -steps > view->offset) {
2916                         scroll_steps = -view->offset;
2918                 } else if (steps > 0) {
2919                         if (view->lineno == view->lines - 1 &&
2920                             view->lines > view->height) {
2921                                 scroll_steps = view->lines - view->offset - 1;
2922                                 if (scroll_steps >= view->height)
2923                                         scroll_steps -= view->height - 1;
2924                         }
2925                 }
2926         }
2928         if (!view_is_displayed(view)) {
2929                 view->offset += scroll_steps;
2930                 assert(0 <= view->offset && view->offset < view->lines);
2931                 view->ops->select(view, &view->line[view->lineno]);
2932                 return;
2933         }
2935         /* Repaint the old "current" line if we be scrolling */
2936         if (ABS(steps) < view->height)
2937                 draw_view_line(view, view->lineno - steps - view->offset);
2939         if (scroll_steps) {
2940                 do_scroll_view(view, scroll_steps);
2941                 return;
2942         }
2944         /* Draw the current line */
2945         draw_view_line(view, view->lineno - view->offset);
2947         wnoutrefresh(view->win);
2948         report("");
2952 /*
2953  * Searching
2954  */
2956 static void search_view(struct view *view, enum request request);
2958 static bool
2959 grep_text(struct view *view, const char *text[])
2961         regmatch_t pmatch;
2962         size_t i;
2964         for (i = 0; text[i]; i++)
2965                 if (*text[i] &&
2966                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2967                         return TRUE;
2968         return FALSE;
2971 static void
2972 select_view_line(struct view *view, unsigned long lineno)
2974         unsigned long old_lineno = view->lineno;
2975         unsigned long old_offset = view->offset;
2977         if (goto_view_line(view, view->offset, lineno)) {
2978                 if (view_is_displayed(view)) {
2979                         if (old_offset != view->offset) {
2980                                 redraw_view(view);
2981                         } else {
2982                                 draw_view_line(view, old_lineno - view->offset);
2983                                 draw_view_line(view, view->lineno - view->offset);
2984                                 wnoutrefresh(view->win);
2985                         }
2986                 } else {
2987                         view->ops->select(view, &view->line[view->lineno]);
2988                 }
2989         }
2992 static void
2993 find_next(struct view *view, enum request request)
2995         unsigned long lineno = view->lineno;
2996         int direction;
2998         if (!*view->grep) {
2999                 if (!*opt_search)
3000                         report("No previous search");
3001                 else
3002                         search_view(view, request);
3003                 return;
3004         }
3006         switch (request) {
3007         case REQ_SEARCH:
3008         case REQ_FIND_NEXT:
3009                 direction = 1;
3010                 break;
3012         case REQ_SEARCH_BACK:
3013         case REQ_FIND_PREV:
3014                 direction = -1;
3015                 break;
3017         default:
3018                 return;
3019         }
3021         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3022                 lineno += direction;
3024         /* Note, lineno is unsigned long so will wrap around in which case it
3025          * will become bigger than view->lines. */
3026         for (; lineno < view->lines; lineno += direction) {
3027                 if (view->ops->grep(view, &view->line[lineno])) {
3028                         select_view_line(view, lineno);
3029                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3030                         return;
3031                 }
3032         }
3034         report("No match found for '%s'", view->grep);
3037 static void
3038 search_view(struct view *view, enum request request)
3040         int regex_err;
3042         if (view->regex) {
3043                 regfree(view->regex);
3044                 *view->grep = 0;
3045         } else {
3046                 view->regex = calloc(1, sizeof(*view->regex));
3047                 if (!view->regex)
3048                         return;
3049         }
3051         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3052         if (regex_err != 0) {
3053                 char buf[SIZEOF_STR] = "unknown error";
3055                 regerror(regex_err, view->regex, buf, sizeof(buf));
3056                 report("Search failed: %s", buf);
3057                 return;
3058         }
3060         string_copy(view->grep, opt_search);
3062         find_next(view, request);
3065 /*
3066  * Incremental updating
3067  */
3069 static void
3070 reset_view(struct view *view)
3072         int i;
3074         for (i = 0; i < view->lines; i++)
3075                 free(view->line[i].data);
3076         free(view->line);
3078         view->p_offset = view->offset;
3079         view->p_yoffset = view->yoffset;
3080         view->p_lineno = view->lineno;
3082         view->line = NULL;
3083         view->offset = 0;
3084         view->yoffset = 0;
3085         view->lines  = 0;
3086         view->lineno = 0;
3087         view->vid[0] = 0;
3088         view->update_secs = 0;
3091 static void
3092 free_argv(const char *argv[])
3094         int argc;
3096         for (argc = 0; argv[argc]; argc++)
3097                 free((void *) argv[argc]);
3100 static const char *
3101 format_arg(const char *name)
3103         static struct {
3104                 const char *name;
3105                 size_t namelen;
3106                 const char *value;
3107                 const char *value_if_empty;
3108         } vars[] = {
3109 #define FORMAT_VAR(name, value, value_if_empty) \
3110         { name, STRING_SIZE(name), value, value_if_empty }
3111                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3112                 FORMAT_VAR("%(file)",           opt_file,       ""),
3113                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3114                 FORMAT_VAR("%(head)",           ref_head,       ""),
3115                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3116                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3117                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3118         };
3119         int i;
3121         for (i = 0; i < ARRAY_SIZE(vars); i++)
3122                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3123                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3125         report("Unknown replacement: `%s`", name);
3126         return NULL;
3129 static bool
3130 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3132         char buf[SIZEOF_STR];
3133         int argc;
3134         bool noreplace = flags == FORMAT_NONE;
3136         free_argv(dst_argv);
3138         for (argc = 0; src_argv[argc]; argc++) {
3139                 const char *arg = src_argv[argc];
3140                 size_t bufpos = 0;
3142                 while (arg) {
3143                         char *next = strstr(arg, "%(");
3144                         int len = next - arg;
3145                         const char *value;
3147                         if (!next || noreplace) {
3148                                 len = strlen(arg);
3149                                 value = "";
3151                         } else {
3152                                 value = format_arg(next);
3154                                 if (!value) {
3155                                         return FALSE;
3156                                 }
3157                         }
3159                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3160                                 return FALSE;
3162                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3163                 }
3165                 dst_argv[argc] = strdup(buf);
3166                 if (!dst_argv[argc])
3167                         break;
3168         }
3170         dst_argv[argc] = NULL;
3172         return src_argv[argc] == NULL;
3175 static bool
3176 restore_view_position(struct view *view)
3178         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3179                 return FALSE;
3181         /* Changing the view position cancels the restoring. */
3182         /* FIXME: Changing back to the first line is not detected. */
3183         if (view->offset != 0 || view->lineno != 0) {
3184                 view->p_restore = FALSE;
3185                 return FALSE;
3186         }
3188         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3189             view_is_displayed(view))
3190                 werase(view->win);
3192         view->yoffset = view->p_yoffset;
3193         view->p_restore = FALSE;
3195         return TRUE;
3198 static void
3199 end_update(struct view *view, bool force)
3201         if (!view->pipe)
3202                 return;
3203         while (!view->ops->read(view, NULL))
3204                 if (!force)
3205                         return;
3206         if (force)
3207                 io_kill(view->pipe);
3208         io_done(view->pipe);
3209         view->pipe = NULL;
3212 static void
3213 setup_update(struct view *view, const char *vid)
3215         reset_view(view);
3216         string_copy_rev(view->vid, vid);
3217         view->pipe = &view->io;
3218         view->start_time = time(NULL);
3221 static bool
3222 prepare_update(struct view *view, const char *argv[], const char *dir)
3224         if (view->pipe)
3225                 end_update(view, TRUE);
3226         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3229 static bool
3230 prepare_update_file(struct view *view, const char *name)
3232         if (view->pipe)
3233                 end_update(view, TRUE);
3234         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3237 static bool
3238 begin_update(struct view *view, bool refresh)
3240         if (view->pipe)
3241                 end_update(view, TRUE);
3243         if (!refresh) {
3244                 if (view->ops->prepare) {
3245                         if (!view->ops->prepare(view))
3246                                 return FALSE;
3247                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3248                         return FALSE;
3249                 }
3251                 /* Put the current ref_* value to the view title ref
3252                  * member. This is needed by the blob view. Most other
3253                  * views sets it automatically after loading because the
3254                  * first line is a commit line. */
3255                 string_copy_rev(view->ref, view->id);
3256         }
3258         if (!io_start(&view->io))
3259                 return FALSE;
3261         setup_update(view, view->id);
3263         return TRUE;
3266 static bool
3267 update_view(struct view *view)
3269         char out_buffer[BUFSIZ * 2];
3270         char *line;
3271         /* Clear the view and redraw everything since the tree sorting
3272          * might have rearranged things. */
3273         bool redraw = view->lines == 0;
3274         bool can_read = TRUE;
3276         if (!view->pipe)
3277                 return TRUE;
3279         if (!io_can_read(view->pipe)) {
3280                 if (view->lines == 0 && view_is_displayed(view)) {
3281                         time_t secs = time(NULL) - view->start_time;
3283                         if (secs > 1 && secs > view->update_secs) {
3284                                 if (view->update_secs == 0)
3285                                         redraw_view(view);
3286                                 update_view_title(view);
3287                                 view->update_secs = secs;
3288                         }
3289                 }
3290                 return TRUE;
3291         }
3293         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3294                 if (opt_iconv_in != ICONV_NONE) {
3295                         ICONV_CONST char *inbuf = line;
3296                         size_t inlen = strlen(line) + 1;
3298                         char *outbuf = out_buffer;
3299                         size_t outlen = sizeof(out_buffer);
3301                         size_t ret;
3303                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3304                         if (ret != (size_t) -1)
3305                                 line = out_buffer;
3306                 }
3308                 if (!view->ops->read(view, line)) {
3309                         report("Allocation failure");
3310                         end_update(view, TRUE);
3311                         return FALSE;
3312                 }
3313         }
3315         {
3316                 unsigned long lines = view->lines;
3317                 int digits;
3319                 for (digits = 0; lines; digits++)
3320                         lines /= 10;
3322                 /* Keep the displayed view in sync with line number scaling. */
3323                 if (digits != view->digits) {
3324                         view->digits = digits;
3325                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3326                                 redraw = TRUE;
3327                 }
3328         }
3330         if (io_error(view->pipe)) {
3331                 report("Failed to read: %s", io_strerror(view->pipe));
3332                 end_update(view, TRUE);
3334         } else if (io_eof(view->pipe)) {
3335                 report("");
3336                 end_update(view, FALSE);
3337         }
3339         if (restore_view_position(view))
3340                 redraw = TRUE;
3342         if (!view_is_displayed(view))
3343                 return TRUE;
3345         if (redraw)
3346                 redraw_view_from(view, 0);
3347         else
3348                 redraw_view_dirty(view);
3350         /* Update the title _after_ the redraw so that if the redraw picks up a
3351          * commit reference in view->ref it'll be available here. */
3352         update_view_title(view);
3353         return TRUE;
3356 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3358 static struct line *
3359 add_line_data(struct view *view, void *data, enum line_type type)
3361         struct line *line;
3363         if (!realloc_lines(&view->line, view->lines, 1))
3364                 return NULL;
3366         line = &view->line[view->lines++];
3367         memset(line, 0, sizeof(*line));
3368         line->type = type;
3369         line->data = data;
3370         line->dirty = 1;
3372         return line;
3375 static struct line *
3376 add_line_text(struct view *view, const char *text, enum line_type type)
3378         char *data = text ? strdup(text) : NULL;
3380         return data ? add_line_data(view, data, type) : NULL;
3383 static struct line *
3384 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3386         char buf[SIZEOF_STR];
3387         va_list args;
3389         va_start(args, fmt);
3390         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3391                 buf[0] = 0;
3392         va_end(args);
3394         return buf[0] ? add_line_text(view, buf, type) : NULL;
3397 /*
3398  * View opening
3399  */
3401 enum open_flags {
3402         OPEN_DEFAULT = 0,       /* Use default view switching. */
3403         OPEN_SPLIT = 1,         /* Split current view. */
3404         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3405         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3406         OPEN_PREPARED = 32,     /* Open already prepared command. */
3407 };
3409 static void
3410 open_view(struct view *prev, enum request request, enum open_flags flags)
3412         bool split = !!(flags & OPEN_SPLIT);
3413         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3414         bool nomaximize = !!(flags & OPEN_REFRESH);
3415         struct view *view = VIEW(request);
3416         int nviews = displayed_views();
3417         struct view *base_view = display[0];
3419         if (view == prev && nviews == 1 && !reload) {
3420                 report("Already in %s view", view->name);
3421                 return;
3422         }
3424         if (view->git_dir && !opt_git_dir[0]) {
3425                 report("The %s view is disabled in pager view", view->name);
3426                 return;
3427         }
3429         if (split) {
3430                 display[1] = view;
3431                 current_view = 1;
3432         } else if (!nomaximize) {
3433                 /* Maximize the current view. */
3434                 memset(display, 0, sizeof(display));
3435                 current_view = 0;
3436                 display[current_view] = view;
3437         }
3439         /* No parent signals that this is the first loaded view. */
3440         if (prev && view != prev) {
3441                 view->parent = prev;
3442         }
3444         /* Resize the view when switching between split- and full-screen,
3445          * or when switching between two different full-screen views. */
3446         if (nviews != displayed_views() ||
3447             (nviews == 1 && base_view != display[0]))
3448                 resize_display();
3450         if (view->ops->open) {
3451                 if (view->pipe)
3452                         end_update(view, TRUE);
3453                 if (!view->ops->open(view)) {
3454                         report("Failed to load %s view", view->name);
3455                         return;
3456                 }
3457                 restore_view_position(view);
3459         } else if ((reload || strcmp(view->vid, view->id)) &&
3460                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3461                 report("Failed to load %s view", view->name);
3462                 return;
3463         }
3465         if (split && prev->lineno - prev->offset >= prev->height) {
3466                 /* Take the title line into account. */
3467                 int lines = prev->lineno - prev->offset - prev->height + 1;
3469                 /* Scroll the view that was split if the current line is
3470                  * outside the new limited view. */
3471                 do_scroll_view(prev, lines);
3472         }
3474         if (prev && view != prev && split && view_is_displayed(prev)) {
3475                 /* "Blur" the previous view. */
3476                 update_view_title(prev);
3477         }
3479         if (view->pipe && view->lines == 0) {
3480                 /* Clear the old view and let the incremental updating refill
3481                  * the screen. */
3482                 werase(view->win);
3483                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3484                 report("");
3485         } else if (view_is_displayed(view)) {
3486                 redraw_view(view);
3487                 report("");
3488         }
3491 static void
3492 open_external_viewer(const char *argv[], const char *dir)
3494         def_prog_mode();           /* save current tty modes */
3495         endwin();                  /* restore original tty modes */
3496         io_run_fg(argv, dir);
3497         fprintf(stderr, "Press Enter to continue");
3498         getc(opt_tty);
3499         reset_prog_mode();
3500         redraw_display(TRUE);
3503 static void
3504 open_mergetool(const char *file)
3506         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3508         open_external_viewer(mergetool_argv, opt_cdup);
3511 static void
3512 open_editor(const char *file)
3514         const char *editor_argv[] = { "vi", file, NULL };
3515         const char *editor;
3517         editor = getenv("GIT_EDITOR");
3518         if (!editor && *opt_editor)
3519                 editor = opt_editor;
3520         if (!editor)
3521                 editor = getenv("VISUAL");
3522         if (!editor)
3523                 editor = getenv("EDITOR");
3524         if (!editor)
3525                 editor = "vi";
3527         editor_argv[0] = editor;
3528         open_external_viewer(editor_argv, opt_cdup);
3531 static void
3532 open_run_request(enum request request)
3534         struct run_request *req = get_run_request(request);
3535         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3537         if (!req) {
3538                 report("Unknown run request");
3539                 return;
3540         }
3542         if (format_argv(argv, req->argv, FORMAT_ALL))
3543                 open_external_viewer(argv, NULL);
3544         free_argv(argv);
3547 /*
3548  * User request switch noodle
3549  */
3551 static int
3552 view_driver(struct view *view, enum request request)
3554         int i;
3556         if (request == REQ_NONE)
3557                 return TRUE;
3559         if (request > REQ_NONE) {
3560                 open_run_request(request);
3561                 /* FIXME: When all views can refresh always do this. */
3562                 if (view == VIEW(REQ_VIEW_STATUS) ||
3563                     view == VIEW(REQ_VIEW_MAIN) ||
3564                     view == VIEW(REQ_VIEW_LOG) ||
3565                     view == VIEW(REQ_VIEW_BRANCH) ||
3566                     view == VIEW(REQ_VIEW_STAGE))
3567                         request = REQ_REFRESH;
3568                 else
3569                         return TRUE;
3570         }
3572         if (view && view->lines) {
3573                 request = view->ops->request(view, request, &view->line[view->lineno]);
3574                 if (request == REQ_NONE)
3575                         return TRUE;
3576         }
3578         switch (request) {
3579         case REQ_MOVE_UP:
3580         case REQ_MOVE_DOWN:
3581         case REQ_MOVE_PAGE_UP:
3582         case REQ_MOVE_PAGE_DOWN:
3583         case REQ_MOVE_FIRST_LINE:
3584         case REQ_MOVE_LAST_LINE:
3585                 move_view(view, request);
3586                 break;
3588         case REQ_SCROLL_LEFT:
3589         case REQ_SCROLL_RIGHT:
3590         case REQ_SCROLL_LINE_DOWN:
3591         case REQ_SCROLL_LINE_UP:
3592         case REQ_SCROLL_PAGE_DOWN:
3593         case REQ_SCROLL_PAGE_UP:
3594                 scroll_view(view, request);
3595                 break;
3597         case REQ_VIEW_BLAME:
3598                 if (!opt_file[0]) {
3599                         report("No file chosen, press %s to open tree view",
3600                                get_key(view->keymap, REQ_VIEW_TREE));
3601                         break;
3602                 }
3603                 open_view(view, request, OPEN_DEFAULT);
3604                 break;
3606         case REQ_VIEW_BLOB:
3607                 if (!ref_blob[0]) {
3608                         report("No file chosen, press %s to open tree view",
3609                                get_key(view->keymap, REQ_VIEW_TREE));
3610                         break;
3611                 }
3612                 open_view(view, request, OPEN_DEFAULT);
3613                 break;
3615         case REQ_VIEW_PAGER:
3616                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3617                         report("No pager content, press %s to run command from prompt",
3618                                get_key(view->keymap, REQ_PROMPT));
3619                         break;
3620                 }
3621                 open_view(view, request, OPEN_DEFAULT);
3622                 break;
3624         case REQ_VIEW_STAGE:
3625                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3626                         report("No stage content, press %s to open the status view and choose file",
3627                                get_key(view->keymap, REQ_VIEW_STATUS));
3628                         break;
3629                 }
3630                 open_view(view, request, OPEN_DEFAULT);
3631                 break;
3633         case REQ_VIEW_STATUS:
3634                 if (opt_is_inside_work_tree == FALSE) {
3635                         report("The status view requires a working tree");
3636                         break;
3637                 }
3638                 open_view(view, request, OPEN_DEFAULT);
3639                 break;
3641         case REQ_VIEW_MAIN:
3642         case REQ_VIEW_DIFF:
3643         case REQ_VIEW_LOG:
3644         case REQ_VIEW_TREE:
3645         case REQ_VIEW_HELP:
3646         case REQ_VIEW_BRANCH:
3647                 open_view(view, request, OPEN_DEFAULT);
3648                 break;
3650         case REQ_NEXT:
3651         case REQ_PREVIOUS:
3652                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3654                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3655                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3656                    (view == VIEW(REQ_VIEW_DIFF) &&
3657                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3658                    (view == VIEW(REQ_VIEW_STAGE) &&
3659                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3660                    (view == VIEW(REQ_VIEW_BLOB) &&
3661                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3662                    (view == VIEW(REQ_VIEW_MAIN) &&
3663                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3664                         int line;
3666                         view = view->parent;
3667                         line = view->lineno;
3668                         move_view(view, request);
3669                         if (view_is_displayed(view))
3670                                 update_view_title(view);
3671                         if (line != view->lineno)
3672                                 view->ops->request(view, REQ_ENTER,
3673                                                    &view->line[view->lineno]);
3675                 } else {
3676                         move_view(view, request);
3677                 }
3678                 break;
3680         case REQ_VIEW_NEXT:
3681         {
3682                 int nviews = displayed_views();
3683                 int next_view = (current_view + 1) % nviews;
3685                 if (next_view == current_view) {
3686                         report("Only one view is displayed");
3687                         break;
3688                 }
3690                 current_view = next_view;
3691                 /* Blur out the title of the previous view. */
3692                 update_view_title(view);
3693                 report("");
3694                 break;
3695         }
3696         case REQ_REFRESH:
3697                 report("Refreshing is not yet supported for the %s view", view->name);
3698                 break;
3700         case REQ_MAXIMIZE:
3701                 if (displayed_views() == 2)
3702                         maximize_view(view);
3703                 break;
3705         case REQ_OPTIONS:
3706                 open_option_menu();
3707                 break;
3709         case REQ_TOGGLE_LINENO:
3710                 toggle_view_option(&opt_line_number, "line numbers");
3711                 break;
3713         case REQ_TOGGLE_DATE:
3714                 toggle_date();
3715                 break;
3717         case REQ_TOGGLE_AUTHOR:
3718                 toggle_author();
3719                 break;
3721         case REQ_TOGGLE_REV_GRAPH:
3722                 toggle_view_option(&opt_rev_graph, "revision graph display");
3723                 break;
3725         case REQ_TOGGLE_REFS:
3726                 toggle_view_option(&opt_show_refs, "reference display");
3727                 break;
3729         case REQ_TOGGLE_SORT_FIELD:
3730         case REQ_TOGGLE_SORT_ORDER:
3731                 report("Sorting is not yet supported for the %s view", view->name);
3732                 break;
3734         case REQ_SEARCH:
3735         case REQ_SEARCH_BACK:
3736                 search_view(view, request);
3737                 break;
3739         case REQ_FIND_NEXT:
3740         case REQ_FIND_PREV:
3741                 find_next(view, request);
3742                 break;
3744         case REQ_STOP_LOADING:
3745                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3746                         view = &views[i];
3747                         if (view->pipe)
3748                                 report("Stopped loading the %s view", view->name),
3749                         end_update(view, TRUE);
3750                 }
3751                 break;
3753         case REQ_SHOW_VERSION:
3754                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3755                 return TRUE;
3757         case REQ_SCREEN_REDRAW:
3758                 redraw_display(TRUE);
3759                 break;
3761         case REQ_EDIT:
3762                 report("Nothing to edit");
3763                 break;
3765         case REQ_ENTER:
3766                 report("Nothing to enter");
3767                 break;
3769         case REQ_VIEW_CLOSE:
3770                 /* XXX: Mark closed views by letting view->parent point to the
3771                  * view itself. Parents to closed view should never be
3772                  * followed. */
3773                 if (view->parent &&
3774                     view->parent->parent != view->parent) {
3775                         maximize_view(view->parent);
3776                         view->parent = view;
3777                         break;
3778                 }
3779                 /* Fall-through */
3780         case REQ_QUIT:
3781                 return FALSE;
3783         default:
3784                 report("Unknown key, press %s for help",
3785                        get_key(view->keymap, REQ_VIEW_HELP));
3786                 return TRUE;
3787         }
3789         return TRUE;
3793 /*
3794  * View backend utilities
3795  */
3797 enum sort_field {
3798         ORDERBY_NAME,
3799         ORDERBY_DATE,
3800         ORDERBY_AUTHOR,
3801 };
3803 struct sort_state {
3804         const enum sort_field *fields;
3805         size_t size, current;
3806         bool reverse;
3807 };
3809 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3810 #define get_sort_field(state) ((state).fields[(state).current])
3811 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3813 static void
3814 sort_view(struct view *view, enum request request, struct sort_state *state,
3815           int (*compare)(const void *, const void *))
3817         switch (request) {
3818         case REQ_TOGGLE_SORT_FIELD:
3819                 state->current = (state->current + 1) % state->size;
3820                 break;
3822         case REQ_TOGGLE_SORT_ORDER:
3823                 state->reverse = !state->reverse;
3824                 break;
3825         default:
3826                 die("Not a sort request");
3827         }
3829         qsort(view->line, view->lines, sizeof(*view->line), compare);
3830         redraw_view(view);
3833 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3835 /* Small author cache to reduce memory consumption. It uses binary
3836  * search to lookup or find place to position new entries. No entries
3837  * are ever freed. */
3838 static const char *
3839 get_author(const char *name)
3841         static const char **authors;
3842         static size_t authors_size;
3843         int from = 0, to = authors_size - 1;
3845         while (from <= to) {
3846                 size_t pos = (to + from) / 2;
3847                 int cmp = strcmp(name, authors[pos]);
3849                 if (!cmp)
3850                         return authors[pos];
3852                 if (cmp < 0)
3853                         to = pos - 1;
3854                 else
3855                         from = pos + 1;
3856         }
3858         if (!realloc_authors(&authors, authors_size, 1))
3859                 return NULL;
3860         name = strdup(name);
3861         if (!name)
3862                 return NULL;
3864         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3865         authors[from] = name;
3866         authors_size++;
3868         return name;
3871 static void
3872 parse_timesec(struct time *time, const char *sec)
3874         time->sec = (time_t) atol(sec);
3877 static void
3878 parse_timezone(struct time *time, const char *zone)
3880         long tz;
3882         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3883         tz += ('0' - zone[2]) * 60 * 60;
3884         tz += ('0' - zone[3]) * 60;
3885         tz += ('0' - zone[4]);
3887         if (zone[0] == '-')
3888                 tz = -tz;
3890         time->tz = tz;
3891         time->sec -= tz;
3894 /* Parse author lines where the name may be empty:
3895  *      author  <email@address.tld> 1138474660 +0100
3896  */
3897 static void
3898 parse_author_line(char *ident, const char **author, struct time *time)
3900         char *nameend = strchr(ident, '<');
3901         char *emailend = strchr(ident, '>');
3903         if (nameend && emailend)
3904                 *nameend = *emailend = 0;
3905         ident = chomp_string(ident);
3906         if (!*ident) {
3907                 if (nameend)
3908                         ident = chomp_string(nameend + 1);
3909                 if (!*ident)
3910                         ident = "Unknown";
3911         }
3913         *author = get_author(ident);
3915         /* Parse epoch and timezone */
3916         if (emailend && emailend[1] == ' ') {
3917                 char *secs = emailend + 2;
3918                 char *zone = strchr(secs, ' ');
3920                 parse_timesec(time, secs);
3922                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3923                         parse_timezone(time, zone + 1);
3924         }
3927 static bool
3928 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3930         char rev[SIZEOF_REV];
3931         const char *revlist_argv[] = {
3932                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3933         };
3934         struct menu_item *items;
3935         char text[SIZEOF_STR];
3936         bool ok = TRUE;
3937         int i;
3939         items = calloc(*parents + 1, sizeof(*items));
3940         if (!items)
3941                 return FALSE;
3943         for (i = 0; i < *parents; i++) {
3944                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3945                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3946                     !(items[i].text = strdup(text))) {
3947                         ok = FALSE;
3948                         break;
3949                 }
3950         }
3952         if (ok) {
3953                 *parents = 0;
3954                 ok = prompt_menu("Select parent", items, parents);
3955         }
3956         for (i = 0; items[i].text; i++)
3957                 free((char *) items[i].text);
3958         free(items);
3959         return ok;
3962 static bool
3963 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3965         char buf[SIZEOF_STR * 4];
3966         const char *revlist_argv[] = {
3967                 "git", "log", "--no-color", "-1",
3968                         "--pretty=format:%P", id, "--", path, NULL
3969         };
3970         int parents;
3972         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3973             (parents = strlen(buf) / 40) < 0) {
3974                 report("Failed to get parent information");
3975                 return FALSE;
3977         } else if (parents == 0) {
3978                 if (path)
3979                         report("Path '%s' does not exist in the parent", path);
3980                 else
3981                         report("The selected commit has no parents");
3982                 return FALSE;
3983         }
3985         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3986                 return FALSE;
3988         string_copy_rev(rev, &buf[41 * parents]);
3989         return TRUE;
3992 /*
3993  * Pager backend
3994  */
3996 static bool
3997 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3999         char text[SIZEOF_STR];
4001         if (opt_line_number && draw_lineno(view, lineno))
4002                 return TRUE;
4004         string_expand(text, sizeof(text), line->data, opt_tab_size);
4005         draw_text(view, line->type, text, TRUE);
4006         return TRUE;
4009 static bool
4010 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4012         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4013         char ref[SIZEOF_STR];
4015         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4016                 return TRUE;
4018         /* This is the only fatal call, since it can "corrupt" the buffer. */
4019         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4020                 return FALSE;
4022         return TRUE;
4025 static void
4026 add_pager_refs(struct view *view, struct line *line)
4028         char buf[SIZEOF_STR];
4029         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4030         struct ref_list *list;
4031         size_t bufpos = 0, i;
4032         const char *sep = "Refs: ";
4033         bool is_tag = FALSE;
4035         assert(line->type == LINE_COMMIT);
4037         list = get_ref_list(commit_id);
4038         if (!list) {
4039                 if (view == VIEW(REQ_VIEW_DIFF))
4040                         goto try_add_describe_ref;
4041                 return;
4042         }
4044         for (i = 0; i < list->size; i++) {
4045                 struct ref *ref = list->refs[i];
4046                 const char *fmt = ref->tag    ? "%s[%s]" :
4047                                   ref->remote ? "%s<%s>" : "%s%s";
4049                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4050                         return;
4051                 sep = ", ";
4052                 if (ref->tag)
4053                         is_tag = TRUE;
4054         }
4056         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4057 try_add_describe_ref:
4058                 /* Add <tag>-g<commit_id> "fake" reference. */
4059                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4060                         return;
4061         }
4063         if (bufpos == 0)
4064                 return;
4066         add_line_text(view, buf, LINE_PP_REFS);
4069 static bool
4070 pager_read(struct view *view, char *data)
4072         struct line *line;
4074         if (!data)
4075                 return TRUE;
4077         line = add_line_text(view, data, get_line_type(data));
4078         if (!line)
4079                 return FALSE;
4081         if (line->type == LINE_COMMIT &&
4082             (view == VIEW(REQ_VIEW_DIFF) ||
4083              view == VIEW(REQ_VIEW_LOG)))
4084                 add_pager_refs(view, line);
4086         return TRUE;
4089 static enum request
4090 pager_request(struct view *view, enum request request, struct line *line)
4092         int split = 0;
4094         if (request != REQ_ENTER)
4095                 return request;
4097         if (line->type == LINE_COMMIT &&
4098            (view == VIEW(REQ_VIEW_LOG) ||
4099             view == VIEW(REQ_VIEW_PAGER))) {
4100                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4101                 split = 1;
4102         }
4104         /* Always scroll the view even if it was split. That way
4105          * you can use Enter to scroll through the log view and
4106          * split open each commit diff. */
4107         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4109         /* FIXME: A minor workaround. Scrolling the view will call report("")
4110          * but if we are scrolling a non-current view this won't properly
4111          * update the view title. */
4112         if (split)
4113                 update_view_title(view);
4115         return REQ_NONE;
4118 static bool
4119 pager_grep(struct view *view, struct line *line)
4121         const char *text[] = { line->data, NULL };
4123         return grep_text(view, text);
4126 static void
4127 pager_select(struct view *view, struct line *line)
4129         if (line->type == LINE_COMMIT) {
4130                 char *text = (char *)line->data + STRING_SIZE("commit ");
4132                 if (view != VIEW(REQ_VIEW_PAGER))
4133                         string_copy_rev(view->ref, text);
4134                 string_copy_rev(ref_commit, text);
4135         }
4138 static struct view_ops pager_ops = {
4139         "line",
4140         NULL,
4141         NULL,
4142         pager_read,
4143         pager_draw,
4144         pager_request,
4145         pager_grep,
4146         pager_select,
4147 };
4149 static const char *log_argv[SIZEOF_ARG] = {
4150         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4151 };
4153 static enum request
4154 log_request(struct view *view, enum request request, struct line *line)
4156         switch (request) {
4157         case REQ_REFRESH:
4158                 load_refs();
4159                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4160                 return REQ_NONE;
4161         default:
4162                 return pager_request(view, request, line);
4163         }
4166 static struct view_ops log_ops = {
4167         "line",
4168         log_argv,
4169         NULL,
4170         pager_read,
4171         pager_draw,
4172         log_request,
4173         pager_grep,
4174         pager_select,
4175 };
4177 static const char *diff_argv[SIZEOF_ARG] = {
4178         "git", "show", "--pretty=fuller", "--no-color", "--root",
4179                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4180 };
4182 static struct view_ops diff_ops = {
4183         "line",
4184         diff_argv,
4185         NULL,
4186         pager_read,
4187         pager_draw,
4188         pager_request,
4189         pager_grep,
4190         pager_select,
4191 };
4193 /*
4194  * Help backend
4195  */
4197 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4199 static bool
4200 help_open_keymap_title(struct view *view, enum keymap keymap)
4202         struct line *line;
4204         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4205                                help_keymap_hidden[keymap] ? '+' : '-',
4206                                enum_name(keymap_table[keymap]));
4207         if (line)
4208                 line->other = keymap;
4210         return help_keymap_hidden[keymap];
4213 static void
4214 help_open_keymap(struct view *view, enum keymap keymap)
4216         const char *group = NULL;
4217         char buf[SIZEOF_STR];
4218         size_t bufpos;
4219         bool add_title = TRUE;
4220         int i;
4222         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4223                 const char *key = NULL;
4225                 if (req_info[i].request == REQ_NONE)
4226                         continue;
4228                 if (!req_info[i].request) {
4229                         group = req_info[i].help;
4230                         continue;
4231                 }
4233                 key = get_keys(keymap, req_info[i].request, TRUE);
4234                 if (!key || !*key)
4235                         continue;
4237                 if (add_title && help_open_keymap_title(view, keymap))
4238                         return;
4239                 add_title = FALSE;
4241                 if (group) {
4242                         add_line_text(view, group, LINE_HELP_GROUP);
4243                         group = NULL;
4244                 }
4246                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4247                                 enum_name(req_info[i]), req_info[i].help);
4248         }
4250         group = "External commands:";
4252         for (i = 0; i < run_requests; i++) {
4253                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4254                 const char *key;
4255                 int argc;
4257                 if (!req || req->keymap != keymap)
4258                         continue;
4260                 key = get_key_name(req->key);
4261                 if (!*key)
4262                         key = "(no key defined)";
4264                 if (add_title && help_open_keymap_title(view, keymap))
4265                         return;
4266                 if (group) {
4267                         add_line_text(view, group, LINE_HELP_GROUP);
4268                         group = NULL;
4269                 }
4271                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4272                         if (!string_format_from(buf, &bufpos, "%s%s",
4273                                                 argc ? " " : "", req->argv[argc]))
4274                                 return;
4276                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4277         }
4280 static bool
4281 help_open(struct view *view)
4283         enum keymap keymap;
4285         reset_view(view);
4286         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4287         add_line_text(view, "", LINE_DEFAULT);
4289         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4290                 help_open_keymap(view, keymap);
4292         return TRUE;
4295 static enum request
4296 help_request(struct view *view, enum request request, struct line *line)
4298         switch (request) {
4299         case REQ_ENTER:
4300                 if (line->type == LINE_HELP_KEYMAP) {
4301                         help_keymap_hidden[line->other] =
4302                                 !help_keymap_hidden[line->other];
4303                         view->p_restore = TRUE;
4304                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4305                 }
4307                 return REQ_NONE;
4308         default:
4309                 return pager_request(view, request, line);
4310         }
4313 static struct view_ops help_ops = {
4314         "line",
4315         NULL,
4316         help_open,
4317         NULL,
4318         pager_draw,
4319         help_request,
4320         pager_grep,
4321         pager_select,
4322 };
4325 /*
4326  * Tree backend
4327  */
4329 struct tree_stack_entry {
4330         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4331         unsigned long lineno;           /* Line number to restore */
4332         char *name;                     /* Position of name in opt_path */
4333 };
4335 /* The top of the path stack. */
4336 static struct tree_stack_entry *tree_stack = NULL;
4337 unsigned long tree_lineno = 0;
4339 static void
4340 pop_tree_stack_entry(void)
4342         struct tree_stack_entry *entry = tree_stack;
4344         tree_lineno = entry->lineno;
4345         entry->name[0] = 0;
4346         tree_stack = entry->prev;
4347         free(entry);
4350 static void
4351 push_tree_stack_entry(const char *name, unsigned long lineno)
4353         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4354         size_t pathlen = strlen(opt_path);
4356         if (!entry)
4357                 return;
4359         entry->prev = tree_stack;
4360         entry->name = opt_path + pathlen;
4361         tree_stack = entry;
4363         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4364                 pop_tree_stack_entry();
4365                 return;
4366         }
4368         /* Move the current line to the first tree entry. */
4369         tree_lineno = 1;
4370         entry->lineno = lineno;
4373 /* Parse output from git-ls-tree(1):
4374  *
4375  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4376  */
4378 #define SIZEOF_TREE_ATTR \
4379         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4381 #define SIZEOF_TREE_MODE \
4382         STRING_SIZE("100644 ")
4384 #define TREE_ID_OFFSET \
4385         STRING_SIZE("100644 blob ")
4387 struct tree_entry {
4388         char id[SIZEOF_REV];
4389         mode_t mode;
4390         struct time time;               /* Date from the author ident. */
4391         const char *author;             /* Author of the commit. */
4392         char name[1];
4393 };
4395 static const char *
4396 tree_path(const struct line *line)
4398         return ((struct tree_entry *) line->data)->name;
4401 static int
4402 tree_compare_entry(const struct line *line1, const struct line *line2)
4404         if (line1->type != line2->type)
4405                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4406         return strcmp(tree_path(line1), tree_path(line2));
4409 static const enum sort_field tree_sort_fields[] = {
4410         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4411 };
4412 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4414 static int
4415 tree_compare(const void *l1, const void *l2)
4417         const struct line *line1 = (const struct line *) l1;
4418         const struct line *line2 = (const struct line *) l2;
4419         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4420         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4422         if (line1->type == LINE_TREE_HEAD)
4423                 return -1;
4424         if (line2->type == LINE_TREE_HEAD)
4425                 return 1;
4427         switch (get_sort_field(tree_sort_state)) {
4428         case ORDERBY_DATE:
4429                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4431         case ORDERBY_AUTHOR:
4432                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4434         case ORDERBY_NAME:
4435         default:
4436                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4437         }
4441 static struct line *
4442 tree_entry(struct view *view, enum line_type type, const char *path,
4443            const char *mode, const char *id)
4445         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4446         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4448         if (!entry || !line) {
4449                 free(entry);
4450                 return NULL;
4451         }
4453         strncpy(entry->name, path, strlen(path));
4454         if (mode)
4455                 entry->mode = strtoul(mode, NULL, 8);
4456         if (id)
4457                 string_copy_rev(entry->id, id);
4459         return line;
4462 static bool
4463 tree_read_date(struct view *view, char *text, bool *read_date)
4465         static const char *author_name;
4466         static struct time author_time;
4468         if (!text && *read_date) {
4469                 *read_date = FALSE;
4470                 return TRUE;
4472         } else if (!text) {
4473                 char *path = *opt_path ? opt_path : ".";
4474                 /* Find next entry to process */
4475                 const char *log_file[] = {
4476                         "git", "log", "--no-color", "--pretty=raw",
4477                                 "--cc", "--raw", view->id, "--", path, NULL
4478                 };
4479                 struct io io = {};
4481                 if (!view->lines) {
4482                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4483                         report("Tree is empty");
4484                         return TRUE;
4485                 }
4487                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4488                         report("Failed to load tree data");
4489                         return TRUE;
4490                 }
4492                 io_done(view->pipe);
4493                 view->io = io;
4494                 *read_date = TRUE;
4495                 return FALSE;
4497         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4498                 parse_author_line(text + STRING_SIZE("author "),
4499                                   &author_name, &author_time);
4501         } else if (*text == ':') {
4502                 char *pos;
4503                 size_t annotated = 1;
4504                 size_t i;
4506                 pos = strchr(text, '\t');
4507                 if (!pos)
4508                         return TRUE;
4509                 text = pos + 1;
4510                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4511                         text += strlen(opt_path);
4512                 pos = strchr(text, '/');
4513                 if (pos)
4514                         *pos = 0;
4516                 for (i = 1; i < view->lines; i++) {
4517                         struct line *line = &view->line[i];
4518                         struct tree_entry *entry = line->data;
4520                         annotated += !!entry->author;
4521                         if (entry->author || strcmp(entry->name, text))
4522                                 continue;
4524                         entry->author = author_name;
4525                         entry->time = author_time;
4526                         line->dirty = 1;
4527                         break;
4528                 }
4530                 if (annotated == view->lines)
4531                         io_kill(view->pipe);
4532         }
4533         return TRUE;
4536 static bool
4537 tree_read(struct view *view, char *text)
4539         static bool read_date = FALSE;
4540         struct tree_entry *data;
4541         struct line *entry, *line;
4542         enum line_type type;
4543         size_t textlen = text ? strlen(text) : 0;
4544         char *path = text + SIZEOF_TREE_ATTR;
4546         if (read_date || !text)
4547                 return tree_read_date(view, text, &read_date);
4549         if (textlen <= SIZEOF_TREE_ATTR)
4550                 return FALSE;
4551         if (view->lines == 0 &&
4552             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4553                 return FALSE;
4555         /* Strip the path part ... */
4556         if (*opt_path) {
4557                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4558                 size_t striplen = strlen(opt_path);
4560                 if (pathlen > striplen)
4561                         memmove(path, path + striplen,
4562                                 pathlen - striplen + 1);
4564                 /* Insert "link" to parent directory. */
4565                 if (view->lines == 1 &&
4566                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4567                         return FALSE;
4568         }
4570         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4571         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4572         if (!entry)
4573                 return FALSE;
4574         data = entry->data;
4576         /* Skip "Directory ..." and ".." line. */
4577         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4578                 if (tree_compare_entry(line, entry) <= 0)
4579                         continue;
4581                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4583                 line->data = data;
4584                 line->type = type;
4585                 for (; line <= entry; line++)
4586                         line->dirty = line->cleareol = 1;
4587                 return TRUE;
4588         }
4590         if (tree_lineno > view->lineno) {
4591                 view->lineno = tree_lineno;
4592                 tree_lineno = 0;
4593         }
4595         return TRUE;
4598 static bool
4599 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4601         struct tree_entry *entry = line->data;
4603         if (line->type == LINE_TREE_HEAD) {
4604                 if (draw_text(view, line->type, "Directory path /", TRUE))
4605                         return TRUE;
4606         } else {
4607                 if (draw_mode(view, entry->mode))
4608                         return TRUE;
4610                 if (opt_author && draw_author(view, entry->author))
4611                         return TRUE;
4613                 if (opt_date && draw_date(view, &entry->time))
4614                         return TRUE;
4615         }
4616         if (draw_text(view, line->type, entry->name, TRUE))
4617                 return TRUE;
4618         return TRUE;
4621 static void
4622 open_blob_editor()
4624         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4625         int fd = mkstemp(file);
4627         if (fd == -1)
4628                 report("Failed to create temporary file");
4629         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4630                 report("Failed to save blob data to file");
4631         else
4632                 open_editor(file);
4633         if (fd != -1)
4634                 unlink(file);
4637 static enum request
4638 tree_request(struct view *view, enum request request, struct line *line)
4640         enum open_flags flags;
4642         switch (request) {
4643         case REQ_VIEW_BLAME:
4644                 if (line->type != LINE_TREE_FILE) {
4645                         report("Blame only supported for files");
4646                         return REQ_NONE;
4647                 }
4649                 string_copy(opt_ref, view->vid);
4650                 return request;
4652         case REQ_EDIT:
4653                 if (line->type != LINE_TREE_FILE) {
4654                         report("Edit only supported for files");
4655                 } else if (!is_head_commit(view->vid)) {
4656                         open_blob_editor();
4657                 } else {
4658                         open_editor(opt_file);
4659                 }
4660                 return REQ_NONE;
4662         case REQ_TOGGLE_SORT_FIELD:
4663         case REQ_TOGGLE_SORT_ORDER:
4664                 sort_view(view, request, &tree_sort_state, tree_compare);
4665                 return REQ_NONE;
4667         case REQ_PARENT:
4668                 if (!*opt_path) {
4669                         /* quit view if at top of tree */
4670                         return REQ_VIEW_CLOSE;
4671                 }
4672                 /* fake 'cd  ..' */
4673                 line = &view->line[1];
4674                 break;
4676         case REQ_ENTER:
4677                 break;
4679         default:
4680                 return request;
4681         }
4683         /* Cleanup the stack if the tree view is at a different tree. */
4684         while (!*opt_path && tree_stack)
4685                 pop_tree_stack_entry();
4687         switch (line->type) {
4688         case LINE_TREE_DIR:
4689                 /* Depending on whether it is a subdirectory or parent link
4690                  * mangle the path buffer. */
4691                 if (line == &view->line[1] && *opt_path) {
4692                         pop_tree_stack_entry();
4694                 } else {
4695                         const char *basename = tree_path(line);
4697                         push_tree_stack_entry(basename, view->lineno);
4698                 }
4700                 /* Trees and subtrees share the same ID, so they are not not
4701                  * unique like blobs. */
4702                 flags = OPEN_RELOAD;
4703                 request = REQ_VIEW_TREE;
4704                 break;
4706         case LINE_TREE_FILE:
4707                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4708                 request = REQ_VIEW_BLOB;
4709                 break;
4711         default:
4712                 return REQ_NONE;
4713         }
4715         open_view(view, request, flags);
4716         if (request == REQ_VIEW_TREE)
4717                 view->lineno = tree_lineno;
4719         return REQ_NONE;
4722 static bool
4723 tree_grep(struct view *view, struct line *line)
4725         struct tree_entry *entry = line->data;
4726         const char *text[] = {
4727                 entry->name,
4728                 opt_author ? entry->author : "",
4729                 mkdate(&entry->time, opt_date),
4730                 NULL
4731         };
4733         return grep_text(view, text);
4736 static void
4737 tree_select(struct view *view, struct line *line)
4739         struct tree_entry *entry = line->data;
4741         if (line->type == LINE_TREE_FILE) {
4742                 string_copy_rev(ref_blob, entry->id);
4743                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4745         } else if (line->type != LINE_TREE_DIR) {
4746                 return;
4747         }
4749         string_copy_rev(view->ref, entry->id);
4752 static bool
4753 tree_prepare(struct view *view)
4755         if (view->lines == 0 && opt_prefix[0]) {
4756                 char *pos = opt_prefix;
4758                 while (pos && *pos) {
4759                         char *end = strchr(pos, '/');
4761                         if (end)
4762                                 *end = 0;
4763                         push_tree_stack_entry(pos, 0);
4764                         pos = end;
4765                         if (end) {
4766                                 *end = '/';
4767                                 pos++;
4768                         }
4769                 }
4771         } else if (strcmp(view->vid, view->id)) {
4772                 opt_path[0] = 0;
4773         }
4775         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4778 static const char *tree_argv[SIZEOF_ARG] = {
4779         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4780 };
4782 static struct view_ops tree_ops = {
4783         "file",
4784         tree_argv,
4785         NULL,
4786         tree_read,
4787         tree_draw,
4788         tree_request,
4789         tree_grep,
4790         tree_select,
4791         tree_prepare,
4792 };
4794 static bool
4795 blob_read(struct view *view, char *line)
4797         if (!line)
4798                 return TRUE;
4799         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4802 static enum request
4803 blob_request(struct view *view, enum request request, struct line *line)
4805         switch (request) {
4806         case REQ_EDIT:
4807                 open_blob_editor();
4808                 return REQ_NONE;
4809         default:
4810                 return pager_request(view, request, line);
4811         }
4814 static const char *blob_argv[SIZEOF_ARG] = {
4815         "git", "cat-file", "blob", "%(blob)", NULL
4816 };
4818 static struct view_ops blob_ops = {
4819         "line",
4820         blob_argv,
4821         NULL,
4822         blob_read,
4823         pager_draw,
4824         blob_request,
4825         pager_grep,
4826         pager_select,
4827 };
4829 /*
4830  * Blame backend
4831  *
4832  * Loading the blame view is a two phase job:
4833  *
4834  *  1. File content is read either using opt_file from the
4835  *     filesystem or using git-cat-file.
4836  *  2. Then blame information is incrementally added by
4837  *     reading output from git-blame.
4838  */
4840 static const char *blame_head_argv[] = {
4841         "git", "blame", "--incremental", "--", "%(file)", NULL
4842 };
4844 static const char *blame_ref_argv[] = {
4845         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4846 };
4848 static const char *blame_cat_file_argv[] = {
4849         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4850 };
4852 struct blame_commit {
4853         char id[SIZEOF_REV];            /* SHA1 ID. */
4854         char title[128];                /* First line of the commit message. */
4855         const char *author;             /* Author of the commit. */
4856         struct time time;               /* Date from the author ident. */
4857         char filename[128];             /* Name of file. */
4858         bool has_previous;              /* Was a "previous" line detected. */
4859 };
4861 struct blame {
4862         struct blame_commit *commit;
4863         unsigned long lineno;
4864         char text[1];
4865 };
4867 static bool
4868 blame_open(struct view *view)
4870         char path[SIZEOF_STR];
4872         if (!view->parent && *opt_prefix) {
4873                 string_copy(path, opt_file);
4874                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4875                         return FALSE;
4876         }
4878         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4879                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4880                         return FALSE;
4881         }
4883         setup_update(view, opt_file);
4884         string_format(view->ref, "%s ...", opt_file);
4886         return TRUE;
4889 static struct blame_commit *
4890 get_blame_commit(struct view *view, const char *id)
4892         size_t i;
4894         for (i = 0; i < view->lines; i++) {
4895                 struct blame *blame = view->line[i].data;
4897                 if (!blame->commit)
4898                         continue;
4900                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4901                         return blame->commit;
4902         }
4904         {
4905                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4907                 if (commit)
4908                         string_ncopy(commit->id, id, SIZEOF_REV);
4909                 return commit;
4910         }
4913 static bool
4914 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4916         const char *pos = *posref;
4918         *posref = NULL;
4919         pos = strchr(pos + 1, ' ');
4920         if (!pos || !isdigit(pos[1]))
4921                 return FALSE;
4922         *number = atoi(pos + 1);
4923         if (*number < min || *number > max)
4924                 return FALSE;
4926         *posref = pos;
4927         return TRUE;
4930 static struct blame_commit *
4931 parse_blame_commit(struct view *view, const char *text, int *blamed)
4933         struct blame_commit *commit;
4934         struct blame *blame;
4935         const char *pos = text + SIZEOF_REV - 2;
4936         size_t orig_lineno = 0;
4937         size_t lineno;
4938         size_t group;
4940         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4941                 return NULL;
4943         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4944             !parse_number(&pos, &lineno, 1, view->lines) ||
4945             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4946                 return NULL;
4948         commit = get_blame_commit(view, text);
4949         if (!commit)
4950                 return NULL;
4952         *blamed += group;
4953         while (group--) {
4954                 struct line *line = &view->line[lineno + group - 1];
4956                 blame = line->data;
4957                 blame->commit = commit;
4958                 blame->lineno = orig_lineno + group - 1;
4959                 line->dirty = 1;
4960         }
4962         return commit;
4965 static bool
4966 blame_read_file(struct view *view, const char *line, bool *read_file)
4968         if (!line) {
4969                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4970                 struct io io = {};
4972                 if (view->lines == 0 && !view->parent)
4973                         die("No blame exist for %s", view->vid);
4975                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4976                         report("Failed to load blame data");
4977                         return TRUE;
4978                 }
4980                 io_done(view->pipe);
4981                 view->io = io;
4982                 *read_file = FALSE;
4983                 return FALSE;
4985         } else {
4986                 size_t linelen = strlen(line);
4987                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4989                 if (!blame)
4990                         return FALSE;
4992                 blame->commit = NULL;
4993                 strncpy(blame->text, line, linelen);
4994                 blame->text[linelen] = 0;
4995                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4996         }
4999 static bool
5000 match_blame_header(const char *name, char **line)
5002         size_t namelen = strlen(name);
5003         bool matched = !strncmp(name, *line, namelen);
5005         if (matched)
5006                 *line += namelen;
5008         return matched;
5011 static bool
5012 blame_read(struct view *view, char *line)
5014         static struct blame_commit *commit = NULL;
5015         static int blamed = 0;
5016         static bool read_file = TRUE;
5018         if (read_file)
5019                 return blame_read_file(view, line, &read_file);
5021         if (!line) {
5022                 /* Reset all! */
5023                 commit = NULL;
5024                 blamed = 0;
5025                 read_file = TRUE;
5026                 string_format(view->ref, "%s", view->vid);
5027                 if (view_is_displayed(view)) {
5028                         update_view_title(view);
5029                         redraw_view_from(view, 0);
5030                 }
5031                 return TRUE;
5032         }
5034         if (!commit) {
5035                 commit = parse_blame_commit(view, line, &blamed);
5036                 string_format(view->ref, "%s %2d%%", view->vid,
5037                               view->lines ? blamed * 100 / view->lines : 0);
5039         } else if (match_blame_header("author ", &line)) {
5040                 commit->author = get_author(line);
5042         } else if (match_blame_header("author-time ", &line)) {
5043                 parse_timesec(&commit->time, line);
5045         } else if (match_blame_header("author-tz ", &line)) {
5046                 parse_timezone(&commit->time, line);
5048         } else if (match_blame_header("summary ", &line)) {
5049                 string_ncopy(commit->title, line, strlen(line));
5051         } else if (match_blame_header("previous ", &line)) {
5052                 commit->has_previous = TRUE;
5054         } else if (match_blame_header("filename ", &line)) {
5055                 string_ncopy(commit->filename, line, strlen(line));
5056                 commit = NULL;
5057         }
5059         return TRUE;
5062 static bool
5063 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5065         struct blame *blame = line->data;
5066         struct time *time = NULL;
5067         const char *id = NULL, *author = NULL;
5068         char text[SIZEOF_STR];
5070         if (blame->commit && *blame->commit->filename) {
5071                 id = blame->commit->id;
5072                 author = blame->commit->author;
5073                 time = &blame->commit->time;
5074         }
5076         if (opt_date && draw_date(view, time))
5077                 return TRUE;
5079         if (opt_author && draw_author(view, author))
5080                 return TRUE;
5082         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5083                 return TRUE;
5085         if (draw_lineno(view, lineno))
5086                 return TRUE;
5088         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5089         draw_text(view, LINE_DEFAULT, text, TRUE);
5090         return TRUE;
5093 static bool
5094 check_blame_commit(struct blame *blame, bool check_null_id)
5096         if (!blame->commit)
5097                 report("Commit data not loaded yet");
5098         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5099                 report("No commit exist for the selected line");
5100         else
5101                 return TRUE;
5102         return FALSE;
5105 static void
5106 setup_blame_parent_line(struct view *view, struct blame *blame)
5108         const char *diff_tree_argv[] = {
5109                 "git", "diff-tree", "-U0", blame->commit->id,
5110                         "--", blame->commit->filename, NULL
5111         };
5112         struct io io = {};
5113         int parent_lineno = -1;
5114         int blamed_lineno = -1;
5115         char *line;
5117         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5118                 return;
5120         while ((line = io_get(&io, '\n', TRUE))) {
5121                 if (*line == '@') {
5122                         char *pos = strchr(line, '+');
5124                         parent_lineno = atoi(line + 4);
5125                         if (pos)
5126                                 blamed_lineno = atoi(pos + 1);
5128                 } else if (*line == '+' && parent_lineno != -1) {
5129                         if (blame->lineno == blamed_lineno - 1 &&
5130                             !strcmp(blame->text, line + 1)) {
5131                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5132                                 break;
5133                         }
5134                         blamed_lineno++;
5135                 }
5136         }
5138         io_done(&io);
5141 static enum request
5142 blame_request(struct view *view, enum request request, struct line *line)
5144         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5145         struct blame *blame = line->data;
5147         switch (request) {
5148         case REQ_VIEW_BLAME:
5149                 if (check_blame_commit(blame, TRUE)) {
5150                         string_copy(opt_ref, blame->commit->id);
5151                         string_copy(opt_file, blame->commit->filename);
5152                         if (blame->lineno)
5153                                 view->lineno = blame->lineno;
5154                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5155                 }
5156                 break;
5158         case REQ_PARENT:
5159                 if (check_blame_commit(blame, TRUE) &&
5160                     select_commit_parent(blame->commit->id, opt_ref,
5161                                          blame->commit->filename)) {
5162                         string_copy(opt_file, blame->commit->filename);
5163                         setup_blame_parent_line(view, blame);
5164                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5165                 }
5166                 break;
5168         case REQ_ENTER:
5169                 if (!check_blame_commit(blame, FALSE))
5170                         break;
5172                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5173                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5174                         break;
5176                 if (!strcmp(blame->commit->id, NULL_ID)) {
5177                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5178                         const char *diff_index_argv[] = {
5179                                 "git", "diff-index", "--root", "--patch-with-stat",
5180                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5181                         };
5183                         if (!blame->commit->has_previous) {
5184                                 diff_index_argv[1] = "diff";
5185                                 diff_index_argv[2] = "--no-color";
5186                                 diff_index_argv[6] = "--";
5187                                 diff_index_argv[7] = "/dev/null";
5188                         }
5190                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5191                                 report("Failed to allocate diff command");
5192                                 break;
5193                         }
5194                         flags |= OPEN_PREPARED;
5195                 }
5197                 open_view(view, REQ_VIEW_DIFF, flags);
5198                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5199                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5200                 break;
5202         default:
5203                 return request;
5204         }
5206         return REQ_NONE;
5209 static bool
5210 blame_grep(struct view *view, struct line *line)
5212         struct blame *blame = line->data;
5213         struct blame_commit *commit = blame->commit;
5214         const char *text[] = {
5215                 blame->text,
5216                 commit ? commit->title : "",
5217                 commit ? commit->id : "",
5218                 commit && opt_author ? commit->author : "",
5219                 commit ? mkdate(&commit->time, opt_date) : "",
5220                 NULL
5221         };
5223         return grep_text(view, text);
5226 static void
5227 blame_select(struct view *view, struct line *line)
5229         struct blame *blame = line->data;
5230         struct blame_commit *commit = blame->commit;
5232         if (!commit)
5233                 return;
5235         if (!strcmp(commit->id, NULL_ID))
5236                 string_ncopy(ref_commit, "HEAD", 4);
5237         else
5238                 string_copy_rev(ref_commit, commit->id);
5241 static struct view_ops blame_ops = {
5242         "line",
5243         NULL,
5244         blame_open,
5245         blame_read,
5246         blame_draw,
5247         blame_request,
5248         blame_grep,
5249         blame_select,
5250 };
5252 /*
5253  * Branch backend
5254  */
5256 struct branch {
5257         const char *author;             /* Author of the last commit. */
5258         struct time time;               /* Date of the last activity. */
5259         const struct ref *ref;          /* Name and commit ID information. */
5260 };
5262 static const struct ref branch_all;
5264 static const enum sort_field branch_sort_fields[] = {
5265         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5266 };
5267 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5269 static int
5270 branch_compare(const void *l1, const void *l2)
5272         const struct branch *branch1 = ((const struct line *) l1)->data;
5273         const struct branch *branch2 = ((const struct line *) l2)->data;
5275         switch (get_sort_field(branch_sort_state)) {
5276         case ORDERBY_DATE:
5277                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5279         case ORDERBY_AUTHOR:
5280                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5282         case ORDERBY_NAME:
5283         default:
5284                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5285         }
5288 static bool
5289 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5291         struct branch *branch = line->data;
5292         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5294         if (opt_date && draw_date(view, &branch->time))
5295                 return TRUE;
5297         if (opt_author && draw_author(view, branch->author))
5298                 return TRUE;
5300         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5301         return TRUE;
5304 static enum request
5305 branch_request(struct view *view, enum request request, struct line *line)
5307         struct branch *branch = line->data;
5309         switch (request) {
5310         case REQ_REFRESH:
5311                 load_refs();
5312                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5313                 return REQ_NONE;
5315         case REQ_TOGGLE_SORT_FIELD:
5316         case REQ_TOGGLE_SORT_ORDER:
5317                 sort_view(view, request, &branch_sort_state, branch_compare);
5318                 return REQ_NONE;
5320         case REQ_ENTER:
5321                 if (branch->ref == &branch_all) {
5322                         const char *all_branches_argv[] = {
5323                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5324                                       "--topo-order", "--all", NULL
5325                         };
5326                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5328                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5329                                 report("Failed to load view of all branches");
5330                                 return REQ_NONE;
5331                         }
5332                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5333                 } else {
5334                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5335                 }
5336                 return REQ_NONE;
5338         default:
5339                 return request;
5340         }
5343 static bool
5344 branch_read(struct view *view, char *line)
5346         static char id[SIZEOF_REV];
5347         struct branch *reference;
5348         size_t i;
5350         if (!line)
5351                 return TRUE;
5353         switch (get_line_type(line)) {
5354         case LINE_COMMIT:
5355                 string_copy_rev(id, line + STRING_SIZE("commit "));
5356                 return TRUE;
5358         case LINE_AUTHOR:
5359                 for (i = 0, reference = NULL; i < view->lines; i++) {
5360                         struct branch *branch = view->line[i].data;
5362                         if (strcmp(branch->ref->id, id))
5363                                 continue;
5365                         view->line[i].dirty = TRUE;
5366                         if (reference) {
5367                                 branch->author = reference->author;
5368                                 branch->time = reference->time;
5369                                 continue;
5370                         }
5372                         parse_author_line(line + STRING_SIZE("author "),
5373                                           &branch->author, &branch->time);
5374                         reference = branch;
5375                 }
5376                 return TRUE;
5378         default:
5379                 return TRUE;
5380         }
5384 static bool
5385 branch_open_visitor(void *data, const struct ref *ref)
5387         struct view *view = data;
5388         struct branch *branch;
5390         if (ref->tag || ref->ltag || ref->remote)
5391                 return TRUE;
5393         branch = calloc(1, sizeof(*branch));
5394         if (!branch)
5395                 return FALSE;
5397         branch->ref = ref;
5398         return !!add_line_data(view, branch, LINE_DEFAULT);
5401 static bool
5402 branch_open(struct view *view)
5404         const char *branch_log[] = {
5405                 "git", "log", "--no-color", "--pretty=raw",
5406                         "--simplify-by-decoration", "--all", NULL
5407         };
5409         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5410                 report("Failed to load branch data");
5411                 return TRUE;
5412         }
5414         setup_update(view, view->id);
5415         branch_open_visitor(view, &branch_all);
5416         foreach_ref(branch_open_visitor, view);
5417         view->p_restore = TRUE;
5419         return TRUE;
5422 static bool
5423 branch_grep(struct view *view, struct line *line)
5425         struct branch *branch = line->data;
5426         const char *text[] = {
5427                 branch->ref->name,
5428                 branch->author,
5429                 NULL
5430         };
5432         return grep_text(view, text);
5435 static void
5436 branch_select(struct view *view, struct line *line)
5438         struct branch *branch = line->data;
5440         string_copy_rev(view->ref, branch->ref->id);
5441         string_copy_rev(ref_commit, branch->ref->id);
5442         string_copy_rev(ref_head, branch->ref->id);
5443         string_copy_rev(ref_branch, branch->ref->name);
5446 static struct view_ops branch_ops = {
5447         "branch",
5448         NULL,
5449         branch_open,
5450         branch_read,
5451         branch_draw,
5452         branch_request,
5453         branch_grep,
5454         branch_select,
5455 };
5457 /*
5458  * Status backend
5459  */
5461 struct status {
5462         char status;
5463         struct {
5464                 mode_t mode;
5465                 char rev[SIZEOF_REV];
5466                 char name[SIZEOF_STR];
5467         } old;
5468         struct {
5469                 mode_t mode;
5470                 char rev[SIZEOF_REV];
5471                 char name[SIZEOF_STR];
5472         } new;
5473 };
5475 static char status_onbranch[SIZEOF_STR];
5476 static struct status stage_status;
5477 static enum line_type stage_line_type;
5478 static size_t stage_chunks;
5479 static int *stage_chunk;
5481 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5483 /* This should work even for the "On branch" line. */
5484 static inline bool
5485 status_has_none(struct view *view, struct line *line)
5487         return line < view->line + view->lines && !line[1].data;
5490 /* Get fields from the diff line:
5491  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5492  */
5493 static inline bool
5494 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5496         const char *old_mode = buf +  1;
5497         const char *new_mode = buf +  8;
5498         const char *old_rev  = buf + 15;
5499         const char *new_rev  = buf + 56;
5500         const char *status   = buf + 97;
5502         if (bufsize < 98 ||
5503             old_mode[-1] != ':' ||
5504             new_mode[-1] != ' ' ||
5505             old_rev[-1]  != ' ' ||
5506             new_rev[-1]  != ' ' ||
5507             status[-1]   != ' ')
5508                 return FALSE;
5510         file->status = *status;
5512         string_copy_rev(file->old.rev, old_rev);
5513         string_copy_rev(file->new.rev, new_rev);
5515         file->old.mode = strtoul(old_mode, NULL, 8);
5516         file->new.mode = strtoul(new_mode, NULL, 8);
5518         file->old.name[0] = file->new.name[0] = 0;
5520         return TRUE;
5523 static bool
5524 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5526         struct status *unmerged = NULL;
5527         char *buf;
5528         struct io io = {};
5530         if (!io_run(&io, argv, opt_cdup, IO_RD))
5531                 return FALSE;
5533         add_line_data(view, NULL, type);
5535         while ((buf = io_get(&io, 0, TRUE))) {
5536                 struct status *file = unmerged;
5538                 if (!file) {
5539                         file = calloc(1, sizeof(*file));
5540                         if (!file || !add_line_data(view, file, type))
5541                                 goto error_out;
5542                 }
5544                 /* Parse diff info part. */
5545                 if (status) {
5546                         file->status = status;
5547                         if (status == 'A')
5548                                 string_copy(file->old.rev, NULL_ID);
5550                 } else if (!file->status || file == unmerged) {
5551                         if (!status_get_diff(file, buf, strlen(buf)))
5552                                 goto error_out;
5554                         buf = io_get(&io, 0, TRUE);
5555                         if (!buf)
5556                                 break;
5558                         /* Collapse all modified entries that follow an
5559                          * associated unmerged entry. */
5560                         if (unmerged == file) {
5561                                 unmerged->status = 'U';
5562                                 unmerged = NULL;
5563                         } else if (file->status == 'U') {
5564                                 unmerged = file;
5565                         }
5566                 }
5568                 /* Grab the old name for rename/copy. */
5569                 if (!*file->old.name &&
5570                     (file->status == 'R' || file->status == 'C')) {
5571                         string_ncopy(file->old.name, buf, strlen(buf));
5573                         buf = io_get(&io, 0, TRUE);
5574                         if (!buf)
5575                                 break;
5576                 }
5578                 /* git-ls-files just delivers a NUL separated list of
5579                  * file names similar to the second half of the
5580                  * git-diff-* output. */
5581                 string_ncopy(file->new.name, buf, strlen(buf));
5582                 if (!*file->old.name)
5583                         string_copy(file->old.name, file->new.name);
5584                 file = NULL;
5585         }
5587         if (io_error(&io)) {
5588 error_out:
5589                 io_done(&io);
5590                 return FALSE;
5591         }
5593         if (!view->line[view->lines - 1].data)
5594                 add_line_data(view, NULL, LINE_STAT_NONE);
5596         io_done(&io);
5597         return TRUE;
5600 /* Don't show unmerged entries in the staged section. */
5601 static const char *status_diff_index_argv[] = {
5602         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5603                              "--cached", "-M", "HEAD", NULL
5604 };
5606 static const char *status_diff_files_argv[] = {
5607         "git", "diff-files", "-z", NULL
5608 };
5610 static const char *status_list_other_argv[] = {
5611         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5612 };
5614 static const char *status_list_no_head_argv[] = {
5615         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5616 };
5618 static const char *update_index_argv[] = {
5619         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5620 };
5622 /* Restore the previous line number to stay in the context or select a
5623  * line with something that can be updated. */
5624 static void
5625 status_restore(struct view *view)
5627         if (view->p_lineno >= view->lines)
5628                 view->p_lineno = view->lines - 1;
5629         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5630                 view->p_lineno++;
5631         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5632                 view->p_lineno--;
5634         /* If the above fails, always skip the "On branch" line. */
5635         if (view->p_lineno < view->lines)
5636                 view->lineno = view->p_lineno;
5637         else
5638                 view->lineno = 1;
5640         if (view->lineno < view->offset)
5641                 view->offset = view->lineno;
5642         else if (view->offset + view->height <= view->lineno)
5643                 view->offset = view->lineno - view->height + 1;
5645         view->p_restore = FALSE;
5648 static void
5649 status_update_onbranch(void)
5651         static const char *paths[][2] = {
5652                 { "rebase-apply/rebasing",      "Rebasing" },
5653                 { "rebase-apply/applying",      "Applying mailbox" },
5654                 { "rebase-apply/",              "Rebasing mailbox" },
5655                 { "rebase-merge/interactive",   "Interactive rebase" },
5656                 { "rebase-merge/",              "Rebase merge" },
5657                 { "MERGE_HEAD",                 "Merging" },
5658                 { "BISECT_LOG",                 "Bisecting" },
5659                 { "HEAD",                       "On branch" },
5660         };
5661         char buf[SIZEOF_STR];
5662         struct stat stat;
5663         int i;
5665         if (is_initial_commit()) {
5666                 string_copy(status_onbranch, "Initial commit");
5667                 return;
5668         }
5670         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5671                 char *head = opt_head;
5673                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5674                     lstat(buf, &stat) < 0)
5675                         continue;
5677                 if (!*opt_head) {
5678                         struct io io = {};
5680                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5681                             io_read_buf(&io, buf, sizeof(buf))) {
5682                                 head = buf;
5683                                 if (!prefixcmp(head, "refs/heads/"))
5684                                         head += STRING_SIZE("refs/heads/");
5685                         }
5686                 }
5688                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5689                         string_copy(status_onbranch, opt_head);
5690                 return;
5691         }
5693         string_copy(status_onbranch, "Not currently on any branch");
5696 /* First parse staged info using git-diff-index(1), then parse unstaged
5697  * info using git-diff-files(1), and finally untracked files using
5698  * git-ls-files(1). */
5699 static bool
5700 status_open(struct view *view)
5702         reset_view(view);
5704         add_line_data(view, NULL, LINE_STAT_HEAD);
5705         status_update_onbranch();
5707         io_run_bg(update_index_argv);
5709         if (is_initial_commit()) {
5710                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5711                         return FALSE;
5712         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5713                 return FALSE;
5714         }
5716         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5717             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5718                 return FALSE;
5720         /* Restore the exact position or use the specialized restore
5721          * mode? */
5722         if (!view->p_restore)
5723                 status_restore(view);
5724         return TRUE;
5727 static bool
5728 status_draw(struct view *view, struct line *line, unsigned int lineno)
5730         struct status *status = line->data;
5731         enum line_type type;
5732         const char *text;
5734         if (!status) {
5735                 switch (line->type) {
5736                 case LINE_STAT_STAGED:
5737                         type = LINE_STAT_SECTION;
5738                         text = "Changes to be committed:";
5739                         break;
5741                 case LINE_STAT_UNSTAGED:
5742                         type = LINE_STAT_SECTION;
5743                         text = "Changed but not updated:";
5744                         break;
5746                 case LINE_STAT_UNTRACKED:
5747                         type = LINE_STAT_SECTION;
5748                         text = "Untracked files:";
5749                         break;
5751                 case LINE_STAT_NONE:
5752                         type = LINE_DEFAULT;
5753                         text = "  (no files)";
5754                         break;
5756                 case LINE_STAT_HEAD:
5757                         type = LINE_STAT_HEAD;
5758                         text = status_onbranch;
5759                         break;
5761                 default:
5762                         return FALSE;
5763                 }
5764         } else {
5765                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5767                 buf[0] = status->status;
5768                 if (draw_text(view, line->type, buf, TRUE))
5769                         return TRUE;
5770                 type = LINE_DEFAULT;
5771                 text = status->new.name;
5772         }
5774         draw_text(view, type, text, TRUE);
5775         return TRUE;
5778 static enum request
5779 status_load_error(struct view *view, struct view *stage, const char *path)
5781         if (displayed_views() == 2 || display[current_view] != view)
5782                 maximize_view(view);
5783         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5784         return REQ_NONE;
5787 static enum request
5788 status_enter(struct view *view, struct line *line)
5790         struct status *status = line->data;
5791         const char *oldpath = status ? status->old.name : NULL;
5792         /* Diffs for unmerged entries are empty when passing the new
5793          * path, so leave it empty. */
5794         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5795         const char *info;
5796         enum open_flags split;
5797         struct view *stage = VIEW(REQ_VIEW_STAGE);
5799         if (line->type == LINE_STAT_NONE ||
5800             (!status && line[1].type == LINE_STAT_NONE)) {
5801                 report("No file to diff");
5802                 return REQ_NONE;
5803         }
5805         switch (line->type) {
5806         case LINE_STAT_STAGED:
5807                 if (is_initial_commit()) {
5808                         const char *no_head_diff_argv[] = {
5809                                 "git", "diff", "--no-color", "--patch-with-stat",
5810                                         "--", "/dev/null", newpath, NULL
5811                         };
5813                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5814                                 return status_load_error(view, stage, newpath);
5815                 } else {
5816                         const char *index_show_argv[] = {
5817                                 "git", "diff-index", "--root", "--patch-with-stat",
5818                                         "-C", "-M", "--cached", "HEAD", "--",
5819                                         oldpath, newpath, NULL
5820                         };
5822                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5823                                 return status_load_error(view, stage, newpath);
5824                 }
5826                 if (status)
5827                         info = "Staged changes to %s";
5828                 else
5829                         info = "Staged changes";
5830                 break;
5832         case LINE_STAT_UNSTAGED:
5833         {
5834                 const char *files_show_argv[] = {
5835                         "git", "diff-files", "--root", "--patch-with-stat",
5836                                 "-C", "-M", "--", oldpath, newpath, NULL
5837                 };
5839                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5840                         return status_load_error(view, stage, newpath);
5841                 if (status)
5842                         info = "Unstaged changes to %s";
5843                 else
5844                         info = "Unstaged changes";
5845                 break;
5846         }
5847         case LINE_STAT_UNTRACKED:
5848                 if (!newpath) {
5849                         report("No file to show");
5850                         return REQ_NONE;
5851                 }
5853                 if (!suffixcmp(status->new.name, -1, "/")) {
5854                         report("Cannot display a directory");
5855                         return REQ_NONE;
5856                 }
5858                 if (!prepare_update_file(stage, newpath))
5859                         return status_load_error(view, stage, newpath);
5860                 info = "Untracked file %s";
5861                 break;
5863         case LINE_STAT_HEAD:
5864                 return REQ_NONE;
5866         default:
5867                 die("line type %d not handled in switch", line->type);
5868         }
5870         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5871         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5872         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5873                 if (status) {
5874                         stage_status = *status;
5875                 } else {
5876                         memset(&stage_status, 0, sizeof(stage_status));
5877                 }
5879                 stage_line_type = line->type;
5880                 stage_chunks = 0;
5881                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5882         }
5884         return REQ_NONE;
5887 static bool
5888 status_exists(struct status *status, enum line_type type)
5890         struct view *view = VIEW(REQ_VIEW_STATUS);
5891         unsigned long lineno;
5893         for (lineno = 0; lineno < view->lines; lineno++) {
5894                 struct line *line = &view->line[lineno];
5895                 struct status *pos = line->data;
5897                 if (line->type != type)
5898                         continue;
5899                 if (!pos && (!status || !status->status) && line[1].data) {
5900                         select_view_line(view, lineno);
5901                         return TRUE;
5902                 }
5903                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5904                         select_view_line(view, lineno);
5905                         return TRUE;
5906                 }
5907         }
5909         return FALSE;
5913 static bool
5914 status_update_prepare(struct io *io, enum line_type type)
5916         const char *staged_argv[] = {
5917                 "git", "update-index", "-z", "--index-info", NULL
5918         };
5919         const char *others_argv[] = {
5920                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5921         };
5923         switch (type) {
5924         case LINE_STAT_STAGED:
5925                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5927         case LINE_STAT_UNSTAGED:
5928         case LINE_STAT_UNTRACKED:
5929                 return io_run(io, others_argv, opt_cdup, IO_WR);
5931         default:
5932                 die("line type %d not handled in switch", type);
5933                 return FALSE;
5934         }
5937 static bool
5938 status_update_write(struct io *io, struct status *status, enum line_type type)
5940         char buf[SIZEOF_STR];
5941         size_t bufsize = 0;
5943         switch (type) {
5944         case LINE_STAT_STAGED:
5945                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5946                                         status->old.mode,
5947                                         status->old.rev,
5948                                         status->old.name, 0))
5949                         return FALSE;
5950                 break;
5952         case LINE_STAT_UNSTAGED:
5953         case LINE_STAT_UNTRACKED:
5954                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5955                         return FALSE;
5956                 break;
5958         default:
5959                 die("line type %d not handled in switch", type);
5960         }
5962         return io_write(io, buf, bufsize);
5965 static bool
5966 status_update_file(struct status *status, enum line_type type)
5968         struct io io = {};
5969         bool result;
5971         if (!status_update_prepare(&io, type))
5972                 return FALSE;
5974         result = status_update_write(&io, status, type);
5975         return io_done(&io) && result;
5978 static bool
5979 status_update_files(struct view *view, struct line *line)
5981         char buf[sizeof(view->ref)];
5982         struct io io = {};
5983         bool result = TRUE;
5984         struct line *pos = view->line + view->lines;
5985         int files = 0;
5986         int file, done;
5987         int cursor_y = -1, cursor_x = -1;
5989         if (!status_update_prepare(&io, line->type))
5990                 return FALSE;
5992         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5993                 files++;
5995         string_copy(buf, view->ref);
5996         getsyx(cursor_y, cursor_x);
5997         for (file = 0, done = 5; result && file < files; line++, file++) {
5998                 int almost_done = file * 100 / files;
6000                 if (almost_done > done) {
6001                         done = almost_done;
6002                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6003                                       file, files, done);
6004                         update_view_title(view);
6005                         setsyx(cursor_y, cursor_x);
6006                         doupdate();
6007                 }
6008                 result = status_update_write(&io, line->data, line->type);
6009         }
6010         string_copy(view->ref, buf);
6012         return io_done(&io) && result;
6015 static bool
6016 status_update(struct view *view)
6018         struct line *line = &view->line[view->lineno];
6020         assert(view->lines);
6022         if (!line->data) {
6023                 /* This should work even for the "On branch" line. */
6024                 if (line < view->line + view->lines && !line[1].data) {
6025                         report("Nothing to update");
6026                         return FALSE;
6027                 }
6029                 if (!status_update_files(view, line + 1)) {
6030                         report("Failed to update file status");
6031                         return FALSE;
6032                 }
6034         } else if (!status_update_file(line->data, line->type)) {
6035                 report("Failed to update file status");
6036                 return FALSE;
6037         }
6039         return TRUE;
6042 static bool
6043 status_revert(struct status *status, enum line_type type, bool has_none)
6045         if (!status || type != LINE_STAT_UNSTAGED) {
6046                 if (type == LINE_STAT_STAGED) {
6047                         report("Cannot revert changes to staged files");
6048                 } else if (type == LINE_STAT_UNTRACKED) {
6049                         report("Cannot revert changes to untracked files");
6050                 } else if (has_none) {
6051                         report("Nothing to revert");
6052                 } else {
6053                         report("Cannot revert changes to multiple files");
6054                 }
6056         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6057                 char mode[10] = "100644";
6058                 const char *reset_argv[] = {
6059                         "git", "update-index", "--cacheinfo", mode,
6060                                 status->old.rev, status->old.name, NULL
6061                 };
6062                 const char *checkout_argv[] = {
6063                         "git", "checkout", "--", status->old.name, NULL
6064                 };
6066                 if (status->status == 'U') {
6067                         string_format(mode, "%5o", status->old.mode);
6069                         if (status->old.mode == 0 && status->new.mode == 0) {
6070                                 reset_argv[2] = "--force-remove";
6071                                 reset_argv[3] = status->old.name;
6072                                 reset_argv[4] = NULL;
6073                         }
6075                         if (!io_run_fg(reset_argv, opt_cdup))
6076                                 return FALSE;
6077                         if (status->old.mode == 0 && status->new.mode == 0)
6078                                 return TRUE;
6079                 }
6081                 return io_run_fg(checkout_argv, opt_cdup);
6082         }
6084         return FALSE;
6087 static enum request
6088 status_request(struct view *view, enum request request, struct line *line)
6090         struct status *status = line->data;
6092         switch (request) {
6093         case REQ_STATUS_UPDATE:
6094                 if (!status_update(view))
6095                         return REQ_NONE;
6096                 break;
6098         case REQ_STATUS_REVERT:
6099                 if (!status_revert(status, line->type, status_has_none(view, line)))
6100                         return REQ_NONE;
6101                 break;
6103         case REQ_STATUS_MERGE:
6104                 if (!status || status->status != 'U') {
6105                         report("Merging only possible for files with unmerged status ('U').");
6106                         return REQ_NONE;
6107                 }
6108                 open_mergetool(status->new.name);
6109                 break;
6111         case REQ_EDIT:
6112                 if (!status)
6113                         return request;
6114                 if (status->status == 'D') {
6115                         report("File has been deleted.");
6116                         return REQ_NONE;
6117                 }
6119                 open_editor(status->new.name);
6120                 break;
6122         case REQ_VIEW_BLAME:
6123                 if (status)
6124                         opt_ref[0] = 0;
6125                 return request;
6127         case REQ_ENTER:
6128                 /* After returning the status view has been split to
6129                  * show the stage view. No further reloading is
6130                  * necessary. */
6131                 return status_enter(view, line);
6133         case REQ_REFRESH:
6134                 /* Simply reload the view. */
6135                 break;
6137         default:
6138                 return request;
6139         }
6141         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6143         return REQ_NONE;
6146 static void
6147 status_select(struct view *view, struct line *line)
6149         struct status *status = line->data;
6150         char file[SIZEOF_STR] = "all files";
6151         const char *text;
6152         const char *key;
6154         if (status && !string_format(file, "'%s'", status->new.name))
6155                 return;
6157         if (!status && line[1].type == LINE_STAT_NONE)
6158                 line++;
6160         switch (line->type) {
6161         case LINE_STAT_STAGED:
6162                 text = "Press %s to unstage %s for commit";
6163                 break;
6165         case LINE_STAT_UNSTAGED:
6166                 text = "Press %s to stage %s for commit";
6167                 break;
6169         case LINE_STAT_UNTRACKED:
6170                 text = "Press %s to stage %s for addition";
6171                 break;
6173         case LINE_STAT_HEAD:
6174         case LINE_STAT_NONE:
6175                 text = "Nothing to update";
6176                 break;
6178         default:
6179                 die("line type %d not handled in switch", line->type);
6180         }
6182         if (status && status->status == 'U') {
6183                 text = "Press %s to resolve conflict in %s";
6184                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6186         } else {
6187                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6188         }
6190         string_format(view->ref, text, key, file);
6191         if (status)
6192                 string_copy(opt_file, status->new.name);
6195 static bool
6196 status_grep(struct view *view, struct line *line)
6198         struct status *status = line->data;
6200         if (status) {
6201                 const char buf[2] = { status->status, 0 };
6202                 const char *text[] = { status->new.name, buf, NULL };
6204                 return grep_text(view, text);
6205         }
6207         return FALSE;
6210 static struct view_ops status_ops = {
6211         "file",
6212         NULL,
6213         status_open,
6214         NULL,
6215         status_draw,
6216         status_request,
6217         status_grep,
6218         status_select,
6219 };
6222 static bool
6223 stage_diff_write(struct io *io, struct line *line, struct line *end)
6225         while (line < end) {
6226                 if (!io_write(io, line->data, strlen(line->data)) ||
6227                     !io_write(io, "\n", 1))
6228                         return FALSE;
6229                 line++;
6230                 if (line->type == LINE_DIFF_CHUNK ||
6231                     line->type == LINE_DIFF_HEADER)
6232                         break;
6233         }
6235         return TRUE;
6238 static struct line *
6239 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6241         for (; view->line < line; line--)
6242                 if (line->type == type)
6243                         return line;
6245         return NULL;
6248 static bool
6249 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6251         const char *apply_argv[SIZEOF_ARG] = {
6252                 "git", "apply", "--whitespace=nowarn", NULL
6253         };
6254         struct line *diff_hdr;
6255         struct io io = {};
6256         int argc = 3;
6258         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6259         if (!diff_hdr)
6260                 return FALSE;
6262         if (!revert)
6263                 apply_argv[argc++] = "--cached";
6264         if (revert || stage_line_type == LINE_STAT_STAGED)
6265                 apply_argv[argc++] = "-R";
6266         apply_argv[argc++] = "-";
6267         apply_argv[argc++] = NULL;
6268         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6269                 return FALSE;
6271         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6272             !stage_diff_write(&io, chunk, view->line + view->lines))
6273                 chunk = NULL;
6275         io_done(&io);
6276         io_run_bg(update_index_argv);
6278         return chunk ? TRUE : FALSE;
6281 static bool
6282 stage_update(struct view *view, struct line *line)
6284         struct line *chunk = NULL;
6286         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6287                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6289         if (chunk) {
6290                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6291                         report("Failed to apply chunk");
6292                         return FALSE;
6293                 }
6295         } else if (!stage_status.status) {
6296                 view = VIEW(REQ_VIEW_STATUS);
6298                 for (line = view->line; line < view->line + view->lines; line++)
6299                         if (line->type == stage_line_type)
6300                                 break;
6302                 if (!status_update_files(view, line + 1)) {
6303                         report("Failed to update files");
6304                         return FALSE;
6305                 }
6307         } else if (!status_update_file(&stage_status, stage_line_type)) {
6308                 report("Failed to update file");
6309                 return FALSE;
6310         }
6312         return TRUE;
6315 static bool
6316 stage_revert(struct view *view, struct line *line)
6318         struct line *chunk = NULL;
6320         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6321                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6323         if (chunk) {
6324                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6325                         return FALSE;
6327                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6328                         report("Failed to revert chunk");
6329                         return FALSE;
6330                 }
6331                 return TRUE;
6333         } else {
6334                 return status_revert(stage_status.status ? &stage_status : NULL,
6335                                      stage_line_type, FALSE);
6336         }
6340 static void
6341 stage_next(struct view *view, struct line *line)
6343         int i;
6345         if (!stage_chunks) {
6346                 for (line = view->line; line < view->line + view->lines; line++) {
6347                         if (line->type != LINE_DIFF_CHUNK)
6348                                 continue;
6350                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6351                                 report("Allocation failure");
6352                                 return;
6353                         }
6355                         stage_chunk[stage_chunks++] = line - view->line;
6356                 }
6357         }
6359         for (i = 0; i < stage_chunks; i++) {
6360                 if (stage_chunk[i] > view->lineno) {
6361                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6362                         report("Chunk %d of %d", i + 1, stage_chunks);
6363                         return;
6364                 }
6365         }
6367         report("No next chunk found");
6370 static enum request
6371 stage_request(struct view *view, enum request request, struct line *line)
6373         switch (request) {
6374         case REQ_STATUS_UPDATE:
6375                 if (!stage_update(view, line))
6376                         return REQ_NONE;
6377                 break;
6379         case REQ_STATUS_REVERT:
6380                 if (!stage_revert(view, line))
6381                         return REQ_NONE;
6382                 break;
6384         case REQ_STAGE_NEXT:
6385                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6386                         report("File is untracked; press %s to add",
6387                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6388                         return REQ_NONE;
6389                 }
6390                 stage_next(view, line);
6391                 return REQ_NONE;
6393         case REQ_EDIT:
6394                 if (!stage_status.new.name[0])
6395                         return request;
6396                 if (stage_status.status == 'D') {
6397                         report("File has been deleted.");
6398                         return REQ_NONE;
6399                 }
6401                 open_editor(stage_status.new.name);
6402                 break;
6404         case REQ_REFRESH:
6405                 /* Reload everything ... */
6406                 break;
6408         case REQ_VIEW_BLAME:
6409                 if (stage_status.new.name[0]) {
6410                         string_copy(opt_file, stage_status.new.name);
6411                         opt_ref[0] = 0;
6412                 }
6413                 return request;
6415         case REQ_ENTER:
6416                 return pager_request(view, request, line);
6418         default:
6419                 return request;
6420         }
6422         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6423         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6425         /* Check whether the staged entry still exists, and close the
6426          * stage view if it doesn't. */
6427         if (!status_exists(&stage_status, stage_line_type)) {
6428                 status_restore(VIEW(REQ_VIEW_STATUS));
6429                 return REQ_VIEW_CLOSE;
6430         }
6432         if (stage_line_type == LINE_STAT_UNTRACKED) {
6433                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6434                         report("Cannot display a directory");
6435                         return REQ_NONE;
6436                 }
6438                 if (!prepare_update_file(view, stage_status.new.name)) {
6439                         report("Failed to open file: %s", strerror(errno));
6440                         return REQ_NONE;
6441                 }
6442         }
6443         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6445         return REQ_NONE;
6448 static struct view_ops stage_ops = {
6449         "line",
6450         NULL,
6451         NULL,
6452         pager_read,
6453         pager_draw,
6454         stage_request,
6455         pager_grep,
6456         pager_select,
6457 };
6460 /*
6461  * Revision graph
6462  */
6464 struct commit {
6465         char id[SIZEOF_REV];            /* SHA1 ID. */
6466         char title[128];                /* First line of the commit message. */
6467         const char *author;             /* Author of the commit. */
6468         struct time time;               /* Date from the author ident. */
6469         struct ref_list *refs;          /* Repository references. */
6470         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6471         size_t graph_size;              /* The width of the graph array. */
6472         bool has_parents;               /* Rewritten --parents seen. */
6473 };
6475 /* Size of rev graph with no  "padding" columns */
6476 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6478 struct rev_graph {
6479         struct rev_graph *prev, *next, *parents;
6480         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6481         size_t size;
6482         struct commit *commit;
6483         size_t pos;
6484         unsigned int boundary:1;
6485 };
6487 /* Parents of the commit being visualized. */
6488 static struct rev_graph graph_parents[4];
6490 /* The current stack of revisions on the graph. */
6491 static struct rev_graph graph_stacks[4] = {
6492         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6493         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6494         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6495         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6496 };
6498 static inline bool
6499 graph_parent_is_merge(struct rev_graph *graph)
6501         return graph->parents->size > 1;
6504 static inline void
6505 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6507         struct commit *commit = graph->commit;
6509         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6510                 commit->graph[commit->graph_size++] = symbol;
6513 static void
6514 clear_rev_graph(struct rev_graph *graph)
6516         graph->boundary = 0;
6517         graph->size = graph->pos = 0;
6518         graph->commit = NULL;
6519         memset(graph->parents, 0, sizeof(*graph->parents));
6522 static void
6523 done_rev_graph(struct rev_graph *graph)
6525         if (graph_parent_is_merge(graph) &&
6526             graph->pos < graph->size - 1 &&
6527             graph->next->size == graph->size + graph->parents->size - 1) {
6528                 size_t i = graph->pos + graph->parents->size - 1;
6530                 graph->commit->graph_size = i * 2;
6531                 while (i < graph->next->size - 1) {
6532                         append_to_rev_graph(graph, ' ');
6533                         append_to_rev_graph(graph, '\\');
6534                         i++;
6535                 }
6536         }
6538         clear_rev_graph(graph);
6541 static void
6542 push_rev_graph(struct rev_graph *graph, const char *parent)
6544         int i;
6546         /* "Collapse" duplicate parents lines.
6547          *
6548          * FIXME: This needs to also update update the drawn graph but
6549          * for now it just serves as a method for pruning graph lines. */
6550         for (i = 0; i < graph->size; i++)
6551                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6552                         return;
6554         if (graph->size < SIZEOF_REVITEMS) {
6555                 string_copy_rev(graph->rev[graph->size++], parent);
6556         }
6559 static chtype
6560 get_rev_graph_symbol(struct rev_graph *graph)
6562         chtype symbol;
6564         if (graph->boundary)
6565                 symbol = REVGRAPH_BOUND;
6566         else if (graph->parents->size == 0)
6567                 symbol = REVGRAPH_INIT;
6568         else if (graph_parent_is_merge(graph))
6569                 symbol = REVGRAPH_MERGE;
6570         else if (graph->pos >= graph->size)
6571                 symbol = REVGRAPH_BRANCH;
6572         else
6573                 symbol = REVGRAPH_COMMIT;
6575         return symbol;
6578 static void
6579 draw_rev_graph(struct rev_graph *graph)
6581         struct rev_filler {
6582                 chtype separator, line;
6583         };
6584         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6585         static struct rev_filler fillers[] = {
6586                 { ' ',  '|' },
6587                 { '`',  '.' },
6588                 { '\'', ' ' },
6589                 { '/',  ' ' },
6590         };
6591         chtype symbol = get_rev_graph_symbol(graph);
6592         struct rev_filler *filler;
6593         size_t i;
6595         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6596         filler = &fillers[DEFAULT];
6598         for (i = 0; i < graph->pos; i++) {
6599                 append_to_rev_graph(graph, filler->line);
6600                 if (graph_parent_is_merge(graph->prev) &&
6601                     graph->prev->pos == i)
6602                         filler = &fillers[RSHARP];
6604                 append_to_rev_graph(graph, filler->separator);
6605         }
6607         /* Place the symbol for this revision. */
6608         append_to_rev_graph(graph, symbol);
6610         if (graph->prev->size > graph->size)
6611                 filler = &fillers[RDIAG];
6612         else
6613                 filler = &fillers[DEFAULT];
6615         i++;
6617         for (; i < graph->size; i++) {
6618                 append_to_rev_graph(graph, filler->separator);
6619                 append_to_rev_graph(graph, filler->line);
6620                 if (graph_parent_is_merge(graph->prev) &&
6621                     i < graph->prev->pos + graph->parents->size)
6622                         filler = &fillers[RSHARP];
6623                 if (graph->prev->size > graph->size)
6624                         filler = &fillers[LDIAG];
6625         }
6627         if (graph->prev->size > graph->size) {
6628                 append_to_rev_graph(graph, filler->separator);
6629                 if (filler->line != ' ')
6630                         append_to_rev_graph(graph, filler->line);
6631         }
6634 /* Prepare the next rev graph */
6635 static void
6636 prepare_rev_graph(struct rev_graph *graph)
6638         size_t i;
6640         /* First, traverse all lines of revisions up to the active one. */
6641         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6642                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6643                         break;
6645                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6646         }
6648         /* Interleave the new revision parent(s). */
6649         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6650                 push_rev_graph(graph->next, graph->parents->rev[i]);
6652         /* Lastly, put any remaining revisions. */
6653         for (i = graph->pos + 1; i < graph->size; i++)
6654                 push_rev_graph(graph->next, graph->rev[i]);
6657 static void
6658 update_rev_graph(struct view *view, struct rev_graph *graph)
6660         /* If this is the finalizing update ... */
6661         if (graph->commit)
6662                 prepare_rev_graph(graph);
6664         /* Graph visualization needs a one rev look-ahead,
6665          * so the first update doesn't visualize anything. */
6666         if (!graph->prev->commit)
6667                 return;
6669         if (view->lines > 2)
6670                 view->line[view->lines - 3].dirty = 1;
6671         if (view->lines > 1)
6672                 view->line[view->lines - 2].dirty = 1;
6673         draw_rev_graph(graph->prev);
6674         done_rev_graph(graph->prev->prev);
6678 /*
6679  * Main view backend
6680  */
6682 static const char *main_argv[SIZEOF_ARG] = {
6683         "git", "log", "--no-color", "--pretty=raw", "--parents",
6684                       "--topo-order", "%(head)", NULL
6685 };
6687 static bool
6688 main_draw(struct view *view, struct line *line, unsigned int lineno)
6690         struct commit *commit = line->data;
6692         if (!commit->author)
6693                 return FALSE;
6695         if (opt_date && draw_date(view, &commit->time))
6696                 return TRUE;
6698         if (opt_author && draw_author(view, commit->author))
6699                 return TRUE;
6701         if (opt_rev_graph && commit->graph_size &&
6702             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6703                 return TRUE;
6705         if (opt_show_refs && commit->refs) {
6706                 size_t i;
6708                 for (i = 0; i < commit->refs->size; i++) {
6709                         struct ref *ref = commit->refs->refs[i];
6710                         enum line_type type;
6712                         if (ref->head)
6713                                 type = LINE_MAIN_HEAD;
6714                         else if (ref->ltag)
6715                                 type = LINE_MAIN_LOCAL_TAG;
6716                         else if (ref->tag)
6717                                 type = LINE_MAIN_TAG;
6718                         else if (ref->tracked)
6719                                 type = LINE_MAIN_TRACKED;
6720                         else if (ref->remote)
6721                                 type = LINE_MAIN_REMOTE;
6722                         else
6723                                 type = LINE_MAIN_REF;
6725                         if (draw_text(view, type, "[", TRUE) ||
6726                             draw_text(view, type, ref->name, TRUE) ||
6727                             draw_text(view, type, "]", TRUE))
6728                                 return TRUE;
6730                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6731                                 return TRUE;
6732                 }
6733         }
6735         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6736         return TRUE;
6739 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6740 static bool
6741 main_read(struct view *view, char *line)
6743         static struct rev_graph *graph = graph_stacks;
6744         enum line_type type;
6745         struct commit *commit;
6747         if (!line) {
6748                 int i;
6750                 if (!view->lines && !view->parent)
6751                         die("No revisions match the given arguments.");
6752                 if (view->lines > 0) {
6753                         commit = view->line[view->lines - 1].data;
6754                         view->line[view->lines - 1].dirty = 1;
6755                         if (!commit->author) {
6756                                 view->lines--;
6757                                 free(commit);
6758                                 graph->commit = NULL;
6759                         }
6760                 }
6761                 update_rev_graph(view, graph);
6763                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6764                         clear_rev_graph(&graph_stacks[i]);
6765                 return TRUE;
6766         }
6768         type = get_line_type(line);
6769         if (type == LINE_COMMIT) {
6770                 commit = calloc(1, sizeof(struct commit));
6771                 if (!commit)
6772                         return FALSE;
6774                 line += STRING_SIZE("commit ");
6775                 if (*line == '-') {
6776                         graph->boundary = 1;
6777                         line++;
6778                 }
6780                 string_copy_rev(commit->id, line);
6781                 commit->refs = get_ref_list(commit->id);
6782                 graph->commit = commit;
6783                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6785                 while ((line = strchr(line, ' '))) {
6786                         line++;
6787                         push_rev_graph(graph->parents, line);
6788                         commit->has_parents = TRUE;
6789                 }
6790                 return TRUE;
6791         }
6793         if (!view->lines)
6794                 return TRUE;
6795         commit = view->line[view->lines - 1].data;
6797         switch (type) {
6798         case LINE_PARENT:
6799                 if (commit->has_parents)
6800                         break;
6801                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6802                 break;
6804         case LINE_AUTHOR:
6805                 parse_author_line(line + STRING_SIZE("author "),
6806                                   &commit->author, &commit->time);
6807                 update_rev_graph(view, graph);
6808                 graph = graph->next;
6809                 break;
6811         default:
6812                 /* Fill in the commit title if it has not already been set. */
6813                 if (commit->title[0])
6814                         break;
6816                 /* Require titles to start with a non-space character at the
6817                  * offset used by git log. */
6818                 if (strncmp(line, "    ", 4))
6819                         break;
6820                 line += 4;
6821                 /* Well, if the title starts with a whitespace character,
6822                  * try to be forgiving.  Otherwise we end up with no title. */
6823                 while (isspace(*line))
6824                         line++;
6825                 if (*line == '\0')
6826                         break;
6827                 /* FIXME: More graceful handling of titles; append "..." to
6828                  * shortened titles, etc. */
6830                 string_expand(commit->title, sizeof(commit->title), line, 1);
6831                 view->line[view->lines - 1].dirty = 1;
6832         }
6834         return TRUE;
6837 static enum request
6838 main_request(struct view *view, enum request request, struct line *line)
6840         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6842         switch (request) {
6843         case REQ_ENTER:
6844                 open_view(view, REQ_VIEW_DIFF, flags);
6845                 break;
6846         case REQ_REFRESH:
6847                 load_refs();
6848                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6849                 break;
6850         default:
6851                 return request;
6852         }
6854         return REQ_NONE;
6857 static bool
6858 grep_refs(struct ref_list *list, regex_t *regex)
6860         regmatch_t pmatch;
6861         size_t i;
6863         if (!opt_show_refs || !list)
6864                 return FALSE;
6866         for (i = 0; i < list->size; i++) {
6867                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6868                         return TRUE;
6869         }
6871         return FALSE;
6874 static bool
6875 main_grep(struct view *view, struct line *line)
6877         struct commit *commit = line->data;
6878         const char *text[] = {
6879                 commit->title,
6880                 opt_author ? commit->author : "",
6881                 mkdate(&commit->time, opt_date),
6882                 NULL
6883         };
6885         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6888 static void
6889 main_select(struct view *view, struct line *line)
6891         struct commit *commit = line->data;
6893         string_copy_rev(view->ref, commit->id);
6894         string_copy_rev(ref_commit, view->ref);
6897 static struct view_ops main_ops = {
6898         "commit",
6899         main_argv,
6900         NULL,
6901         main_read,
6902         main_draw,
6903         main_request,
6904         main_grep,
6905         main_select,
6906 };
6909 /*
6910  * Status management
6911  */
6913 /* Whether or not the curses interface has been initialized. */
6914 static bool cursed = FALSE;
6916 /* Terminal hacks and workarounds. */
6917 static bool use_scroll_redrawwin;
6918 static bool use_scroll_status_wclear;
6920 /* The status window is used for polling keystrokes. */
6921 static WINDOW *status_win;
6923 /* Reading from the prompt? */
6924 static bool input_mode = FALSE;
6926 static bool status_empty = FALSE;
6928 /* Update status and title window. */
6929 static void
6930 report(const char *msg, ...)
6932         struct view *view = display[current_view];
6934         if (input_mode)
6935                 return;
6937         if (!view) {
6938                 char buf[SIZEOF_STR];
6939                 va_list args;
6941                 va_start(args, msg);
6942                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6943                         buf[sizeof(buf) - 1] = 0;
6944                         buf[sizeof(buf) - 2] = '.';
6945                         buf[sizeof(buf) - 3] = '.';
6946                         buf[sizeof(buf) - 4] = '.';
6947                 }
6948                 va_end(args);
6949                 die("%s", buf);
6950         }
6952         if (!status_empty || *msg) {
6953                 va_list args;
6955                 va_start(args, msg);
6957                 wmove(status_win, 0, 0);
6958                 if (view->has_scrolled && use_scroll_status_wclear)
6959                         wclear(status_win);
6960                 if (*msg) {
6961                         vwprintw(status_win, msg, args);
6962                         status_empty = FALSE;
6963                 } else {
6964                         status_empty = TRUE;
6965                 }
6966                 wclrtoeol(status_win);
6967                 wnoutrefresh(status_win);
6969                 va_end(args);
6970         }
6972         update_view_title(view);
6975 static void
6976 init_display(void)
6978         const char *term;
6979         int x, y;
6981         /* Initialize the curses library */
6982         if (isatty(STDIN_FILENO)) {
6983                 cursed = !!initscr();
6984                 opt_tty = stdin;
6985         } else {
6986                 /* Leave stdin and stdout alone when acting as a pager. */
6987                 opt_tty = fopen("/dev/tty", "r+");
6988                 if (!opt_tty)
6989                         die("Failed to open /dev/tty");
6990                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6991         }
6993         if (!cursed)
6994                 die("Failed to initialize curses");
6996         nonl();         /* Disable conversion and detect newlines from input. */
6997         cbreak();       /* Take input chars one at a time, no wait for \n */
6998         noecho();       /* Don't echo input */
6999         leaveok(stdscr, FALSE);
7001         if (has_colors())
7002                 init_colors();
7004         getmaxyx(stdscr, y, x);
7005         status_win = newwin(1, 0, y - 1, 0);
7006         if (!status_win)
7007                 die("Failed to create status window");
7009         /* Enable keyboard mapping */
7010         keypad(status_win, TRUE);
7011         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7013         TABSIZE = opt_tab_size;
7015         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7016         if (term && !strcmp(term, "gnome-terminal")) {
7017                 /* In the gnome-terminal-emulator, the message from
7018                  * scrolling up one line when impossible followed by
7019                  * scrolling down one line causes corruption of the
7020                  * status line. This is fixed by calling wclear. */
7021                 use_scroll_status_wclear = TRUE;
7022                 use_scroll_redrawwin = FALSE;
7024         } else if (term && !strcmp(term, "xrvt-xpm")) {
7025                 /* No problems with full optimizations in xrvt-(unicode)
7026                  * and aterm. */
7027                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7029         } else {
7030                 /* When scrolling in (u)xterm the last line in the
7031                  * scrolling direction will update slowly. */
7032                 use_scroll_redrawwin = TRUE;
7033                 use_scroll_status_wclear = FALSE;
7034         }
7037 static int
7038 get_input(int prompt_position)
7040         struct view *view;
7041         int i, key, cursor_y, cursor_x;
7042         bool loading = FALSE;
7044         if (prompt_position)
7045                 input_mode = TRUE;
7047         while (TRUE) {
7048                 foreach_view (view, i) {
7049                         update_view(view);
7050                         if (view_is_displayed(view) && view->has_scrolled &&
7051                             use_scroll_redrawwin)
7052                                 redrawwin(view->win);
7053                         view->has_scrolled = FALSE;
7054                         if (view->pipe)
7055                                 loading = TRUE;
7056                 }
7058                 /* Update the cursor position. */
7059                 if (prompt_position) {
7060                         getbegyx(status_win, cursor_y, cursor_x);
7061                         cursor_x = prompt_position;
7062                 } else {
7063                         view = display[current_view];
7064                         getbegyx(view->win, cursor_y, cursor_x);
7065                         cursor_x = view->width - 1;
7066                         cursor_y += view->lineno - view->offset;
7067                 }
7068                 setsyx(cursor_y, cursor_x);
7070                 /* Refresh, accept single keystroke of input */
7071                 doupdate();
7072                 nodelay(status_win, loading);
7073                 key = wgetch(status_win);
7075                 /* wgetch() with nodelay() enabled returns ERR when
7076                  * there's no input. */
7077                 if (key == ERR) {
7079                 } else if (key == KEY_RESIZE) {
7080                         int height, width;
7082                         getmaxyx(stdscr, height, width);
7084                         wresize(status_win, 1, width);
7085                         mvwin(status_win, height - 1, 0);
7086                         wnoutrefresh(status_win);
7087                         resize_display();
7088                         redraw_display(TRUE);
7090                 } else {
7091                         input_mode = FALSE;
7092                         return key;
7093                 }
7094         }
7097 static char *
7098 prompt_input(const char *prompt, input_handler handler, void *data)
7100         enum input_status status = INPUT_OK;
7101         static char buf[SIZEOF_STR];
7102         size_t pos = 0;
7104         buf[pos] = 0;
7106         while (status == INPUT_OK || status == INPUT_SKIP) {
7107                 int key;
7109                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7110                 wclrtoeol(status_win);
7112                 key = get_input(pos + 1);
7113                 switch (key) {
7114                 case KEY_RETURN:
7115                 case KEY_ENTER:
7116                 case '\n':
7117                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7118                         break;
7120                 case KEY_BACKSPACE:
7121                         if (pos > 0)
7122                                 buf[--pos] = 0;
7123                         else
7124                                 status = INPUT_CANCEL;
7125                         break;
7127                 case KEY_ESC:
7128                         status = INPUT_CANCEL;
7129                         break;
7131                 default:
7132                         if (pos >= sizeof(buf)) {
7133                                 report("Input string too long");
7134                                 return NULL;
7135                         }
7137                         status = handler(data, buf, key);
7138                         if (status == INPUT_OK)
7139                                 buf[pos++] = (char) key;
7140                 }
7141         }
7143         /* Clear the status window */
7144         status_empty = FALSE;
7145         report("");
7147         if (status == INPUT_CANCEL)
7148                 return NULL;
7150         buf[pos++] = 0;
7152         return buf;
7155 static enum input_status
7156 prompt_yesno_handler(void *data, char *buf, int c)
7158         if (c == 'y' || c == 'Y')
7159                 return INPUT_STOP;
7160         if (c == 'n' || c == 'N')
7161                 return INPUT_CANCEL;
7162         return INPUT_SKIP;
7165 static bool
7166 prompt_yesno(const char *prompt)
7168         char prompt2[SIZEOF_STR];
7170         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7171                 return FALSE;
7173         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7176 static enum input_status
7177 read_prompt_handler(void *data, char *buf, int c)
7179         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7182 static char *
7183 read_prompt(const char *prompt)
7185         return prompt_input(prompt, read_prompt_handler, NULL);
7188 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7190         enum input_status status = INPUT_OK;
7191         int size = 0;
7193         while (items[size].text)
7194                 size++;
7196         while (status == INPUT_OK) {
7197                 const struct menu_item *item = &items[*selected];
7198                 int key;
7199                 int i;
7201                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7202                           prompt, *selected + 1, size);
7203                 if (item->hotkey)
7204                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7205                 wprintw(status_win, "%s", item->text);
7206                 wclrtoeol(status_win);
7208                 key = get_input(COLS - 1);
7209                 switch (key) {
7210                 case KEY_RETURN:
7211                 case KEY_ENTER:
7212                 case '\n':
7213                         status = INPUT_STOP;
7214                         break;
7216                 case KEY_LEFT:
7217                 case KEY_UP:
7218                         *selected = *selected - 1;
7219                         if (*selected < 0)
7220                                 *selected = size - 1;
7221                         break;
7223                 case KEY_RIGHT:
7224                 case KEY_DOWN:
7225                         *selected = (*selected + 1) % size;
7226                         break;
7228                 case KEY_ESC:
7229                         status = INPUT_CANCEL;
7230                         break;
7232                 default:
7233                         for (i = 0; items[i].text; i++)
7234                                 if (items[i].hotkey == key) {
7235                                         *selected = i;
7236                                         status = INPUT_STOP;
7237                                         break;
7238                                 }
7239                 }
7240         }
7242         /* Clear the status window */
7243         status_empty = FALSE;
7244         report("");
7246         return status != INPUT_CANCEL;
7249 /*
7250  * Repository properties
7251  */
7253 static struct ref **refs = NULL;
7254 static size_t refs_size = 0;
7255 static struct ref *refs_head = NULL;
7257 static struct ref_list **ref_lists = NULL;
7258 static size_t ref_lists_size = 0;
7260 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7261 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7262 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7264 static int
7265 compare_refs(const void *ref1_, const void *ref2_)
7267         const struct ref *ref1 = *(const struct ref **)ref1_;
7268         const struct ref *ref2 = *(const struct ref **)ref2_;
7270         if (ref1->tag != ref2->tag)
7271                 return ref2->tag - ref1->tag;
7272         if (ref1->ltag != ref2->ltag)
7273                 return ref2->ltag - ref2->ltag;
7274         if (ref1->head != ref2->head)
7275                 return ref2->head - ref1->head;
7276         if (ref1->tracked != ref2->tracked)
7277                 return ref2->tracked - ref1->tracked;
7278         if (ref1->remote != ref2->remote)
7279                 return ref2->remote - ref1->remote;
7280         return strcmp(ref1->name, ref2->name);
7283 static void
7284 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7286         size_t i;
7288         for (i = 0; i < refs_size; i++)
7289                 if (!visitor(data, refs[i]))
7290                         break;
7293 static struct ref *
7294 get_ref_head()
7296         return refs_head;
7299 static struct ref_list *
7300 get_ref_list(const char *id)
7302         struct ref_list *list;
7303         size_t i;
7305         for (i = 0; i < ref_lists_size; i++)
7306                 if (!strcmp(id, ref_lists[i]->id))
7307                         return ref_lists[i];
7309         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7310                 return NULL;
7311         list = calloc(1, sizeof(*list));
7312         if (!list)
7313                 return NULL;
7315         for (i = 0; i < refs_size; i++) {
7316                 if (!strcmp(id, refs[i]->id) &&
7317                     realloc_refs_list(&list->refs, list->size, 1))
7318                         list->refs[list->size++] = refs[i];
7319         }
7321         if (!list->refs) {
7322                 free(list);
7323                 return NULL;
7324         }
7326         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7327         ref_lists[ref_lists_size++] = list;
7328         return list;
7331 static int
7332 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7334         struct ref *ref = NULL;
7335         bool tag = FALSE;
7336         bool ltag = FALSE;
7337         bool remote = FALSE;
7338         bool tracked = FALSE;
7339         bool head = FALSE;
7340         int from = 0, to = refs_size - 1;
7342         if (!prefixcmp(name, "refs/tags/")) {
7343                 if (!suffixcmp(name, namelen, "^{}")) {
7344                         namelen -= 3;
7345                         name[namelen] = 0;
7346                 } else {
7347                         ltag = TRUE;
7348                 }
7350                 tag = TRUE;
7351                 namelen -= STRING_SIZE("refs/tags/");
7352                 name    += STRING_SIZE("refs/tags/");
7354         } else if (!prefixcmp(name, "refs/remotes/")) {
7355                 remote = TRUE;
7356                 namelen -= STRING_SIZE("refs/remotes/");
7357                 name    += STRING_SIZE("refs/remotes/");
7358                 tracked  = !strcmp(opt_remote, name);
7360         } else if (!prefixcmp(name, "refs/heads/")) {
7361                 namelen -= STRING_SIZE("refs/heads/");
7362                 name    += STRING_SIZE("refs/heads/");
7363                 if (!strncmp(opt_head, name, namelen))
7364                         return OK;
7366         } else if (!strcmp(name, "HEAD")) {
7367                 head     = TRUE;
7368                 if (*opt_head) {
7369                         namelen  = strlen(opt_head);
7370                         name     = opt_head;
7371                 }
7372         }
7374         /* If we are reloading or it's an annotated tag, replace the
7375          * previous SHA1 with the resolved commit id; relies on the fact
7376          * git-ls-remote lists the commit id of an annotated tag right
7377          * before the commit id it points to. */
7378         while (from <= to) {
7379                 size_t pos = (to + from) / 2;
7380                 int cmp = strcmp(name, refs[pos]->name);
7382                 if (!cmp) {
7383                         ref = refs[pos];
7384                         break;
7385                 }
7387                 if (cmp < 0)
7388                         to = pos - 1;
7389                 else
7390                         from = pos + 1;
7391         }
7393         if (!ref) {
7394                 if (!realloc_refs(&refs, refs_size, 1))
7395                         return ERR;
7396                 ref = calloc(1, sizeof(*ref) + namelen);
7397                 if (!ref)
7398                         return ERR;
7399                 memmove(refs + from + 1, refs + from,
7400                         (refs_size - from) * sizeof(*refs));
7401                 refs[from] = ref;
7402                 strncpy(ref->name, name, namelen);
7403                 refs_size++;
7404         }
7406         ref->head = head;
7407         ref->tag = tag;
7408         ref->ltag = ltag;
7409         ref->remote = remote;
7410         ref->tracked = tracked;
7411         string_copy_rev(ref->id, id);
7413         if (head)
7414                 refs_head = ref;
7415         return OK;
7418 static int
7419 load_refs(void)
7421         const char *head_argv[] = {
7422                 "git", "symbolic-ref", "HEAD", NULL
7423         };
7424         static const char *ls_remote_argv[SIZEOF_ARG] = {
7425                 "git", "ls-remote", opt_git_dir, NULL
7426         };
7427         static bool init = FALSE;
7428         size_t i;
7430         if (!init) {
7431                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7432                         die("TIG_LS_REMOTE contains too many arguments");
7433                 init = TRUE;
7434         }
7436         if (!*opt_git_dir)
7437                 return OK;
7439         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7440             !prefixcmp(opt_head, "refs/heads/")) {
7441                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7443                 memmove(opt_head, offset, strlen(offset) + 1);
7444         }
7446         refs_head = NULL;
7447         for (i = 0; i < refs_size; i++)
7448                 refs[i]->id[0] = 0;
7450         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7451                 return ERR;
7453         /* Update the ref lists to reflect changes. */
7454         for (i = 0; i < ref_lists_size; i++) {
7455                 struct ref_list *list = ref_lists[i];
7456                 size_t old, new;
7458                 for (old = new = 0; old < list->size; old++)
7459                         if (!strcmp(list->id, list->refs[old]->id))
7460                                 list->refs[new++] = list->refs[old];
7461                 list->size = new;
7462         }
7464         return OK;
7467 static void
7468 set_remote_branch(const char *name, const char *value, size_t valuelen)
7470         if (!strcmp(name, ".remote")) {
7471                 string_ncopy(opt_remote, value, valuelen);
7473         } else if (*opt_remote && !strcmp(name, ".merge")) {
7474                 size_t from = strlen(opt_remote);
7476                 if (!prefixcmp(value, "refs/heads/"))
7477                         value += STRING_SIZE("refs/heads/");
7479                 if (!string_format_from(opt_remote, &from, "/%s", value))
7480                         opt_remote[0] = 0;
7481         }
7484 static void
7485 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7487         const char *argv[SIZEOF_ARG] = { name, "=" };
7488         int argc = 1 + (cmd == option_set_command);
7489         int error = ERR;
7491         if (!argv_from_string(argv, &argc, value))
7492                 config_msg = "Too many option arguments";
7493         else
7494                 error = cmd(argc, argv);
7496         if (error == ERR)
7497                 warn("Option 'tig.%s': %s", name, config_msg);
7500 static bool
7501 set_environment_variable(const char *name, const char *value)
7503         size_t len = strlen(name) + 1 + strlen(value) + 1;
7504         char *env = malloc(len);
7506         if (env &&
7507             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7508             putenv(env) == 0)
7509                 return TRUE;
7510         free(env);
7511         return FALSE;
7514 static void
7515 set_work_tree(const char *value)
7517         char cwd[SIZEOF_STR];
7519         if (!getcwd(cwd, sizeof(cwd)))
7520                 die("Failed to get cwd path: %s", strerror(errno));
7521         if (chdir(opt_git_dir) < 0)
7522                 die("Failed to chdir(%s): %s", strerror(errno));
7523         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7524                 die("Failed to get git path: %s", strerror(errno));
7525         if (chdir(cwd) < 0)
7526                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7527         if (chdir(value) < 0)
7528                 die("Failed to chdir(%s): %s", value, strerror(errno));
7529         if (!getcwd(cwd, sizeof(cwd)))
7530                 die("Failed to get cwd path: %s", strerror(errno));
7531         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7532                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7533         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7534                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7535         opt_is_inside_work_tree = TRUE;
7538 static int
7539 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7541         if (!strcmp(name, "i18n.commitencoding"))
7542                 string_ncopy(opt_encoding, value, valuelen);
7544         else if (!strcmp(name, "core.editor"))
7545                 string_ncopy(opt_editor, value, valuelen);
7547         else if (!strcmp(name, "core.worktree"))
7548                 set_work_tree(value);
7550         else if (!prefixcmp(name, "tig.color."))
7551                 set_repo_config_option(name + 10, value, option_color_command);
7553         else if (!prefixcmp(name, "tig.bind."))
7554                 set_repo_config_option(name + 9, value, option_bind_command);
7556         else if (!prefixcmp(name, "tig."))
7557                 set_repo_config_option(name + 4, value, option_set_command);
7559         else if (*opt_head && !prefixcmp(name, "branch.") &&
7560                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7561                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7563         return OK;
7566 static int
7567 load_git_config(void)
7569         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7571         return io_run_load(config_list_argv, "=", read_repo_config_option);
7574 static int
7575 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7577         if (!opt_git_dir[0]) {
7578                 string_ncopy(opt_git_dir, name, namelen);
7580         } else if (opt_is_inside_work_tree == -1) {
7581                 /* This can be 3 different values depending on the
7582                  * version of git being used. If git-rev-parse does not
7583                  * understand --is-inside-work-tree it will simply echo
7584                  * the option else either "true" or "false" is printed.
7585                  * Default to true for the unknown case. */
7586                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7588         } else if (*name == '.') {
7589                 string_ncopy(opt_cdup, name, namelen);
7591         } else {
7592                 string_ncopy(opt_prefix, name, namelen);
7593         }
7595         return OK;
7598 static int
7599 load_repo_info(void)
7601         const char *rev_parse_argv[] = {
7602                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7603                         "--show-cdup", "--show-prefix", NULL
7604         };
7606         return io_run_load(rev_parse_argv, "=", read_repo_info);
7610 /*
7611  * Main
7612  */
7614 static const char usage[] =
7615 "tig " TIG_VERSION " (" __DATE__ ")\n"
7616 "\n"
7617 "Usage: tig        [options] [revs] [--] [paths]\n"
7618 "   or: tig show   [options] [revs] [--] [paths]\n"
7619 "   or: tig blame  [rev] path\n"
7620 "   or: tig status\n"
7621 "   or: tig <      [git command output]\n"
7622 "\n"
7623 "Options:\n"
7624 "  -v, --version   Show version and exit\n"
7625 "  -h, --help      Show help message and exit";
7627 static void __NORETURN
7628 quit(int sig)
7630         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7631         if (cursed)
7632                 endwin();
7633         exit(0);
7636 static void __NORETURN
7637 die(const char *err, ...)
7639         va_list args;
7641         endwin();
7643         va_start(args, err);
7644         fputs("tig: ", stderr);
7645         vfprintf(stderr, err, args);
7646         fputs("\n", stderr);
7647         va_end(args);
7649         exit(1);
7652 static void
7653 warn(const char *msg, ...)
7655         va_list args;
7657         va_start(args, msg);
7658         fputs("tig warning: ", stderr);
7659         vfprintf(stderr, msg, args);
7660         fputs("\n", stderr);
7661         va_end(args);
7664 static enum request
7665 parse_options(int argc, const char *argv[])
7667         enum request request = REQ_VIEW_MAIN;
7668         const char *subcommand;
7669         bool seen_dashdash = FALSE;
7670         /* XXX: This is vulnerable to the user overriding options
7671          * required for the main view parser. */
7672         const char *custom_argv[SIZEOF_ARG] = {
7673                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7674                         "--topo-order", NULL
7675         };
7676         int i, j = 6;
7678         if (!isatty(STDIN_FILENO)) {
7679                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7680                 return REQ_VIEW_PAGER;
7681         }
7683         if (argc <= 1)
7684                 return REQ_NONE;
7686         subcommand = argv[1];
7687         if (!strcmp(subcommand, "status")) {
7688                 if (argc > 2)
7689                         warn("ignoring arguments after `%s'", subcommand);
7690                 return REQ_VIEW_STATUS;
7692         } else if (!strcmp(subcommand, "blame")) {
7693                 if (argc <= 2 || argc > 4)
7694                         die("invalid number of options to blame\n\n%s", usage);
7696                 i = 2;
7697                 if (argc == 4) {
7698                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7699                         i++;
7700                 }
7702                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7703                 return REQ_VIEW_BLAME;
7705         } else if (!strcmp(subcommand, "show")) {
7706                 request = REQ_VIEW_DIFF;
7708         } else {
7709                 subcommand = NULL;
7710         }
7712         if (subcommand) {
7713                 custom_argv[1] = subcommand;
7714                 j = 2;
7715         }
7717         for (i = 1 + !!subcommand; i < argc; i++) {
7718                 const char *opt = argv[i];
7720                 if (seen_dashdash || !strcmp(opt, "--")) {
7721                         seen_dashdash = TRUE;
7723                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7724                         printf("tig version %s\n", TIG_VERSION);
7725                         quit(0);
7727                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7728                         printf("%s\n", usage);
7729                         quit(0);
7730                 }
7732                 custom_argv[j++] = opt;
7733                 if (j >= ARRAY_SIZE(custom_argv))
7734                         die("command too long");
7735         }
7737         if (!prepare_update(VIEW(request), custom_argv, NULL))
7738                 die("Failed to format arguments");
7740         return request;
7743 int
7744 main(int argc, const char *argv[])
7746         const char *codeset = "UTF-8";
7747         enum request request = parse_options(argc, argv);
7748         struct view *view;
7749         size_t i;
7751         signal(SIGINT, quit);
7752         signal(SIGPIPE, SIG_IGN);
7754         if (setlocale(LC_ALL, "")) {
7755                 codeset = nl_langinfo(CODESET);
7756         }
7758         if (load_repo_info() == ERR)
7759                 die("Failed to load repo info.");
7761         if (load_options() == ERR)
7762                 die("Failed to load user config.");
7764         if (load_git_config() == ERR)
7765                 die("Failed to load repo config.");
7767         /* Require a git repository unless when running in pager mode. */
7768         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7769                 die("Not a git repository");
7771         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7772                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7773                 if (opt_iconv_in == ICONV_NONE)
7774                         die("Failed to initialize character set conversion");
7775         }
7777         if (codeset && strcmp(codeset, "UTF-8")) {
7778                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7779                 if (opt_iconv_out == ICONV_NONE)
7780                         die("Failed to initialize character set conversion");
7781         }
7783         if (load_refs() == ERR)
7784                 die("Failed to load refs.");
7786         foreach_view (view, i)
7787                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7788                         die("Too many arguments in the `%s` environment variable",
7789                             view->cmd_env);
7791         init_display();
7793         if (request != REQ_NONE)
7794                 open_view(NULL, request, OPEN_PREPARED);
7795         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7797         while (view_driver(display[current_view], request)) {
7798                 int key = get_input(0);
7800                 view = display[current_view];
7801                 request = get_keybinding(view->keymap, key);
7803                 /* Some low-level request handling. This keeps access to
7804                  * status_win restricted. */
7805                 switch (request) {
7806                 case REQ_PROMPT:
7807                 {
7808                         char *cmd = read_prompt(":");
7810                         if (cmd && isdigit(*cmd)) {
7811                                 int lineno = view->lineno + 1;
7813                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7814                                         select_view_line(view, lineno - 1);
7815                                         report("");
7816                                 } else {
7817                                         report("Unable to parse '%s' as a line number", cmd);
7818                                 }
7820                         } else if (cmd) {
7821                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7822                                 const char *argv[SIZEOF_ARG] = { "git" };
7823                                 int argc = 1;
7825                                 /* When running random commands, initially show the
7826                                  * command in the title. However, it maybe later be
7827                                  * overwritten if a commit line is selected. */
7828                                 string_ncopy(next->ref, cmd, strlen(cmd));
7830                                 if (!argv_from_string(argv, &argc, cmd)) {
7831                                         report("Too many arguments");
7832                                 } else if (!prepare_update(next, argv, NULL)) {
7833                                         report("Failed to format command");
7834                                 } else {
7835                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7836                                 }
7837                         }
7839                         request = REQ_NONE;
7840                         break;
7841                 }
7842                 case REQ_SEARCH:
7843                 case REQ_SEARCH_BACK:
7844                 {
7845                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7846                         char *search = read_prompt(prompt);
7848                         if (search)
7849                                 string_ncopy(opt_search, search, strlen(search));
7850                         else if (*opt_search)
7851                                 request = request == REQ_SEARCH ?
7852                                         REQ_FIND_NEXT :
7853                                         REQ_FIND_PREV;
7854                         else
7855                                 request = REQ_NONE;
7856                         break;
7857                 }
7858                 default:
7859                         break;
7860                 }
7861         }
7863         quit(0);
7865         return 0;