Code

Fix unbind behavoir
[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_UNKNOWN = KEY_MAX + 1,
1170         REQ_OFFSET,
1171         REQ_INFO
1173 #undef  REQ_GROUP
1174 #undef  REQ_
1175 };
1177 struct request_info {
1178         enum request request;
1179         const char *name;
1180         int namelen;
1181         const char *help;
1182 };
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187         REQ_INFO
1188 #undef  REQ_GROUP
1189 #undef  REQ_
1190 };
1192 static enum request
1193 get_request(const char *name)
1195         int namelen = strlen(name);
1196         int i;
1198         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199                 if (enum_equals(req_info[i], name, namelen))
1200                         return req_info[i].request;
1202         return REQ_UNKNOWN;
1206 /*
1207  * Options
1208  */
1210 /* Option and state variables. */
1211 static enum date opt_date               = DATE_DEFAULT;
1212 static enum author opt_author           = AUTHOR_DEFAULT;
1213 static bool opt_line_number             = FALSE;
1214 static bool opt_line_graphics           = TRUE;
1215 static bool opt_rev_graph               = FALSE;
1216 static bool opt_show_refs               = TRUE;
1217 static int opt_num_interval             = 5;
1218 static double opt_hscroll               = 0.50;
1219 static double opt_scale_split_view      = 2.0 / 3.0;
1220 static int opt_tab_size                 = 8;
1221 static int opt_author_cols              = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR]        = "";
1223 static char opt_file[SIZEOF_STR]        = "";
1224 static char opt_ref[SIZEOF_REF]         = "";
1225 static char opt_head[SIZEOF_REF]        = "";
1226 static char opt_remote[SIZEOF_REF]      = "";
1227 static char opt_encoding[20]            = "UTF-8";
1228 static iconv_t opt_iconv_in             = ICONV_NONE;
1229 static iconv_t opt_iconv_out            = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR]      = "";
1231 static char opt_cdup[SIZEOF_STR]        = "";
1232 static char opt_prefix[SIZEOF_STR]      = "";
1233 static char opt_git_dir[SIZEOF_STR]     = "";
1234 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR]      = "";
1236 static FILE *opt_tty                    = NULL;
1238 #define is_initial_commit()     (!get_ref_head())
1239 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1242 /*
1243  * Line-oriented content detection.
1244  */
1246 #define LINE_INFO \
1247 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1248 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1249 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1250 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1251 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1252 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1253 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1254 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1255 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1261 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1262 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1263 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1264 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1265 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1266 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1267 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1268 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1269 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1270 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1271 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1272 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1273 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1275 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1276 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1277 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1278 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1279 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1280 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1281 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1282 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1283 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1284 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1285 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1286 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1287 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1288 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1289 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1290 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1291 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1292 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1293 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1294 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1295 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1296 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1297 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1298 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1299 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1301 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1302 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1303 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1305 enum line_type {
1306 #define LINE(type, line, fg, bg, attr) \
1307         LINE_##type
1308         LINE_INFO,
1309         LINE_NONE
1310 #undef  LINE
1311 };
1313 struct line_info {
1314         const char *name;       /* Option name. */
1315         int namelen;            /* Size of option name. */
1316         const char *line;       /* The start of line to match. */
1317         int linelen;            /* Size of string to match. */
1318         int fg, bg, attr;       /* Color and text attributes for the lines. */
1319 };
1321 static struct line_info line_info[] = {
1322 #define LINE(type, line, fg, bg, attr) \
1323         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1324         LINE_INFO
1325 #undef  LINE
1326 };
1328 static enum line_type
1329 get_line_type(const char *line)
1331         int linelen = strlen(line);
1332         enum line_type type;
1334         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1335                 /* Case insensitive search matches Signed-off-by lines better. */
1336                 if (linelen >= line_info[type].linelen &&
1337                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1338                         return type;
1340         return LINE_DEFAULT;
1343 static inline int
1344 get_line_attr(enum line_type type)
1346         assert(type < ARRAY_SIZE(line_info));
1347         return COLOR_PAIR(type) | line_info[type].attr;
1350 static struct line_info *
1351 get_line_info(const char *name)
1353         size_t namelen = strlen(name);
1354         enum line_type type;
1356         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1357                 if (enum_equals(line_info[type], name, namelen))
1358                         return &line_info[type];
1360         return NULL;
1363 static void
1364 init_colors(void)
1366         int default_bg = line_info[LINE_DEFAULT].bg;
1367         int default_fg = line_info[LINE_DEFAULT].fg;
1368         enum line_type type;
1370         start_color();
1372         if (assume_default_colors(default_fg, default_bg) == ERR) {
1373                 default_bg = COLOR_BLACK;
1374                 default_fg = COLOR_WHITE;
1375         }
1377         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1378                 struct line_info *info = &line_info[type];
1379                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1380                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1382                 init_pair(type, fg, bg);
1383         }
1386 struct line {
1387         enum line_type type;
1389         /* State flags */
1390         unsigned int selected:1;
1391         unsigned int dirty:1;
1392         unsigned int cleareol:1;
1393         unsigned int other:16;
1395         void *data;             /* User data */
1396 };
1399 /*
1400  * Keys
1401  */
1403 struct keybinding {
1404         int alias;
1405         enum request request;
1406 };
1408 static struct keybinding default_keybindings[] = {
1409         /* View switching */
1410         { 'm',          REQ_VIEW_MAIN },
1411         { 'd',          REQ_VIEW_DIFF },
1412         { 'l',          REQ_VIEW_LOG },
1413         { 't',          REQ_VIEW_TREE },
1414         { 'f',          REQ_VIEW_BLOB },
1415         { 'B',          REQ_VIEW_BLAME },
1416         { 'H',          REQ_VIEW_BRANCH },
1417         { 'p',          REQ_VIEW_PAGER },
1418         { 'h',          REQ_VIEW_HELP },
1419         { 'S',          REQ_VIEW_STATUS },
1420         { 'c',          REQ_VIEW_STAGE },
1422         /* View manipulation */
1423         { 'q',          REQ_VIEW_CLOSE },
1424         { KEY_TAB,      REQ_VIEW_NEXT },
1425         { KEY_RETURN,   REQ_ENTER },
1426         { KEY_UP,       REQ_PREVIOUS },
1427         { KEY_DOWN,     REQ_NEXT },
1428         { 'R',          REQ_REFRESH },
1429         { KEY_F(5),     REQ_REFRESH },
1430         { 'O',          REQ_MAXIMIZE },
1432         /* Cursor navigation */
1433         { 'k',          REQ_MOVE_UP },
1434         { 'j',          REQ_MOVE_DOWN },
1435         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1436         { KEY_END,      REQ_MOVE_LAST_LINE },
1437         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1438         { ' ',          REQ_MOVE_PAGE_DOWN },
1439         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1440         { 'b',          REQ_MOVE_PAGE_UP },
1441         { '-',          REQ_MOVE_PAGE_UP },
1443         /* Scrolling */
1444         { KEY_LEFT,     REQ_SCROLL_LEFT },
1445         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1446         { KEY_IC,       REQ_SCROLL_LINE_UP },
1447         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1448         { 'w',          REQ_SCROLL_PAGE_UP },
1449         { 's',          REQ_SCROLL_PAGE_DOWN },
1451         /* Searching */
1452         { '/',          REQ_SEARCH },
1453         { '?',          REQ_SEARCH_BACK },
1454         { 'n',          REQ_FIND_NEXT },
1455         { 'N',          REQ_FIND_PREV },
1457         /* Misc */
1458         { 'Q',          REQ_QUIT },
1459         { 'z',          REQ_STOP_LOADING },
1460         { 'v',          REQ_SHOW_VERSION },
1461         { 'r',          REQ_SCREEN_REDRAW },
1462         { 'o',          REQ_OPTIONS },
1463         { '.',          REQ_TOGGLE_LINENO },
1464         { 'D',          REQ_TOGGLE_DATE },
1465         { 'A',          REQ_TOGGLE_AUTHOR },
1466         { 'g',          REQ_TOGGLE_REV_GRAPH },
1467         { 'F',          REQ_TOGGLE_REFS },
1468         { 'I',          REQ_TOGGLE_SORT_ORDER },
1469         { 'i',          REQ_TOGGLE_SORT_FIELD },
1470         { ':',          REQ_PROMPT },
1471         { 'u',          REQ_STATUS_UPDATE },
1472         { '!',          REQ_STATUS_REVERT },
1473         { 'M',          REQ_STATUS_MERGE },
1474         { '@',          REQ_STAGE_NEXT },
1475         { ',',          REQ_PARENT },
1476         { 'e',          REQ_EDIT },
1477 };
1479 #define KEYMAP_INFO \
1480         KEYMAP_(GENERIC), \
1481         KEYMAP_(MAIN), \
1482         KEYMAP_(DIFF), \
1483         KEYMAP_(LOG), \
1484         KEYMAP_(TREE), \
1485         KEYMAP_(BLOB), \
1486         KEYMAP_(BLAME), \
1487         KEYMAP_(BRANCH), \
1488         KEYMAP_(PAGER), \
1489         KEYMAP_(HELP), \
1490         KEYMAP_(STATUS), \
1491         KEYMAP_(STAGE)
1493 enum keymap {
1494 #define KEYMAP_(name) KEYMAP_##name
1495         KEYMAP_INFO
1496 #undef  KEYMAP_
1497 };
1499 static const struct enum_map keymap_table[] = {
1500 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1501         KEYMAP_INFO
1502 #undef  KEYMAP_
1503 };
1505 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1507 struct keybinding_table {
1508         struct keybinding *data;
1509         size_t size;
1510 };
1512 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1514 static void
1515 add_keybinding(enum keymap keymap, enum request request, int key)
1517         struct keybinding_table *table = &keybindings[keymap];
1519         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1520         if (!table->data)
1521                 die("Failed to allocate keybinding");
1522         table->data[table->size].alias = key;
1523         table->data[table->size++].request = request;
1525         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1526                 int i;
1528                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1529                         if (default_keybindings[i].alias == key)
1530                                 default_keybindings[i].request = REQ_NONE;
1531         }
1534 /* Looks for a key binding first in the given map, then in the generic map, and
1535  * lastly in the default keybindings. */
1536 static enum request
1537 get_keybinding(enum keymap keymap, int key)
1539         size_t i;
1541         for (i = 0; i < keybindings[keymap].size; i++)
1542                 if (keybindings[keymap].data[i].alias == key)
1543                         return keybindings[keymap].data[i].request;
1545         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1546                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1547                         return keybindings[KEYMAP_GENERIC].data[i].request;
1549         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1550                 if (default_keybindings[i].alias == key)
1551                         return default_keybindings[i].request;
1553         return (enum request) key;
1557 struct key {
1558         const char *name;
1559         int value;
1560 };
1562 static const struct key key_table[] = {
1563         { "Enter",      KEY_RETURN },
1564         { "Space",      ' ' },
1565         { "Backspace",  KEY_BACKSPACE },
1566         { "Tab",        KEY_TAB },
1567         { "Escape",     KEY_ESC },
1568         { "Left",       KEY_LEFT },
1569         { "Right",      KEY_RIGHT },
1570         { "Up",         KEY_UP },
1571         { "Down",       KEY_DOWN },
1572         { "Insert",     KEY_IC },
1573         { "Delete",     KEY_DC },
1574         { "Hash",       '#' },
1575         { "Home",       KEY_HOME },
1576         { "End",        KEY_END },
1577         { "PageUp",     KEY_PPAGE },
1578         { "PageDown",   KEY_NPAGE },
1579         { "F1",         KEY_F(1) },
1580         { "F2",         KEY_F(2) },
1581         { "F3",         KEY_F(3) },
1582         { "F4",         KEY_F(4) },
1583         { "F5",         KEY_F(5) },
1584         { "F6",         KEY_F(6) },
1585         { "F7",         KEY_F(7) },
1586         { "F8",         KEY_F(8) },
1587         { "F9",         KEY_F(9) },
1588         { "F10",        KEY_F(10) },
1589         { "F11",        KEY_F(11) },
1590         { "F12",        KEY_F(12) },
1591 };
1593 static int
1594 get_key_value(const char *name)
1596         int i;
1598         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1599                 if (!strcasecmp(key_table[i].name, name))
1600                         return key_table[i].value;
1602         if (strlen(name) == 1 && isprint(*name))
1603                 return (int) *name;
1605         return ERR;
1608 static const char *
1609 get_key_name(int key_value)
1611         static char key_char[] = "'X'";
1612         const char *seq = NULL;
1613         int key;
1615         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1616                 if (key_table[key].value == key_value)
1617                         seq = key_table[key].name;
1619         if (seq == NULL &&
1620             key_value < 127 &&
1621             isprint(key_value)) {
1622                 key_char[1] = (char) key_value;
1623                 seq = key_char;
1624         }
1626         return seq ? seq : "(no key)";
1629 static bool
1630 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1632         const char *sep = *pos > 0 ? ", " : "";
1633         const char *keyname = get_key_name(keybinding->alias);
1635         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1638 static bool
1639 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1640                            enum keymap keymap, bool all)
1642         int i;
1644         for (i = 0; i < keybindings[keymap].size; i++) {
1645                 if (keybindings[keymap].data[i].request == request) {
1646                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1647                                 return FALSE;
1648                         if (!all)
1649                                 break;
1650                 }
1651         }
1653         return TRUE;
1656 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1658 static const char *
1659 get_keys(enum keymap keymap, enum request request, bool all)
1661         static char buf[BUFSIZ];
1662         size_t pos = 0;
1663         int i;
1665         buf[pos] = 0;
1667         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1668                 return "Too many keybindings!";
1669         if (pos > 0 && !all)
1670                 return buf;
1672         if (keymap != KEYMAP_GENERIC) {
1673                 /* Only the generic keymap includes the default keybindings when
1674                  * listing all keys. */
1675                 if (all)
1676                         return buf;
1678                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1679                         return "Too many keybindings!";
1680                 if (pos)
1681                         return buf;
1682         }
1684         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1685                 if (default_keybindings[i].request == request) {
1686                         if (!append_key(buf, &pos, &default_keybindings[i]))
1687                                 return "Too many keybindings!";
1688                         if (!all)
1689                                 return buf;
1690                 }
1691         }
1693         return buf;
1696 struct run_request {
1697         enum keymap keymap;
1698         int key;
1699         const char *argv[SIZEOF_ARG];
1700 };
1702 static struct run_request *run_request;
1703 static size_t run_requests;
1705 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1707 static enum request
1708 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1710         struct run_request *req;
1712         if (argc >= ARRAY_SIZE(req->argv) - 1)
1713                 return REQ_NONE;
1715         if (!realloc_run_requests(&run_request, run_requests, 1))
1716                 return REQ_NONE;
1718         req = &run_request[run_requests];
1719         req->keymap = keymap;
1720         req->key = key;
1721         req->argv[0] = NULL;
1723         if (!format_argv(req->argv, argv, FORMAT_NONE))
1724                 return REQ_NONE;
1726         return REQ_NONE + ++run_requests;
1729 static struct run_request *
1730 get_run_request(enum request request)
1732         if (request <= REQ_NONE)
1733                 return NULL;
1734         return &run_request[request - REQ_NONE - 1];
1737 static void
1738 add_builtin_run_requests(void)
1740         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1741         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1742         const char *commit[] = { "git", "commit", NULL };
1743         const char *gc[] = { "git", "gc", NULL };
1744         struct {
1745                 enum keymap keymap;
1746                 int key;
1747                 int argc;
1748                 const char **argv;
1749         } reqs[] = {
1750                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1751                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1752                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1753                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1754         };
1755         int i;
1757         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1758                 enum request req;
1760                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1761                 if (req != REQ_NONE)
1762                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1763         }
1766 /*
1767  * User config file handling.
1768  */
1770 static int   config_lineno;
1771 static bool  config_errors;
1772 static const char *config_msg;
1774 static const struct enum_map color_map[] = {
1775 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1776         COLOR_MAP(DEFAULT),
1777         COLOR_MAP(BLACK),
1778         COLOR_MAP(BLUE),
1779         COLOR_MAP(CYAN),
1780         COLOR_MAP(GREEN),
1781         COLOR_MAP(MAGENTA),
1782         COLOR_MAP(RED),
1783         COLOR_MAP(WHITE),
1784         COLOR_MAP(YELLOW),
1785 };
1787 static const struct enum_map attr_map[] = {
1788 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1789         ATTR_MAP(NORMAL),
1790         ATTR_MAP(BLINK),
1791         ATTR_MAP(BOLD),
1792         ATTR_MAP(DIM),
1793         ATTR_MAP(REVERSE),
1794         ATTR_MAP(STANDOUT),
1795         ATTR_MAP(UNDERLINE),
1796 };
1798 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1800 static int parse_step(double *opt, const char *arg)
1802         *opt = atoi(arg);
1803         if (!strchr(arg, '%'))
1804                 return OK;
1806         /* "Shift down" so 100% and 1 does not conflict. */
1807         *opt = (*opt - 1) / 100;
1808         if (*opt >= 1.0) {
1809                 *opt = 0.99;
1810                 config_msg = "Step value larger than 100%";
1811                 return ERR;
1812         }
1813         if (*opt < 0.0) {
1814                 *opt = 1;
1815                 config_msg = "Invalid step value";
1816                 return ERR;
1817         }
1818         return OK;
1821 static int
1822 parse_int(int *opt, const char *arg, int min, int max)
1824         int value = atoi(arg);
1826         if (min <= value && value <= max) {
1827                 *opt = value;
1828                 return OK;
1829         }
1831         config_msg = "Integer value out of bound";
1832         return ERR;
1835 static bool
1836 set_color(int *color, const char *name)
1838         if (map_enum(color, color_map, name))
1839                 return TRUE;
1840         if (!prefixcmp(name, "color"))
1841                 return parse_int(color, name + 5, 0, 255) == OK;
1842         return FALSE;
1845 /* Wants: object fgcolor bgcolor [attribute] */
1846 static int
1847 option_color_command(int argc, const char *argv[])
1849         struct line_info *info;
1851         if (argc < 3) {
1852                 config_msg = "Wrong number of arguments given to color command";
1853                 return ERR;
1854         }
1856         info = get_line_info(argv[0]);
1857         if (!info) {
1858                 static const struct enum_map obsolete[] = {
1859                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1860                         ENUM_MAP("main-date",   LINE_DATE),
1861                         ENUM_MAP("main-author", LINE_AUTHOR),
1862                 };
1863                 int index;
1865                 if (!map_enum(&index, obsolete, argv[0])) {
1866                         config_msg = "Unknown color name";
1867                         return ERR;
1868                 }
1869                 info = &line_info[index];
1870         }
1872         if (!set_color(&info->fg, argv[1]) ||
1873             !set_color(&info->bg, argv[2])) {
1874                 config_msg = "Unknown color";
1875                 return ERR;
1876         }
1878         info->attr = 0;
1879         while (argc-- > 3) {
1880                 int attr;
1882                 if (!set_attribute(&attr, argv[argc])) {
1883                         config_msg = "Unknown attribute";
1884                         return ERR;
1885                 }
1886                 info->attr |= attr;
1887         }
1889         return OK;
1892 static int parse_bool(bool *opt, const char *arg)
1894         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1895                 ? TRUE : FALSE;
1896         return OK;
1899 static int parse_enum_do(unsigned int *opt, const char *arg,
1900                          const struct enum_map *map, size_t map_size)
1902         bool is_true;
1904         assert(map_size > 1);
1906         if (map_enum_do(map, map_size, (int *) opt, arg))
1907                 return OK;
1909         if (parse_bool(&is_true, arg) != OK)
1910                 return ERR;
1912         *opt = is_true ? map[1].value : map[0].value;
1913         return OK;
1916 #define parse_enum(opt, arg, map) \
1917         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1919 static int
1920 parse_string(char *opt, const char *arg, size_t optsize)
1922         int arglen = strlen(arg);
1924         switch (arg[0]) {
1925         case '\"':
1926         case '\'':
1927                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1928                         config_msg = "Unmatched quotation";
1929                         return ERR;
1930                 }
1931                 arg += 1; arglen -= 2;
1932         default:
1933                 string_ncopy_do(opt, optsize, arg, arglen);
1934                 return OK;
1935         }
1938 /* Wants: name = value */
1939 static int
1940 option_set_command(int argc, const char *argv[])
1942         if (argc != 3) {
1943                 config_msg = "Wrong number of arguments given to set command";
1944                 return ERR;
1945         }
1947         if (strcmp(argv[1], "=")) {
1948                 config_msg = "No value assigned";
1949                 return ERR;
1950         }
1952         if (!strcmp(argv[0], "show-author"))
1953                 return parse_enum(&opt_author, argv[2], author_map);
1955         if (!strcmp(argv[0], "show-date"))
1956                 return parse_enum(&opt_date, argv[2], date_map);
1958         if (!strcmp(argv[0], "show-rev-graph"))
1959                 return parse_bool(&opt_rev_graph, argv[2]);
1961         if (!strcmp(argv[0], "show-refs"))
1962                 return parse_bool(&opt_show_refs, argv[2]);
1964         if (!strcmp(argv[0], "show-line-numbers"))
1965                 return parse_bool(&opt_line_number, argv[2]);
1967         if (!strcmp(argv[0], "line-graphics"))
1968                 return parse_bool(&opt_line_graphics, argv[2]);
1970         if (!strcmp(argv[0], "line-number-interval"))
1971                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1973         if (!strcmp(argv[0], "author-width"))
1974                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1976         if (!strcmp(argv[0], "horizontal-scroll"))
1977                 return parse_step(&opt_hscroll, argv[2]);
1979         if (!strcmp(argv[0], "split-view-height"))
1980                 return parse_step(&opt_scale_split_view, argv[2]);
1982         if (!strcmp(argv[0], "tab-size"))
1983                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1985         if (!strcmp(argv[0], "commit-encoding"))
1986                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1988         config_msg = "Unknown variable name";
1989         return ERR;
1992 /* Wants: mode request key */
1993 static int
1994 option_bind_command(int argc, const char *argv[])
1996         enum request request;
1997         int keymap = -1;
1998         int key;
2000         if (argc < 3) {
2001                 config_msg = "Wrong number of arguments given to bind command";
2002                 return ERR;
2003         }
2005         if (set_keymap(&keymap, argv[0]) == ERR) {
2006                 config_msg = "Unknown key map";
2007                 return ERR;
2008         }
2010         key = get_key_value(argv[1]);
2011         if (key == ERR) {
2012                 config_msg = "Unknown key";
2013                 return ERR;
2014         }
2016         request = get_request(argv[2]);
2017         if (request == REQ_UNKNOWN) {
2018                 static const struct enum_map obsolete[] = {
2019                         ENUM_MAP("cherry-pick",         REQ_NONE),
2020                         ENUM_MAP("screen-resize",       REQ_NONE),
2021                         ENUM_MAP("tree-parent",         REQ_PARENT),
2022                 };
2023                 int alias;
2025                 if (map_enum(&alias, obsolete, argv[2])) {
2026                         if (alias != REQ_NONE)
2027                                 add_keybinding(keymap, alias, key);
2028                         config_msg = "Obsolete request name";
2029                         return ERR;
2030                 }
2031         }
2032         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2033                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2034         if (request == REQ_UNKNOWN) {
2035                 config_msg = "Unknown request name";
2036                 return ERR;
2037         }
2039         add_keybinding(keymap, request, key);
2041         return OK;
2044 static int
2045 set_option(const char *opt, char *value)
2047         const char *argv[SIZEOF_ARG];
2048         int argc = 0;
2050         if (!argv_from_string(argv, &argc, value)) {
2051                 config_msg = "Too many option arguments";
2052                 return ERR;
2053         }
2055         if (!strcmp(opt, "color"))
2056                 return option_color_command(argc, argv);
2058         if (!strcmp(opt, "set"))
2059                 return option_set_command(argc, argv);
2061         if (!strcmp(opt, "bind"))
2062                 return option_bind_command(argc, argv);
2064         config_msg = "Unknown option command";
2065         return ERR;
2068 static int
2069 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2071         int status = OK;
2073         config_lineno++;
2074         config_msg = "Internal error";
2076         /* Check for comment markers, since read_properties() will
2077          * only ensure opt and value are split at first " \t". */
2078         optlen = strcspn(opt, "#");
2079         if (optlen == 0)
2080                 return OK;
2082         if (opt[optlen] != 0) {
2083                 config_msg = "No option value";
2084                 status = ERR;
2086         }  else {
2087                 /* Look for comment endings in the value. */
2088                 size_t len = strcspn(value, "#");
2090                 if (len < valuelen) {
2091                         valuelen = len;
2092                         value[valuelen] = 0;
2093                 }
2095                 status = set_option(opt, value);
2096         }
2098         if (status == ERR) {
2099                 warn("Error on line %d, near '%.*s': %s",
2100                      config_lineno, (int) optlen, opt, config_msg);
2101                 config_errors = TRUE;
2102         }
2104         /* Always keep going if errors are encountered. */
2105         return OK;
2108 static void
2109 load_option_file(const char *path)
2111         struct io io = {};
2113         /* It's OK that the file doesn't exist. */
2114         if (!io_open(&io, "%s", path))
2115                 return;
2117         config_lineno = 0;
2118         config_errors = FALSE;
2120         if (io_load(&io, " \t", read_option) == ERR ||
2121             config_errors == TRUE)
2122                 warn("Errors while loading %s.", path);
2125 static int
2126 load_options(void)
2128         const char *home = getenv("HOME");
2129         const char *tigrc_user = getenv("TIGRC_USER");
2130         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2131         char buf[SIZEOF_STR];
2133         add_builtin_run_requests();
2135         if (!tigrc_system)
2136                 tigrc_system = SYSCONFDIR "/tigrc";
2137         load_option_file(tigrc_system);
2139         if (!tigrc_user) {
2140                 if (!home || !string_format(buf, "%s/.tigrc", home))
2141                         return ERR;
2142                 tigrc_user = buf;
2143         }
2144         load_option_file(tigrc_user);
2146         return OK;
2150 /*
2151  * The viewer
2152  */
2154 struct view;
2155 struct view_ops;
2157 /* The display array of active views and the index of the current view. */
2158 static struct view *display[2];
2159 static unsigned int current_view;
2161 #define foreach_displayed_view(view, i) \
2162         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2164 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2166 /* Current head and commit ID */
2167 static char ref_blob[SIZEOF_REF]        = "";
2168 static char ref_commit[SIZEOF_REF]      = "HEAD";
2169 static char ref_head[SIZEOF_REF]        = "HEAD";
2170 static char ref_branch[SIZEOF_REF]      = "";
2172 enum view_type {
2173         VIEW_MAIN,
2174         VIEW_DIFF,
2175         VIEW_LOG,
2176         VIEW_TREE,
2177         VIEW_BLOB,
2178         VIEW_BLAME,
2179         VIEW_BRANCH,
2180         VIEW_HELP,
2181         VIEW_PAGER,
2182         VIEW_STATUS,
2183         VIEW_STAGE,
2184 };
2186 struct view {
2187         enum view_type type;    /* View type */
2188         const char *name;       /* View name */
2189         const char *cmd_env;    /* Command line set via environment */
2190         const char *id;         /* Points to either of ref_{head,commit,blob} */
2192         struct view_ops *ops;   /* View operations */
2194         enum keymap keymap;     /* What keymap does this view have */
2195         bool git_dir;           /* Whether the view requires a git directory. */
2196         bool refresh;           /* Whether the view supports refreshing. */
2198         char ref[SIZEOF_REF];   /* Hovered commit reference */
2199         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2201         int height, width;      /* The width and height of the main window */
2202         WINDOW *win;            /* The main window */
2203         WINDOW *title;          /* The title window living below the main window */
2205         /* Navigation */
2206         unsigned long offset;   /* Offset of the window top */
2207         unsigned long yoffset;  /* Offset from the window side. */
2208         unsigned long lineno;   /* Current line number */
2209         unsigned long p_offset; /* Previous offset of the window top */
2210         unsigned long p_yoffset;/* Previous offset from the window side */
2211         unsigned long p_lineno; /* Previous current line number */
2212         bool p_restore;         /* Should the previous position be restored. */
2214         /* Searching */
2215         char grep[SIZEOF_STR];  /* Search string */
2216         regex_t *regex;         /* Pre-compiled regexp */
2218         /* If non-NULL, points to the view that opened this view. If this view
2219          * is closed tig will switch back to the parent view. */
2220         struct view *parent;
2222         /* Buffering */
2223         size_t lines;           /* Total number of lines */
2224         struct line *line;      /* Line index */
2225         unsigned int digits;    /* Number of digits in the lines member. */
2227         /* Drawing */
2228         struct line *curline;   /* Line currently being drawn. */
2229         enum line_type curtype; /* Attribute currently used for drawing. */
2230         unsigned long col;      /* Column when drawing. */
2231         bool has_scrolled;      /* View was scrolled. */
2233         /* Loading */
2234         struct io io;
2235         struct io *pipe;
2236         time_t start_time;
2237         time_t update_secs;
2238 };
2240 struct view_ops {
2241         /* What type of content being displayed. Used in the title bar. */
2242         const char *type;
2243         /* Default command arguments. */
2244         const char **argv;
2245         /* Open and reads in all view content. */
2246         bool (*open)(struct view *view);
2247         /* Read one line; updates view->line. */
2248         bool (*read)(struct view *view, char *data);
2249         /* Draw one line; @lineno must be < view->height. */
2250         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2251         /* Depending on view handle a special requests. */
2252         enum request (*request)(struct view *view, enum request request, struct line *line);
2253         /* Search for regexp in a line. */
2254         bool (*grep)(struct view *view, struct line *line);
2255         /* Select line */
2256         void (*select)(struct view *view, struct line *line);
2257         /* Prepare view for loading */
2258         bool (*prepare)(struct view *view);
2259 };
2261 static struct view_ops blame_ops;
2262 static struct view_ops blob_ops;
2263 static struct view_ops diff_ops;
2264 static struct view_ops help_ops;
2265 static struct view_ops log_ops;
2266 static struct view_ops main_ops;
2267 static struct view_ops pager_ops;
2268 static struct view_ops stage_ops;
2269 static struct view_ops status_ops;
2270 static struct view_ops tree_ops;
2271 static struct view_ops branch_ops;
2273 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2274         { type, name, #env, ref, ops, map, git, refresh }
2276 #define VIEW_(id, name, ops, git, refresh, ref) \
2277         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2279 static struct view views[] = {
2280         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  TRUE,  ref_head),
2281         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  FALSE, ref_commit),
2282         VIEW_(LOG,    "log",    &log_ops,    TRUE,  TRUE,  ref_head),
2283         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  FALSE, ref_commit),
2284         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  FALSE, ref_blob),
2285         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  FALSE, ref_commit),
2286         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  TRUE,  ref_head),
2287         VIEW_(HELP,   "help",   &help_ops,   FALSE, FALSE, ""),
2288         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, FALSE, "stdin"),
2289         VIEW_(STATUS, "status", &status_ops, TRUE,  TRUE,  ""),
2290         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  TRUE,  ""),
2291 };
2293 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2295 #define foreach_view(view, i) \
2296         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2298 #define view_is_displayed(view) \
2299         (view == display[0] || view == display[1])
2301 #define view_has_parent(view, child_type, parent_type) \
2302         (view->type == child_type && view->parent && view->parent->type == parent_type)
2304 static inline void
2305 set_view_attr(struct view *view, enum line_type type)
2307         if (!view->curline->selected && view->curtype != type) {
2308                 (void) wattrset(view->win, get_line_attr(type));
2309                 wchgat(view->win, -1, 0, type, NULL);
2310                 view->curtype = type;
2311         }
2314 static int
2315 draw_chars(struct view *view, enum line_type type, const char *string,
2316            int max_len, bool use_tilde)
2318         static char out_buffer[BUFSIZ * 2];
2319         int len = 0;
2320         int col = 0;
2321         int trimmed = FALSE;
2322         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2324         if (max_len <= 0)
2325                 return 0;
2327         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2329         set_view_attr(view, type);
2330         if (len > 0) {
2331                 if (opt_iconv_out != ICONV_NONE) {
2332                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2333                         size_t inlen = len + 1;
2335                         char *outbuf = out_buffer;
2336                         size_t outlen = sizeof(out_buffer);
2338                         size_t ret;
2340                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2341                         if (ret != (size_t) -1) {
2342                                 string = out_buffer;
2343                                 len = sizeof(out_buffer) - outlen;
2344                         }
2345                 }
2347                 waddnstr(view->win, string, len);
2348         }
2349         if (trimmed && use_tilde) {
2350                 set_view_attr(view, LINE_DELIMITER);
2351                 waddch(view->win, '~');
2352                 col++;
2353         }
2355         return col;
2358 static int
2359 draw_space(struct view *view, enum line_type type, int max, int spaces)
2361         static char space[] = "                    ";
2362         int col = 0;
2364         spaces = MIN(max, spaces);
2366         while (spaces > 0) {
2367                 int len = MIN(spaces, sizeof(space) - 1);
2369                 col += draw_chars(view, type, space, len, FALSE);
2370                 spaces -= len;
2371         }
2373         return col;
2376 static bool
2377 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2379         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2380         return view->width + view->yoffset <= view->col;
2383 static bool
2384 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2386         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2387         int max = view->width + view->yoffset - view->col;
2388         int i;
2390         if (max < size)
2391                 size = max;
2393         set_view_attr(view, type);
2394         /* Using waddch() instead of waddnstr() ensures that
2395          * they'll be rendered correctly for the cursor line. */
2396         for (i = skip; i < size; i++)
2397                 waddch(view->win, graphic[i]);
2399         view->col += size;
2400         if (size < max && skip <= size)
2401                 waddch(view->win, ' ');
2402         view->col++;
2404         return view->width + view->yoffset <= view->col;
2407 static bool
2408 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2410         int max = MIN(view->width + view->yoffset - view->col, len);
2411         int col;
2413         if (text)
2414                 col = draw_chars(view, type, text, max - 1, trim);
2415         else
2416                 col = draw_space(view, type, max - 1, max - 1);
2418         view->col += col;
2419         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2420         return view->width + view->yoffset <= view->col;
2423 static bool
2424 draw_date(struct view *view, struct time *time)
2426         const char *date = mkdate(time, opt_date);
2427         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2429         return draw_field(view, LINE_DATE, date, cols, FALSE);
2432 static bool
2433 draw_author(struct view *view, const char *author)
2435         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2436         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2438         if (abbreviate && author)
2439                 author = get_author_initials(author);
2441         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2444 static bool
2445 draw_mode(struct view *view, mode_t mode)
2447         const char *str;
2449         if (S_ISDIR(mode))
2450                 str = "drwxr-xr-x";
2451         else if (S_ISLNK(mode))
2452                 str = "lrwxrwxrwx";
2453         else if (S_ISGITLINK(mode))
2454                 str = "m---------";
2455         else if (S_ISREG(mode) && mode & S_IXUSR)
2456                 str = "-rwxr-xr-x";
2457         else if (S_ISREG(mode))
2458                 str = "-rw-r--r--";
2459         else
2460                 str = "----------";
2462         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2465 static bool
2466 draw_lineno(struct view *view, unsigned int lineno)
2468         char number[10];
2469         int digits3 = view->digits < 3 ? 3 : view->digits;
2470         int max = MIN(view->width + view->yoffset - view->col, digits3);
2471         char *text = NULL;
2472         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2474         lineno += view->offset + 1;
2475         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2476                 static char fmt[] = "%1ld";
2478                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2479                 if (string_format(number, fmt, lineno))
2480                         text = number;
2481         }
2482         if (text)
2483                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2484         else
2485                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2486         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2489 static bool
2490 draw_view_line(struct view *view, unsigned int lineno)
2492         struct line *line;
2493         bool selected = (view->offset + lineno == view->lineno);
2495         assert(view_is_displayed(view));
2497         if (view->offset + lineno >= view->lines)
2498                 return FALSE;
2500         line = &view->line[view->offset + lineno];
2502         wmove(view->win, lineno, 0);
2503         if (line->cleareol)
2504                 wclrtoeol(view->win);
2505         view->col = 0;
2506         view->curline = line;
2507         view->curtype = LINE_NONE;
2508         line->selected = FALSE;
2509         line->dirty = line->cleareol = 0;
2511         if (selected) {
2512                 set_view_attr(view, LINE_CURSOR);
2513                 line->selected = TRUE;
2514                 view->ops->select(view, line);
2515         }
2517         return view->ops->draw(view, line, lineno);
2520 static void
2521 redraw_view_dirty(struct view *view)
2523         bool dirty = FALSE;
2524         int lineno;
2526         for (lineno = 0; lineno < view->height; lineno++) {
2527                 if (view->offset + lineno >= view->lines)
2528                         break;
2529                 if (!view->line[view->offset + lineno].dirty)
2530                         continue;
2531                 dirty = TRUE;
2532                 if (!draw_view_line(view, lineno))
2533                         break;
2534         }
2536         if (!dirty)
2537                 return;
2538         wnoutrefresh(view->win);
2541 static void
2542 redraw_view_from(struct view *view, int lineno)
2544         assert(0 <= lineno && lineno < view->height);
2546         for (; lineno < view->height; lineno++) {
2547                 if (!draw_view_line(view, lineno))
2548                         break;
2549         }
2551         wnoutrefresh(view->win);
2554 static void
2555 redraw_view(struct view *view)
2557         werase(view->win);
2558         redraw_view_from(view, 0);
2562 static void
2563 update_view_title(struct view *view)
2565         char buf[SIZEOF_STR];
2566         char state[SIZEOF_STR];
2567         size_t bufpos = 0, statelen = 0;
2569         assert(view_is_displayed(view));
2571         if (view->type != VIEW_STATUS && view->lines) {
2572                 unsigned int view_lines = view->offset + view->height;
2573                 unsigned int lines = view->lines
2574                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2575                                    : 0;
2577                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2578                                    view->ops->type,
2579                                    view->lineno + 1,
2580                                    view->lines,
2581                                    lines);
2583         }
2585         if (view->pipe) {
2586                 time_t secs = time(NULL) - view->start_time;
2588                 /* Three git seconds are a long time ... */
2589                 if (secs > 2)
2590                         string_format_from(state, &statelen, " loading %lds", secs);
2591         }
2593         string_format_from(buf, &bufpos, "[%s]", view->name);
2594         if (*view->ref && bufpos < view->width) {
2595                 size_t refsize = strlen(view->ref);
2596                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2598                 if (minsize < view->width)
2599                         refsize = view->width - minsize + 7;
2600                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2601         }
2603         if (statelen && bufpos < view->width) {
2604                 string_format_from(buf, &bufpos, "%s", state);
2605         }
2607         if (view == display[current_view])
2608                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2609         else
2610                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2612         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2613         wclrtoeol(view->title);
2614         wnoutrefresh(view->title);
2617 static int
2618 apply_step(double step, int value)
2620         if (step >= 1)
2621                 return (int) step;
2622         value *= step + 0.01;
2623         return value ? value : 1;
2626 static void
2627 resize_display(void)
2629         int offset, i;
2630         struct view *base = display[0];
2631         struct view *view = display[1] ? display[1] : display[0];
2633         /* Setup window dimensions */
2635         getmaxyx(stdscr, base->height, base->width);
2637         /* Make room for the status window. */
2638         base->height -= 1;
2640         if (view != base) {
2641                 /* Horizontal split. */
2642                 view->width   = base->width;
2643                 view->height  = apply_step(opt_scale_split_view, base->height);
2644                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2645                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2646                 base->height -= view->height;
2648                 /* Make room for the title bar. */
2649                 view->height -= 1;
2650         }
2652         /* Make room for the title bar. */
2653         base->height -= 1;
2655         offset = 0;
2657         foreach_displayed_view (view, i) {
2658                 if (!view->win) {
2659                         view->win = newwin(view->height, 0, offset, 0);
2660                         if (!view->win)
2661                                 die("Failed to create %s view", view->name);
2663                         scrollok(view->win, FALSE);
2665                         view->title = newwin(1, 0, offset + view->height, 0);
2666                         if (!view->title)
2667                                 die("Failed to create title window");
2669                 } else {
2670                         wresize(view->win, view->height, view->width);
2671                         mvwin(view->win,   offset, 0);
2672                         mvwin(view->title, offset + view->height, 0);
2673                 }
2675                 offset += view->height + 1;
2676         }
2679 static void
2680 redraw_display(bool clear)
2682         struct view *view;
2683         int i;
2685         foreach_displayed_view (view, i) {
2686                 if (clear)
2687                         wclear(view->win);
2688                 redraw_view(view);
2689                 update_view_title(view);
2690         }
2693 static void
2694 toggle_enum_option_do(unsigned int *opt, const char *help,
2695                       const struct enum_map *map, size_t size)
2697         *opt = (*opt + 1) % size;
2698         redraw_display(FALSE);
2699         report("Displaying %s %s", enum_name(map[*opt]), help);
2702 #define toggle_enum_option(opt, help, map) \
2703         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2705 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2706 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2708 static void
2709 toggle_view_option(bool *option, const char *help)
2711         *option = !*option;
2712         redraw_display(FALSE);
2713         report("%sabling %s", *option ? "En" : "Dis", help);
2716 static void
2717 open_option_menu(void)
2719         const struct menu_item menu[] = {
2720                 { '.', "line numbers", &opt_line_number },
2721                 { 'D', "date display", &opt_date },
2722                 { 'A', "author display", &opt_author },
2723                 { 'g', "revision graph display", &opt_rev_graph },
2724                 { 'F', "reference display", &opt_show_refs },
2725                 { 0 }
2726         };
2727         int selected = 0;
2729         if (prompt_menu("Toggle option", menu, &selected)) {
2730                 if (menu[selected].data == &opt_date)
2731                         toggle_date();
2732                 else if (menu[selected].data == &opt_author)
2733                         toggle_author();
2734                 else
2735                         toggle_view_option(menu[selected].data, menu[selected].text);
2736         }
2739 static void
2740 maximize_view(struct view *view)
2742         memset(display, 0, sizeof(display));
2743         current_view = 0;
2744         display[current_view] = view;
2745         resize_display();
2746         redraw_display(FALSE);
2747         report("");
2751 /*
2752  * Navigation
2753  */
2755 static bool
2756 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2758         if (lineno >= view->lines)
2759                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2761         if (offset > lineno || offset + view->height <= lineno) {
2762                 unsigned long half = view->height / 2;
2764                 if (lineno > half)
2765                         offset = lineno - half;
2766                 else
2767                         offset = 0;
2768         }
2770         if (offset != view->offset || lineno != view->lineno) {
2771                 view->offset = offset;
2772                 view->lineno = lineno;
2773                 return TRUE;
2774         }
2776         return FALSE;
2779 /* Scrolling backend */
2780 static void
2781 do_scroll_view(struct view *view, int lines)
2783         bool redraw_current_line = FALSE;
2785         /* The rendering expects the new offset. */
2786         view->offset += lines;
2788         assert(0 <= view->offset && view->offset < view->lines);
2789         assert(lines);
2791         /* Move current line into the view. */
2792         if (view->lineno < view->offset) {
2793                 view->lineno = view->offset;
2794                 redraw_current_line = TRUE;
2795         } else if (view->lineno >= view->offset + view->height) {
2796                 view->lineno = view->offset + view->height - 1;
2797                 redraw_current_line = TRUE;
2798         }
2800         assert(view->offset <= view->lineno && view->lineno < view->lines);
2802         /* Redraw the whole screen if scrolling is pointless. */
2803         if (view->height < ABS(lines)) {
2804                 redraw_view(view);
2806         } else {
2807                 int line = lines > 0 ? view->height - lines : 0;
2808                 int end = line + ABS(lines);
2810                 scrollok(view->win, TRUE);
2811                 wscrl(view->win, lines);
2812                 scrollok(view->win, FALSE);
2814                 while (line < end && draw_view_line(view, line))
2815                         line++;
2817                 if (redraw_current_line)
2818                         draw_view_line(view, view->lineno - view->offset);
2819                 wnoutrefresh(view->win);
2820         }
2822         view->has_scrolled = TRUE;
2823         report("");
2826 /* Scroll frontend */
2827 static void
2828 scroll_view(struct view *view, enum request request)
2830         int lines = 1;
2832         assert(view_is_displayed(view));
2834         switch (request) {
2835         case REQ_SCROLL_LEFT:
2836                 if (view->yoffset == 0) {
2837                         report("Cannot scroll beyond the first column");
2838                         return;
2839                 }
2840                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2841                         view->yoffset = 0;
2842                 else
2843                         view->yoffset -= apply_step(opt_hscroll, view->width);
2844                 redraw_view_from(view, 0);
2845                 report("");
2846                 return;
2847         case REQ_SCROLL_RIGHT:
2848                 view->yoffset += apply_step(opt_hscroll, view->width);
2849                 redraw_view(view);
2850                 report("");
2851                 return;
2852         case REQ_SCROLL_PAGE_DOWN:
2853                 lines = view->height;
2854         case REQ_SCROLL_LINE_DOWN:
2855                 if (view->offset + lines > view->lines)
2856                         lines = view->lines - view->offset;
2858                 if (lines == 0 || view->offset + view->height >= view->lines) {
2859                         report("Cannot scroll beyond the last line");
2860                         return;
2861                 }
2862                 break;
2864         case REQ_SCROLL_PAGE_UP:
2865                 lines = view->height;
2866         case REQ_SCROLL_LINE_UP:
2867                 if (lines > view->offset)
2868                         lines = view->offset;
2870                 if (lines == 0) {
2871                         report("Cannot scroll beyond the first line");
2872                         return;
2873                 }
2875                 lines = -lines;
2876                 break;
2878         default:
2879                 die("request %d not handled in switch", request);
2880         }
2882         do_scroll_view(view, lines);
2885 /* Cursor moving */
2886 static void
2887 move_view(struct view *view, enum request request)
2889         int scroll_steps = 0;
2890         int steps;
2892         switch (request) {
2893         case REQ_MOVE_FIRST_LINE:
2894                 steps = -view->lineno;
2895                 break;
2897         case REQ_MOVE_LAST_LINE:
2898                 steps = view->lines - view->lineno - 1;
2899                 break;
2901         case REQ_MOVE_PAGE_UP:
2902                 steps = view->height > view->lineno
2903                       ? -view->lineno : -view->height;
2904                 break;
2906         case REQ_MOVE_PAGE_DOWN:
2907                 steps = view->lineno + view->height >= view->lines
2908                       ? view->lines - view->lineno - 1 : view->height;
2909                 break;
2911         case REQ_MOVE_UP:
2912                 steps = -1;
2913                 break;
2915         case REQ_MOVE_DOWN:
2916                 steps = 1;
2917                 break;
2919         default:
2920                 die("request %d not handled in switch", request);
2921         }
2923         if (steps <= 0 && view->lineno == 0) {
2924                 report("Cannot move beyond the first line");
2925                 return;
2927         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2928                 report("Cannot move beyond the last line");
2929                 return;
2930         }
2932         /* Move the current line */
2933         view->lineno += steps;
2934         assert(0 <= view->lineno && view->lineno < view->lines);
2936         /* Check whether the view needs to be scrolled */
2937         if (view->lineno < view->offset ||
2938             view->lineno >= view->offset + view->height) {
2939                 scroll_steps = steps;
2940                 if (steps < 0 && -steps > view->offset) {
2941                         scroll_steps = -view->offset;
2943                 } else if (steps > 0) {
2944                         if (view->lineno == view->lines - 1 &&
2945                             view->lines > view->height) {
2946                                 scroll_steps = view->lines - view->offset - 1;
2947                                 if (scroll_steps >= view->height)
2948                                         scroll_steps -= view->height - 1;
2949                         }
2950                 }
2951         }
2953         if (!view_is_displayed(view)) {
2954                 view->offset += scroll_steps;
2955                 assert(0 <= view->offset && view->offset < view->lines);
2956                 view->ops->select(view, &view->line[view->lineno]);
2957                 return;
2958         }
2960         /* Repaint the old "current" line if we be scrolling */
2961         if (ABS(steps) < view->height)
2962                 draw_view_line(view, view->lineno - steps - view->offset);
2964         if (scroll_steps) {
2965                 do_scroll_view(view, scroll_steps);
2966                 return;
2967         }
2969         /* Draw the current line */
2970         draw_view_line(view, view->lineno - view->offset);
2972         wnoutrefresh(view->win);
2973         report("");
2977 /*
2978  * Searching
2979  */
2981 static void search_view(struct view *view, enum request request);
2983 static bool
2984 grep_text(struct view *view, const char *text[])
2986         regmatch_t pmatch;
2987         size_t i;
2989         for (i = 0; text[i]; i++)
2990                 if (*text[i] &&
2991                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2992                         return TRUE;
2993         return FALSE;
2996 static void
2997 select_view_line(struct view *view, unsigned long lineno)
2999         unsigned long old_lineno = view->lineno;
3000         unsigned long old_offset = view->offset;
3002         if (goto_view_line(view, view->offset, lineno)) {
3003                 if (view_is_displayed(view)) {
3004                         if (old_offset != view->offset) {
3005                                 redraw_view(view);
3006                         } else {
3007                                 draw_view_line(view, old_lineno - view->offset);
3008                                 draw_view_line(view, view->lineno - view->offset);
3009                                 wnoutrefresh(view->win);
3010                         }
3011                 } else {
3012                         view->ops->select(view, &view->line[view->lineno]);
3013                 }
3014         }
3017 static void
3018 find_next(struct view *view, enum request request)
3020         unsigned long lineno = view->lineno;
3021         int direction;
3023         if (!*view->grep) {
3024                 if (!*opt_search)
3025                         report("No previous search");
3026                 else
3027                         search_view(view, request);
3028                 return;
3029         }
3031         switch (request) {
3032         case REQ_SEARCH:
3033         case REQ_FIND_NEXT:
3034                 direction = 1;
3035                 break;
3037         case REQ_SEARCH_BACK:
3038         case REQ_FIND_PREV:
3039                 direction = -1;
3040                 break;
3042         default:
3043                 return;
3044         }
3046         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3047                 lineno += direction;
3049         /* Note, lineno is unsigned long so will wrap around in which case it
3050          * will become bigger than view->lines. */
3051         for (; lineno < view->lines; lineno += direction) {
3052                 if (view->ops->grep(view, &view->line[lineno])) {
3053                         select_view_line(view, lineno);
3054                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3055                         return;
3056                 }
3057         }
3059         report("No match found for '%s'", view->grep);
3062 static void
3063 search_view(struct view *view, enum request request)
3065         int regex_err;
3067         if (view->regex) {
3068                 regfree(view->regex);
3069                 *view->grep = 0;
3070         } else {
3071                 view->regex = calloc(1, sizeof(*view->regex));
3072                 if (!view->regex)
3073                         return;
3074         }
3076         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3077         if (regex_err != 0) {
3078                 char buf[SIZEOF_STR] = "unknown error";
3080                 regerror(regex_err, view->regex, buf, sizeof(buf));
3081                 report("Search failed: %s", buf);
3082                 return;
3083         }
3085         string_copy(view->grep, opt_search);
3087         find_next(view, request);
3090 /*
3091  * Incremental updating
3092  */
3094 static void
3095 reset_view(struct view *view)
3097         int i;
3099         for (i = 0; i < view->lines; i++)
3100                 free(view->line[i].data);
3101         free(view->line);
3103         view->p_offset = view->offset;
3104         view->p_yoffset = view->yoffset;
3105         view->p_lineno = view->lineno;
3107         view->line = NULL;
3108         view->offset = 0;
3109         view->yoffset = 0;
3110         view->lines  = 0;
3111         view->lineno = 0;
3112         view->vid[0] = 0;
3113         view->update_secs = 0;
3116 static void
3117 free_argv(const char *argv[])
3119         int argc;
3121         for (argc = 0; argv[argc]; argc++)
3122                 free((void *) argv[argc]);
3125 static const char *
3126 format_arg(const char *name)
3128         static struct {
3129                 const char *name;
3130                 size_t namelen;
3131                 const char *value;
3132                 const char *value_if_empty;
3133         } vars[] = {
3134 #define FORMAT_VAR(name, value, value_if_empty) \
3135         { name, STRING_SIZE(name), value, value_if_empty }
3136                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3137                 FORMAT_VAR("%(file)",           opt_file,       ""),
3138                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3139                 FORMAT_VAR("%(head)",           ref_head,       ""),
3140                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3141                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3142                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3143         };
3144         int i;
3146         for (i = 0; i < ARRAY_SIZE(vars); i++)
3147                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3148                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3150         report("Unknown replacement: `%s`", name);
3151         return NULL;
3154 static bool
3155 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3157         char buf[SIZEOF_STR];
3158         int argc;
3159         bool noreplace = flags == FORMAT_NONE;
3161         free_argv(dst_argv);
3163         for (argc = 0; src_argv[argc]; argc++) {
3164                 const char *arg = src_argv[argc];
3165                 size_t bufpos = 0;
3167                 while (arg) {
3168                         char *next = strstr(arg, "%(");
3169                         int len = next - arg;
3170                         const char *value;
3172                         if (!next || noreplace) {
3173                                 len = strlen(arg);
3174                                 value = "";
3176                         } else {
3177                                 value = format_arg(next);
3179                                 if (!value) {
3180                                         return FALSE;
3181                                 }
3182                         }
3184                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3185                                 return FALSE;
3187                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3188                 }
3190                 dst_argv[argc] = strdup(buf);
3191                 if (!dst_argv[argc])
3192                         break;
3193         }
3195         dst_argv[argc] = NULL;
3197         return src_argv[argc] == NULL;
3200 static bool
3201 restore_view_position(struct view *view)
3203         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3204                 return FALSE;
3206         /* Changing the view position cancels the restoring. */
3207         /* FIXME: Changing back to the first line is not detected. */
3208         if (view->offset != 0 || view->lineno != 0) {
3209                 view->p_restore = FALSE;
3210                 return FALSE;
3211         }
3213         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3214             view_is_displayed(view))
3215                 werase(view->win);
3217         view->yoffset = view->p_yoffset;
3218         view->p_restore = FALSE;
3220         return TRUE;
3223 static void
3224 end_update(struct view *view, bool force)
3226         if (!view->pipe)
3227                 return;
3228         while (!view->ops->read(view, NULL))
3229                 if (!force)
3230                         return;
3231         if (force)
3232                 io_kill(view->pipe);
3233         io_done(view->pipe);
3234         view->pipe = NULL;
3237 static void
3238 setup_update(struct view *view, const char *vid)
3240         reset_view(view);
3241         string_copy_rev(view->vid, vid);
3242         view->pipe = &view->io;
3243         view->start_time = time(NULL);
3246 static bool
3247 prepare_update(struct view *view, const char *argv[], const char *dir)
3249         if (view->pipe)
3250                 end_update(view, TRUE);
3251         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3254 static bool
3255 prepare_update_file(struct view *view, const char *name)
3257         if (view->pipe)
3258                 end_update(view, TRUE);
3259         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3262 static bool
3263 begin_update(struct view *view, bool refresh)
3265         if (view->pipe)
3266                 end_update(view, TRUE);
3268         if (!refresh) {
3269                 if (view->ops->prepare) {
3270                         if (!view->ops->prepare(view))
3271                                 return FALSE;
3272                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3273                         return FALSE;
3274                 }
3276                 /* Put the current ref_* value to the view title ref
3277                  * member. This is needed by the blob view. Most other
3278                  * views sets it automatically after loading because the
3279                  * first line is a commit line. */
3280                 string_copy_rev(view->ref, view->id);
3281         }
3283         if (!io_start(&view->io))
3284                 return FALSE;
3286         setup_update(view, view->id);
3288         return TRUE;
3291 static bool
3292 update_view(struct view *view)
3294         char out_buffer[BUFSIZ * 2];
3295         char *line;
3296         /* Clear the view and redraw everything since the tree sorting
3297          * might have rearranged things. */
3298         bool redraw = view->lines == 0;
3299         bool can_read = TRUE;
3301         if (!view->pipe)
3302                 return TRUE;
3304         if (!io_can_read(view->pipe)) {
3305                 if (view->lines == 0 && view_is_displayed(view)) {
3306                         time_t secs = time(NULL) - view->start_time;
3308                         if (secs > 1 && secs > view->update_secs) {
3309                                 if (view->update_secs == 0)
3310                                         redraw_view(view);
3311                                 update_view_title(view);
3312                                 view->update_secs = secs;
3313                         }
3314                 }
3315                 return TRUE;
3316         }
3318         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3319                 if (opt_iconv_in != ICONV_NONE) {
3320                         ICONV_CONST char *inbuf = line;
3321                         size_t inlen = strlen(line) + 1;
3323                         char *outbuf = out_buffer;
3324                         size_t outlen = sizeof(out_buffer);
3326                         size_t ret;
3328                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3329                         if (ret != (size_t) -1)
3330                                 line = out_buffer;
3331                 }
3333                 if (!view->ops->read(view, line)) {
3334                         report("Allocation failure");
3335                         end_update(view, TRUE);
3336                         return FALSE;
3337                 }
3338         }
3340         {
3341                 unsigned long lines = view->lines;
3342                 int digits;
3344                 for (digits = 0; lines; digits++)
3345                         lines /= 10;
3347                 /* Keep the displayed view in sync with line number scaling. */
3348                 if (digits != view->digits) {
3349                         view->digits = digits;
3350                         if (opt_line_number || view->type == VIEW_BLAME)
3351                                 redraw = TRUE;
3352                 }
3353         }
3355         if (io_error(view->pipe)) {
3356                 report("Failed to read: %s", io_strerror(view->pipe));
3357                 end_update(view, TRUE);
3359         } else if (io_eof(view->pipe)) {
3360                 report("");
3361                 end_update(view, FALSE);
3362         }
3364         if (restore_view_position(view))
3365                 redraw = TRUE;
3367         if (!view_is_displayed(view))
3368                 return TRUE;
3370         if (redraw)
3371                 redraw_view_from(view, 0);
3372         else
3373                 redraw_view_dirty(view);
3375         /* Update the title _after_ the redraw so that if the redraw picks up a
3376          * commit reference in view->ref it'll be available here. */
3377         update_view_title(view);
3378         return TRUE;
3381 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3383 static struct line *
3384 add_line_data(struct view *view, void *data, enum line_type type)
3386         struct line *line;
3388         if (!realloc_lines(&view->line, view->lines, 1))
3389                 return NULL;
3391         line = &view->line[view->lines++];
3392         memset(line, 0, sizeof(*line));
3393         line->type = type;
3394         line->data = data;
3395         line->dirty = 1;
3397         return line;
3400 static struct line *
3401 add_line_text(struct view *view, const char *text, enum line_type type)
3403         char *data = text ? strdup(text) : NULL;
3405         return data ? add_line_data(view, data, type) : NULL;
3408 static struct line *
3409 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3411         char buf[SIZEOF_STR];
3412         va_list args;
3414         va_start(args, fmt);
3415         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3416                 buf[0] = 0;
3417         va_end(args);
3419         return buf[0] ? add_line_text(view, buf, type) : NULL;
3422 /*
3423  * View opening
3424  */
3426 enum open_flags {
3427         OPEN_DEFAULT = 0,       /* Use default view switching. */
3428         OPEN_SPLIT = 1,         /* Split current view. */
3429         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3430         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3431         OPEN_PREPARED = 32,     /* Open already prepared command. */
3432 };
3434 static void
3435 open_view(struct view *prev, enum request request, enum open_flags flags)
3437         bool split = !!(flags & OPEN_SPLIT);
3438         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3439         bool nomaximize = !!(flags & OPEN_REFRESH);
3440         struct view *view = VIEW(request);
3441         int nviews = displayed_views();
3442         struct view *base_view = display[0];
3444         if (view == prev && nviews == 1 && !reload) {
3445                 report("Already in %s view", view->name);
3446                 return;
3447         }
3449         if (view->git_dir && !opt_git_dir[0]) {
3450                 report("The %s view is disabled in pager view", view->name);
3451                 return;
3452         }
3454         if (split) {
3455                 display[1] = view;
3456                 current_view = 1;
3457         } else if (!nomaximize) {
3458                 /* Maximize the current view. */
3459                 memset(display, 0, sizeof(display));
3460                 current_view = 0;
3461                 display[current_view] = view;
3462         }
3464         /* No parent signals that this is the first loaded view. */
3465         if (prev && view != prev) {
3466                 view->parent = prev;
3467         }
3469         /* Resize the view when switching between split- and full-screen,
3470          * or when switching between two different full-screen views. */
3471         if (nviews != displayed_views() ||
3472             (nviews == 1 && base_view != display[0]))
3473                 resize_display();
3475         if (view->ops->open) {
3476                 if (view->pipe)
3477                         end_update(view, TRUE);
3478                 if (!view->ops->open(view)) {
3479                         report("Failed to load %s view", view->name);
3480                         return;
3481                 }
3482                 restore_view_position(view);
3484         } else if ((reload || strcmp(view->vid, view->id)) &&
3485                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3486                 report("Failed to load %s view", view->name);
3487                 return;
3488         }
3490         if (split && prev->lineno - prev->offset >= prev->height) {
3491                 /* Take the title line into account. */
3492                 int lines = prev->lineno - prev->offset - prev->height + 1;
3494                 /* Scroll the view that was split if the current line is
3495                  * outside the new limited view. */
3496                 do_scroll_view(prev, lines);
3497         }
3499         if (prev && view != prev && split && view_is_displayed(prev)) {
3500                 /* "Blur" the previous view. */
3501                 update_view_title(prev);
3502         }
3504         if (view->pipe && view->lines == 0) {
3505                 /* Clear the old view and let the incremental updating refill
3506                  * the screen. */
3507                 werase(view->win);
3508                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3509                 report("");
3510         } else if (view_is_displayed(view)) {
3511                 redraw_view(view);
3512                 report("");
3513         }
3516 static void
3517 open_external_viewer(const char *argv[], const char *dir)
3519         def_prog_mode();           /* save current tty modes */
3520         endwin();                  /* restore original tty modes */
3521         io_run_fg(argv, dir);
3522         fprintf(stderr, "Press Enter to continue");
3523         getc(opt_tty);
3524         reset_prog_mode();
3525         redraw_display(TRUE);
3528 static void
3529 open_mergetool(const char *file)
3531         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3533         open_external_viewer(mergetool_argv, opt_cdup);
3536 static void
3537 open_editor(const char *file)
3539         const char *editor_argv[] = { "vi", file, NULL };
3540         const char *editor;
3542         editor = getenv("GIT_EDITOR");
3543         if (!editor && *opt_editor)
3544                 editor = opt_editor;
3545         if (!editor)
3546                 editor = getenv("VISUAL");
3547         if (!editor)
3548                 editor = getenv("EDITOR");
3549         if (!editor)
3550                 editor = "vi";
3552         editor_argv[0] = editor;
3553         open_external_viewer(editor_argv, opt_cdup);
3556 static void
3557 open_run_request(enum request request)
3559         struct run_request *req = get_run_request(request);
3560         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3562         if (!req) {
3563                 report("Unknown run request");
3564                 return;
3565         }
3567         if (format_argv(argv, req->argv, FORMAT_ALL))
3568                 open_external_viewer(argv, NULL);
3569         free_argv(argv);
3572 /*
3573  * User request switch noodle
3574  */
3576 static int
3577 view_driver(struct view *view, enum request request)
3579         int i;
3581         if (request == REQ_NONE)
3582                 return TRUE;
3584         if (request > REQ_NONE) {
3585                 open_run_request(request);
3586                 /* FIXME: When all views can refresh always do this. */
3587                 if (view->refresh)
3588                         request = REQ_REFRESH;
3589                 else
3590                         return TRUE;
3591         }
3593         if (view && view->lines) {
3594                 request = view->ops->request(view, request, &view->line[view->lineno]);
3595                 if (request == REQ_NONE)
3596                         return TRUE;
3597         }
3599         switch (request) {
3600         case REQ_MOVE_UP:
3601         case REQ_MOVE_DOWN:
3602         case REQ_MOVE_PAGE_UP:
3603         case REQ_MOVE_PAGE_DOWN:
3604         case REQ_MOVE_FIRST_LINE:
3605         case REQ_MOVE_LAST_LINE:
3606                 move_view(view, request);
3607                 break;
3609         case REQ_SCROLL_LEFT:
3610         case REQ_SCROLL_RIGHT:
3611         case REQ_SCROLL_LINE_DOWN:
3612         case REQ_SCROLL_LINE_UP:
3613         case REQ_SCROLL_PAGE_DOWN:
3614         case REQ_SCROLL_PAGE_UP:
3615                 scroll_view(view, request);
3616                 break;
3618         case REQ_VIEW_BLAME:
3619                 if (!opt_file[0]) {
3620                         report("No file chosen, press %s to open tree view",
3621                                get_key(view->keymap, REQ_VIEW_TREE));
3622                         break;
3623                 }
3624                 open_view(view, request, OPEN_DEFAULT);
3625                 break;
3627         case REQ_VIEW_BLOB:
3628                 if (!ref_blob[0]) {
3629                         report("No file chosen, press %s to open tree view",
3630                                get_key(view->keymap, REQ_VIEW_TREE));
3631                         break;
3632                 }
3633                 open_view(view, request, OPEN_DEFAULT);
3634                 break;
3636         case REQ_VIEW_PAGER:
3637                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3638                         report("No pager content, press %s to run command from prompt",
3639                                get_key(view->keymap, REQ_PROMPT));
3640                         break;
3641                 }
3642                 open_view(view, request, OPEN_DEFAULT);
3643                 break;
3645         case REQ_VIEW_STAGE:
3646                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3647                         report("No stage content, press %s to open the status view and choose file",
3648                                get_key(view->keymap, REQ_VIEW_STATUS));
3649                         break;
3650                 }
3651                 open_view(view, request, OPEN_DEFAULT);
3652                 break;
3654         case REQ_VIEW_STATUS:
3655                 if (opt_is_inside_work_tree == FALSE) {
3656                         report("The status view requires a working tree");
3657                         break;
3658                 }
3659                 open_view(view, request, OPEN_DEFAULT);
3660                 break;
3662         case REQ_VIEW_MAIN:
3663         case REQ_VIEW_DIFF:
3664         case REQ_VIEW_LOG:
3665         case REQ_VIEW_TREE:
3666         case REQ_VIEW_HELP:
3667         case REQ_VIEW_BRANCH:
3668                 open_view(view, request, OPEN_DEFAULT);
3669                 break;
3671         case REQ_NEXT:
3672         case REQ_PREVIOUS:
3673                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3675                 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3676                     view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3677                     view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3678                     view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3679                     view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3680                         int line;
3682                         view = view->parent;
3683                         line = view->lineno;
3684                         move_view(view, request);
3685                         if (view_is_displayed(view))
3686                                 update_view_title(view);
3687                         if (line != view->lineno)
3688                                 view->ops->request(view, REQ_ENTER,
3689                                                    &view->line[view->lineno]);
3691                 } else {
3692                         move_view(view, request);
3693                 }
3694                 break;
3696         case REQ_VIEW_NEXT:
3697         {
3698                 int nviews = displayed_views();
3699                 int next_view = (current_view + 1) % nviews;
3701                 if (next_view == current_view) {
3702                         report("Only one view is displayed");
3703                         break;
3704                 }
3706                 current_view = next_view;
3707                 /* Blur out the title of the previous view. */
3708                 update_view_title(view);
3709                 report("");
3710                 break;
3711         }
3712         case REQ_REFRESH:
3713                 report("Refreshing is not yet supported for the %s view", view->name);
3714                 break;
3716         case REQ_MAXIMIZE:
3717                 if (displayed_views() == 2)
3718                         maximize_view(view);
3719                 break;
3721         case REQ_OPTIONS:
3722                 open_option_menu();
3723                 break;
3725         case REQ_TOGGLE_LINENO:
3726                 toggle_view_option(&opt_line_number, "line numbers");
3727                 break;
3729         case REQ_TOGGLE_DATE:
3730                 toggle_date();
3731                 break;
3733         case REQ_TOGGLE_AUTHOR:
3734                 toggle_author();
3735                 break;
3737         case REQ_TOGGLE_REV_GRAPH:
3738                 toggle_view_option(&opt_rev_graph, "revision graph display");
3739                 break;
3741         case REQ_TOGGLE_REFS:
3742                 toggle_view_option(&opt_show_refs, "reference display");
3743                 break;
3745         case REQ_TOGGLE_SORT_FIELD:
3746         case REQ_TOGGLE_SORT_ORDER:
3747                 report("Sorting is not yet supported for the %s view", view->name);
3748                 break;
3750         case REQ_SEARCH:
3751         case REQ_SEARCH_BACK:
3752                 search_view(view, request);
3753                 break;
3755         case REQ_FIND_NEXT:
3756         case REQ_FIND_PREV:
3757                 find_next(view, request);
3758                 break;
3760         case REQ_STOP_LOADING:
3761                 foreach_view(view, i) {
3762                         if (view->pipe)
3763                                 report("Stopped loading the %s view", view->name),
3764                         end_update(view, TRUE);
3765                 }
3766                 break;
3768         case REQ_SHOW_VERSION:
3769                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3770                 return TRUE;
3772         case REQ_SCREEN_REDRAW:
3773                 redraw_display(TRUE);
3774                 break;
3776         case REQ_EDIT:
3777                 report("Nothing to edit");
3778                 break;
3780         case REQ_ENTER:
3781                 report("Nothing to enter");
3782                 break;
3784         case REQ_VIEW_CLOSE:
3785                 /* XXX: Mark closed views by letting view->parent point to the
3786                  * view itself. Parents to closed view should never be
3787                  * followed. */
3788                 if (view->parent &&
3789                     view->parent->parent != view->parent) {
3790                         maximize_view(view->parent);
3791                         view->parent = view;
3792                         break;
3793                 }
3794                 /* Fall-through */
3795         case REQ_QUIT:
3796                 return FALSE;
3798         default:
3799                 report("Unknown key, press %s for help",
3800                        get_key(view->keymap, REQ_VIEW_HELP));
3801                 return TRUE;
3802         }
3804         return TRUE;
3808 /*
3809  * View backend utilities
3810  */
3812 enum sort_field {
3813         ORDERBY_NAME,
3814         ORDERBY_DATE,
3815         ORDERBY_AUTHOR,
3816 };
3818 struct sort_state {
3819         const enum sort_field *fields;
3820         size_t size, current;
3821         bool reverse;
3822 };
3824 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3825 #define get_sort_field(state) ((state).fields[(state).current])
3826 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3828 static void
3829 sort_view(struct view *view, enum request request, struct sort_state *state,
3830           int (*compare)(const void *, const void *))
3832         switch (request) {
3833         case REQ_TOGGLE_SORT_FIELD:
3834                 state->current = (state->current + 1) % state->size;
3835                 break;
3837         case REQ_TOGGLE_SORT_ORDER:
3838                 state->reverse = !state->reverse;
3839                 break;
3840         default:
3841                 die("Not a sort request");
3842         }
3844         qsort(view->line, view->lines, sizeof(*view->line), compare);
3845         redraw_view(view);
3848 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3850 /* Small author cache to reduce memory consumption. It uses binary
3851  * search to lookup or find place to position new entries. No entries
3852  * are ever freed. */
3853 static const char *
3854 get_author(const char *name)
3856         static const char **authors;
3857         static size_t authors_size;
3858         int from = 0, to = authors_size - 1;
3860         while (from <= to) {
3861                 size_t pos = (to + from) / 2;
3862                 int cmp = strcmp(name, authors[pos]);
3864                 if (!cmp)
3865                         return authors[pos];
3867                 if (cmp < 0)
3868                         to = pos - 1;
3869                 else
3870                         from = pos + 1;
3871         }
3873         if (!realloc_authors(&authors, authors_size, 1))
3874                 return NULL;
3875         name = strdup(name);
3876         if (!name)
3877                 return NULL;
3879         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3880         authors[from] = name;
3881         authors_size++;
3883         return name;
3886 static void
3887 parse_timesec(struct time *time, const char *sec)
3889         time->sec = (time_t) atol(sec);
3892 static void
3893 parse_timezone(struct time *time, const char *zone)
3895         long tz;
3897         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3898         tz += ('0' - zone[2]) * 60 * 60;
3899         tz += ('0' - zone[3]) * 60;
3900         tz += ('0' - zone[4]);
3902         if (zone[0] == '-')
3903                 tz = -tz;
3905         time->tz = tz;
3906         time->sec -= tz;
3909 /* Parse author lines where the name may be empty:
3910  *      author  <email@address.tld> 1138474660 +0100
3911  */
3912 static void
3913 parse_author_line(char *ident, const char **author, struct time *time)
3915         char *nameend = strchr(ident, '<');
3916         char *emailend = strchr(ident, '>');
3918         if (nameend && emailend)
3919                 *nameend = *emailend = 0;
3920         ident = chomp_string(ident);
3921         if (!*ident) {
3922                 if (nameend)
3923                         ident = chomp_string(nameend + 1);
3924                 if (!*ident)
3925                         ident = "Unknown";
3926         }
3928         *author = get_author(ident);
3930         /* Parse epoch and timezone */
3931         if (emailend && emailend[1] == ' ') {
3932                 char *secs = emailend + 2;
3933                 char *zone = strchr(secs, ' ');
3935                 parse_timesec(time, secs);
3937                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3938                         parse_timezone(time, zone + 1);
3939         }
3942 static bool
3943 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3945         char rev[SIZEOF_REV];
3946         const char *revlist_argv[] = {
3947                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3948         };
3949         struct menu_item *items;
3950         char text[SIZEOF_STR];
3951         bool ok = TRUE;
3952         int i;
3954         items = calloc(*parents + 1, sizeof(*items));
3955         if (!items)
3956                 return FALSE;
3958         for (i = 0; i < *parents; i++) {
3959                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3960                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3961                     !(items[i].text = strdup(text))) {
3962                         ok = FALSE;
3963                         break;
3964                 }
3965         }
3967         if (ok) {
3968                 *parents = 0;
3969                 ok = prompt_menu("Select parent", items, parents);
3970         }
3971         for (i = 0; items[i].text; i++)
3972                 free((char *) items[i].text);
3973         free(items);
3974         return ok;
3977 static bool
3978 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3980         char buf[SIZEOF_STR * 4];
3981         const char *revlist_argv[] = {
3982                 "git", "log", "--no-color", "-1",
3983                         "--pretty=format:%P", id, "--", path, NULL
3984         };
3985         int parents;
3987         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3988             (parents = strlen(buf) / 40) < 0) {
3989                 report("Failed to get parent information");
3990                 return FALSE;
3992         } else if (parents == 0) {
3993                 if (path)
3994                         report("Path '%s' does not exist in the parent", path);
3995                 else
3996                         report("The selected commit has no parents");
3997                 return FALSE;
3998         }
4000         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
4001                 return FALSE;
4003         string_copy_rev(rev, &buf[41 * parents]);
4004         return TRUE;
4007 /*
4008  * Pager backend
4009  */
4011 static bool
4012 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4014         char text[SIZEOF_STR];
4016         if (opt_line_number && draw_lineno(view, lineno))
4017                 return TRUE;
4019         string_expand(text, sizeof(text), line->data, opt_tab_size);
4020         draw_text(view, line->type, text, TRUE);
4021         return TRUE;
4024 static bool
4025 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4027         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4028         char ref[SIZEOF_STR];
4030         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4031                 return TRUE;
4033         /* This is the only fatal call, since it can "corrupt" the buffer. */
4034         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4035                 return FALSE;
4037         return TRUE;
4040 static void
4041 add_pager_refs(struct view *view, struct line *line)
4043         char buf[SIZEOF_STR];
4044         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4045         struct ref_list *list;
4046         size_t bufpos = 0, i;
4047         const char *sep = "Refs: ";
4048         bool is_tag = FALSE;
4050         assert(line->type == LINE_COMMIT);
4052         list = get_ref_list(commit_id);
4053         if (!list) {
4054                 if (view->type == VIEW_DIFF)
4055                         goto try_add_describe_ref;
4056                 return;
4057         }
4059         for (i = 0; i < list->size; i++) {
4060                 struct ref *ref = list->refs[i];
4061                 const char *fmt = ref->tag    ? "%s[%s]" :
4062                                   ref->remote ? "%s<%s>" : "%s%s";
4064                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4065                         return;
4066                 sep = ", ";
4067                 if (ref->tag)
4068                         is_tag = TRUE;
4069         }
4071         if (!is_tag && view->type == VIEW_DIFF) {
4072 try_add_describe_ref:
4073                 /* Add <tag>-g<commit_id> "fake" reference. */
4074                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4075                         return;
4076         }
4078         if (bufpos == 0)
4079                 return;
4081         add_line_text(view, buf, LINE_PP_REFS);
4084 static bool
4085 pager_read(struct view *view, char *data)
4087         struct line *line;
4089         if (!data)
4090                 return TRUE;
4092         line = add_line_text(view, data, get_line_type(data));
4093         if (!line)
4094                 return FALSE;
4096         if (line->type == LINE_COMMIT &&
4097             (view->type == VIEW_DIFF ||
4098              view->type == VIEW_LOG))
4099                 add_pager_refs(view, line);
4101         return TRUE;
4104 static enum request
4105 pager_request(struct view *view, enum request request, struct line *line)
4107         int split = 0;
4109         if (request != REQ_ENTER)
4110                 return request;
4112         if (line->type == LINE_COMMIT &&
4113            (view->type == VIEW_LOG ||
4114             view->type == VIEW_PAGER)) {
4115                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4116                 split = 1;
4117         }
4119         /* Always scroll the view even if it was split. That way
4120          * you can use Enter to scroll through the log view and
4121          * split open each commit diff. */
4122         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4124         /* FIXME: A minor workaround. Scrolling the view will call report("")
4125          * but if we are scrolling a non-current view this won't properly
4126          * update the view title. */
4127         if (split)
4128                 update_view_title(view);
4130         return REQ_NONE;
4133 static bool
4134 pager_grep(struct view *view, struct line *line)
4136         const char *text[] = { line->data, NULL };
4138         return grep_text(view, text);
4141 static void
4142 pager_select(struct view *view, struct line *line)
4144         if (line->type == LINE_COMMIT) {
4145                 char *text = (char *)line->data + STRING_SIZE("commit ");
4147                 if (view->type != VIEW_PAGER)
4148                         string_copy_rev(view->ref, text);
4149                 string_copy_rev(ref_commit, text);
4150         }
4153 static struct view_ops pager_ops = {
4154         "line",
4155         NULL,
4156         NULL,
4157         pager_read,
4158         pager_draw,
4159         pager_request,
4160         pager_grep,
4161         pager_select,
4162 };
4164 static const char *log_argv[SIZEOF_ARG] = {
4165         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4166 };
4168 static enum request
4169 log_request(struct view *view, enum request request, struct line *line)
4171         switch (request) {
4172         case REQ_REFRESH:
4173                 load_refs();
4174                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4175                 return REQ_NONE;
4176         default:
4177                 return pager_request(view, request, line);
4178         }
4181 static struct view_ops log_ops = {
4182         "line",
4183         log_argv,
4184         NULL,
4185         pager_read,
4186         pager_draw,
4187         log_request,
4188         pager_grep,
4189         pager_select,
4190 };
4192 static const char *diff_argv[SIZEOF_ARG] = {
4193         "git", "show", "--pretty=fuller", "--no-color", "--root",
4194                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4195 };
4197 static struct view_ops diff_ops = {
4198         "line",
4199         diff_argv,
4200         NULL,
4201         pager_read,
4202         pager_draw,
4203         pager_request,
4204         pager_grep,
4205         pager_select,
4206 };
4208 /*
4209  * Help backend
4210  */
4212 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4214 static bool
4215 help_open_keymap_title(struct view *view, enum keymap keymap)
4217         struct line *line;
4219         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4220                                help_keymap_hidden[keymap] ? '+' : '-',
4221                                enum_name(keymap_table[keymap]));
4222         if (line)
4223                 line->other = keymap;
4225         return help_keymap_hidden[keymap];
4228 static void
4229 help_open_keymap(struct view *view, enum keymap keymap)
4231         const char *group = NULL;
4232         char buf[SIZEOF_STR];
4233         size_t bufpos;
4234         bool add_title = TRUE;
4235         int i;
4237         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4238                 const char *key = NULL;
4240                 if (req_info[i].request == REQ_NONE)
4241                         continue;
4243                 if (!req_info[i].request) {
4244                         group = req_info[i].help;
4245                         continue;
4246                 }
4248                 key = get_keys(keymap, req_info[i].request, TRUE);
4249                 if (!key || !*key)
4250                         continue;
4252                 if (add_title && help_open_keymap_title(view, keymap))
4253                         return;
4254                 add_title = FALSE;
4256                 if (group) {
4257                         add_line_text(view, group, LINE_HELP_GROUP);
4258                         group = NULL;
4259                 }
4261                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4262                                 enum_name(req_info[i]), req_info[i].help);
4263         }
4265         group = "External commands:";
4267         for (i = 0; i < run_requests; i++) {
4268                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4269                 const char *key;
4270                 int argc;
4272                 if (!req || req->keymap != keymap)
4273                         continue;
4275                 key = get_key_name(req->key);
4276                 if (!*key)
4277                         key = "(no key defined)";
4279                 if (add_title && help_open_keymap_title(view, keymap))
4280                         return;
4281                 if (group) {
4282                         add_line_text(view, group, LINE_HELP_GROUP);
4283                         group = NULL;
4284                 }
4286                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4287                         if (!string_format_from(buf, &bufpos, "%s%s",
4288                                                 argc ? " " : "", req->argv[argc]))
4289                                 return;
4291                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4292         }
4295 static bool
4296 help_open(struct view *view)
4298         enum keymap keymap;
4300         reset_view(view);
4301         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4302         add_line_text(view, "", LINE_DEFAULT);
4304         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4305                 help_open_keymap(view, keymap);
4307         return TRUE;
4310 static enum request
4311 help_request(struct view *view, enum request request, struct line *line)
4313         switch (request) {
4314         case REQ_ENTER:
4315                 if (line->type == LINE_HELP_KEYMAP) {
4316                         help_keymap_hidden[line->other] =
4317                                 !help_keymap_hidden[line->other];
4318                         view->p_restore = TRUE;
4319                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4320                 }
4322                 return REQ_NONE;
4323         default:
4324                 return pager_request(view, request, line);
4325         }
4328 static struct view_ops help_ops = {
4329         "line",
4330         NULL,
4331         help_open,
4332         NULL,
4333         pager_draw,
4334         help_request,
4335         pager_grep,
4336         pager_select,
4337 };
4340 /*
4341  * Tree backend
4342  */
4344 struct tree_stack_entry {
4345         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4346         unsigned long lineno;           /* Line number to restore */
4347         char *name;                     /* Position of name in opt_path */
4348 };
4350 /* The top of the path stack. */
4351 static struct tree_stack_entry *tree_stack = NULL;
4352 unsigned long tree_lineno = 0;
4354 static void
4355 pop_tree_stack_entry(void)
4357         struct tree_stack_entry *entry = tree_stack;
4359         tree_lineno = entry->lineno;
4360         entry->name[0] = 0;
4361         tree_stack = entry->prev;
4362         free(entry);
4365 static void
4366 push_tree_stack_entry(const char *name, unsigned long lineno)
4368         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4369         size_t pathlen = strlen(opt_path);
4371         if (!entry)
4372                 return;
4374         entry->prev = tree_stack;
4375         entry->name = opt_path + pathlen;
4376         tree_stack = entry;
4378         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4379                 pop_tree_stack_entry();
4380                 return;
4381         }
4383         /* Move the current line to the first tree entry. */
4384         tree_lineno = 1;
4385         entry->lineno = lineno;
4388 /* Parse output from git-ls-tree(1):
4389  *
4390  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4391  */
4393 #define SIZEOF_TREE_ATTR \
4394         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4396 #define SIZEOF_TREE_MODE \
4397         STRING_SIZE("100644 ")
4399 #define TREE_ID_OFFSET \
4400         STRING_SIZE("100644 blob ")
4402 struct tree_entry {
4403         char id[SIZEOF_REV];
4404         mode_t mode;
4405         struct time time;               /* Date from the author ident. */
4406         const char *author;             /* Author of the commit. */
4407         char name[1];
4408 };
4410 static const char *
4411 tree_path(const struct line *line)
4413         return ((struct tree_entry *) line->data)->name;
4416 static int
4417 tree_compare_entry(const struct line *line1, const struct line *line2)
4419         if (line1->type != line2->type)
4420                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4421         return strcmp(tree_path(line1), tree_path(line2));
4424 static const enum sort_field tree_sort_fields[] = {
4425         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4426 };
4427 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4429 static int
4430 tree_compare(const void *l1, const void *l2)
4432         const struct line *line1 = (const struct line *) l1;
4433         const struct line *line2 = (const struct line *) l2;
4434         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4435         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4437         if (line1->type == LINE_TREE_HEAD)
4438                 return -1;
4439         if (line2->type == LINE_TREE_HEAD)
4440                 return 1;
4442         switch (get_sort_field(tree_sort_state)) {
4443         case ORDERBY_DATE:
4444                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4446         case ORDERBY_AUTHOR:
4447                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4449         case ORDERBY_NAME:
4450         default:
4451                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4452         }
4456 static struct line *
4457 tree_entry(struct view *view, enum line_type type, const char *path,
4458            const char *mode, const char *id)
4460         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4461         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4463         if (!entry || !line) {
4464                 free(entry);
4465                 return NULL;
4466         }
4468         strncpy(entry->name, path, strlen(path));
4469         if (mode)
4470                 entry->mode = strtoul(mode, NULL, 8);
4471         if (id)
4472                 string_copy_rev(entry->id, id);
4474         return line;
4477 static bool
4478 tree_read_date(struct view *view, char *text, bool *read_date)
4480         static const char *author_name;
4481         static struct time author_time;
4483         if (!text && *read_date) {
4484                 *read_date = FALSE;
4485                 return TRUE;
4487         } else if (!text) {
4488                 char *path = *opt_path ? opt_path : ".";
4489                 /* Find next entry to process */
4490                 const char *log_file[] = {
4491                         "git", "log", "--no-color", "--pretty=raw",
4492                                 "--cc", "--raw", view->id, "--", path, NULL
4493                 };
4494                 struct io io = {};
4496                 if (!view->lines) {
4497                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4498                         report("Tree is empty");
4499                         return TRUE;
4500                 }
4502                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4503                         report("Failed to load tree data");
4504                         return TRUE;
4505                 }
4507                 io_done(view->pipe);
4508                 view->io = io;
4509                 *read_date = TRUE;
4510                 return FALSE;
4512         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4513                 parse_author_line(text + STRING_SIZE("author "),
4514                                   &author_name, &author_time);
4516         } else if (*text == ':') {
4517                 char *pos;
4518                 size_t annotated = 1;
4519                 size_t i;
4521                 pos = strchr(text, '\t');
4522                 if (!pos)
4523                         return TRUE;
4524                 text = pos + 1;
4525                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4526                         text += strlen(opt_path);
4527                 pos = strchr(text, '/');
4528                 if (pos)
4529                         *pos = 0;
4531                 for (i = 1; i < view->lines; i++) {
4532                         struct line *line = &view->line[i];
4533                         struct tree_entry *entry = line->data;
4535                         annotated += !!entry->author;
4536                         if (entry->author || strcmp(entry->name, text))
4537                                 continue;
4539                         entry->author = author_name;
4540                         entry->time = author_time;
4541                         line->dirty = 1;
4542                         break;
4543                 }
4545                 if (annotated == view->lines)
4546                         io_kill(view->pipe);
4547         }
4548         return TRUE;
4551 static bool
4552 tree_read(struct view *view, char *text)
4554         static bool read_date = FALSE;
4555         struct tree_entry *data;
4556         struct line *entry, *line;
4557         enum line_type type;
4558         size_t textlen = text ? strlen(text) : 0;
4559         char *path = text + SIZEOF_TREE_ATTR;
4561         if (read_date || !text)
4562                 return tree_read_date(view, text, &read_date);
4564         if (textlen <= SIZEOF_TREE_ATTR)
4565                 return FALSE;
4566         if (view->lines == 0 &&
4567             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4568                 return FALSE;
4570         /* Strip the path part ... */
4571         if (*opt_path) {
4572                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4573                 size_t striplen = strlen(opt_path);
4575                 if (pathlen > striplen)
4576                         memmove(path, path + striplen,
4577                                 pathlen - striplen + 1);
4579                 /* Insert "link" to parent directory. */
4580                 if (view->lines == 1 &&
4581                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4582                         return FALSE;
4583         }
4585         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4586         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4587         if (!entry)
4588                 return FALSE;
4589         data = entry->data;
4591         /* Skip "Directory ..." and ".." line. */
4592         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4593                 if (tree_compare_entry(line, entry) <= 0)
4594                         continue;
4596                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4598                 line->data = data;
4599                 line->type = type;
4600                 for (; line <= entry; line++)
4601                         line->dirty = line->cleareol = 1;
4602                 return TRUE;
4603         }
4605         if (tree_lineno > view->lineno) {
4606                 view->lineno = tree_lineno;
4607                 tree_lineno = 0;
4608         }
4610         return TRUE;
4613 static bool
4614 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4616         struct tree_entry *entry = line->data;
4618         if (line->type == LINE_TREE_HEAD) {
4619                 if (draw_text(view, line->type, "Directory path /", TRUE))
4620                         return TRUE;
4621         } else {
4622                 if (draw_mode(view, entry->mode))
4623                         return TRUE;
4625                 if (opt_author && draw_author(view, entry->author))
4626                         return TRUE;
4628                 if (opt_date && draw_date(view, &entry->time))
4629                         return TRUE;
4630         }
4631         if (draw_text(view, line->type, entry->name, TRUE))
4632                 return TRUE;
4633         return TRUE;
4636 static void
4637 open_blob_editor()
4639         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4640         int fd = mkstemp(file);
4642         if (fd == -1)
4643                 report("Failed to create temporary file");
4644         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4645                 report("Failed to save blob data to file");
4646         else
4647                 open_editor(file);
4648         if (fd != -1)
4649                 unlink(file);
4652 static enum request
4653 tree_request(struct view *view, enum request request, struct line *line)
4655         enum open_flags flags;
4657         switch (request) {
4658         case REQ_VIEW_BLAME:
4659                 if (line->type != LINE_TREE_FILE) {
4660                         report("Blame only supported for files");
4661                         return REQ_NONE;
4662                 }
4664                 string_copy(opt_ref, view->vid);
4665                 return request;
4667         case REQ_EDIT:
4668                 if (line->type != LINE_TREE_FILE) {
4669                         report("Edit only supported for files");
4670                 } else if (!is_head_commit(view->vid)) {
4671                         open_blob_editor();
4672                 } else {
4673                         open_editor(opt_file);
4674                 }
4675                 return REQ_NONE;
4677         case REQ_TOGGLE_SORT_FIELD:
4678         case REQ_TOGGLE_SORT_ORDER:
4679                 sort_view(view, request, &tree_sort_state, tree_compare);
4680                 return REQ_NONE;
4682         case REQ_PARENT:
4683                 if (!*opt_path) {
4684                         /* quit view if at top of tree */
4685                         return REQ_VIEW_CLOSE;
4686                 }
4687                 /* fake 'cd  ..' */
4688                 line = &view->line[1];
4689                 break;
4691         case REQ_ENTER:
4692                 break;
4694         default:
4695                 return request;
4696         }
4698         /* Cleanup the stack if the tree view is at a different tree. */
4699         while (!*opt_path && tree_stack)
4700                 pop_tree_stack_entry();
4702         switch (line->type) {
4703         case LINE_TREE_DIR:
4704                 /* Depending on whether it is a subdirectory or parent link
4705                  * mangle the path buffer. */
4706                 if (line == &view->line[1] && *opt_path) {
4707                         pop_tree_stack_entry();
4709                 } else {
4710                         const char *basename = tree_path(line);
4712                         push_tree_stack_entry(basename, view->lineno);
4713                 }
4715                 /* Trees and subtrees share the same ID, so they are not not
4716                  * unique like blobs. */
4717                 flags = OPEN_RELOAD;
4718                 request = REQ_VIEW_TREE;
4719                 break;
4721         case LINE_TREE_FILE:
4722                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4723                 request = REQ_VIEW_BLOB;
4724                 break;
4726         default:
4727                 return REQ_NONE;
4728         }
4730         open_view(view, request, flags);
4731         if (request == REQ_VIEW_TREE)
4732                 view->lineno = tree_lineno;
4734         return REQ_NONE;
4737 static bool
4738 tree_grep(struct view *view, struct line *line)
4740         struct tree_entry *entry = line->data;
4741         const char *text[] = {
4742                 entry->name,
4743                 opt_author ? entry->author : "",
4744                 mkdate(&entry->time, opt_date),
4745                 NULL
4746         };
4748         return grep_text(view, text);
4751 static void
4752 tree_select(struct view *view, struct line *line)
4754         struct tree_entry *entry = line->data;
4756         if (line->type == LINE_TREE_FILE) {
4757                 string_copy_rev(ref_blob, entry->id);
4758                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4760         } else if (line->type != LINE_TREE_DIR) {
4761                 return;
4762         }
4764         string_copy_rev(view->ref, entry->id);
4767 static bool
4768 tree_prepare(struct view *view)
4770         if (view->lines == 0 && opt_prefix[0]) {
4771                 char *pos = opt_prefix;
4773                 while (pos && *pos) {
4774                         char *end = strchr(pos, '/');
4776                         if (end)
4777                                 *end = 0;
4778                         push_tree_stack_entry(pos, 0);
4779                         pos = end;
4780                         if (end) {
4781                                 *end = '/';
4782                                 pos++;
4783                         }
4784                 }
4786         } else if (strcmp(view->vid, view->id)) {
4787                 opt_path[0] = 0;
4788         }
4790         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4793 static const char *tree_argv[SIZEOF_ARG] = {
4794         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4795 };
4797 static struct view_ops tree_ops = {
4798         "file",
4799         tree_argv,
4800         NULL,
4801         tree_read,
4802         tree_draw,
4803         tree_request,
4804         tree_grep,
4805         tree_select,
4806         tree_prepare,
4807 };
4809 static bool
4810 blob_read(struct view *view, char *line)
4812         if (!line)
4813                 return TRUE;
4814         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4817 static enum request
4818 blob_request(struct view *view, enum request request, struct line *line)
4820         switch (request) {
4821         case REQ_EDIT:
4822                 open_blob_editor();
4823                 return REQ_NONE;
4824         default:
4825                 return pager_request(view, request, line);
4826         }
4829 static const char *blob_argv[SIZEOF_ARG] = {
4830         "git", "cat-file", "blob", "%(blob)", NULL
4831 };
4833 static struct view_ops blob_ops = {
4834         "line",
4835         blob_argv,
4836         NULL,
4837         blob_read,
4838         pager_draw,
4839         blob_request,
4840         pager_grep,
4841         pager_select,
4842 };
4844 /*
4845  * Blame backend
4846  *
4847  * Loading the blame view is a two phase job:
4848  *
4849  *  1. File content is read either using opt_file from the
4850  *     filesystem or using git-cat-file.
4851  *  2. Then blame information is incrementally added by
4852  *     reading output from git-blame.
4853  */
4855 static const char *blame_head_argv[] = {
4856         "git", "blame", "--incremental", "--", "%(file)", NULL
4857 };
4859 static const char *blame_ref_argv[] = {
4860         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4861 };
4863 static const char *blame_cat_file_argv[] = {
4864         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4865 };
4867 struct blame_commit {
4868         char id[SIZEOF_REV];            /* SHA1 ID. */
4869         char title[128];                /* First line of the commit message. */
4870         const char *author;             /* Author of the commit. */
4871         struct time time;               /* Date from the author ident. */
4872         char filename[128];             /* Name of file. */
4873         bool has_previous;              /* Was a "previous" line detected. */
4874 };
4876 struct blame {
4877         struct blame_commit *commit;
4878         unsigned long lineno;
4879         char text[1];
4880 };
4882 static bool
4883 blame_open(struct view *view)
4885         char path[SIZEOF_STR];
4887         if (!view->parent && *opt_prefix) {
4888                 string_copy(path, opt_file);
4889                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4890                         return FALSE;
4891         }
4893         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4894                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4895                         return FALSE;
4896         }
4898         setup_update(view, opt_file);
4899         string_format(view->ref, "%s ...", opt_file);
4901         return TRUE;
4904 static struct blame_commit *
4905 get_blame_commit(struct view *view, const char *id)
4907         size_t i;
4909         for (i = 0; i < view->lines; i++) {
4910                 struct blame *blame = view->line[i].data;
4912                 if (!blame->commit)
4913                         continue;
4915                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4916                         return blame->commit;
4917         }
4919         {
4920                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4922                 if (commit)
4923                         string_ncopy(commit->id, id, SIZEOF_REV);
4924                 return commit;
4925         }
4928 static bool
4929 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4931         const char *pos = *posref;
4933         *posref = NULL;
4934         pos = strchr(pos + 1, ' ');
4935         if (!pos || !isdigit(pos[1]))
4936                 return FALSE;
4937         *number = atoi(pos + 1);
4938         if (*number < min || *number > max)
4939                 return FALSE;
4941         *posref = pos;
4942         return TRUE;
4945 static struct blame_commit *
4946 parse_blame_commit(struct view *view, const char *text, int *blamed)
4948         struct blame_commit *commit;
4949         struct blame *blame;
4950         const char *pos = text + SIZEOF_REV - 2;
4951         size_t orig_lineno = 0;
4952         size_t lineno;
4953         size_t group;
4955         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4956                 return NULL;
4958         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4959             !parse_number(&pos, &lineno, 1, view->lines) ||
4960             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4961                 return NULL;
4963         commit = get_blame_commit(view, text);
4964         if (!commit)
4965                 return NULL;
4967         *blamed += group;
4968         while (group--) {
4969                 struct line *line = &view->line[lineno + group - 1];
4971                 blame = line->data;
4972                 blame->commit = commit;
4973                 blame->lineno = orig_lineno + group - 1;
4974                 line->dirty = 1;
4975         }
4977         return commit;
4980 static bool
4981 blame_read_file(struct view *view, const char *line, bool *read_file)
4983         if (!line) {
4984                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4985                 struct io io = {};
4987                 if (view->lines == 0 && !view->parent)
4988                         die("No blame exist for %s", view->vid);
4990                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4991                         report("Failed to load blame data");
4992                         return TRUE;
4993                 }
4995                 io_done(view->pipe);
4996                 view->io = io;
4997                 *read_file = FALSE;
4998                 return FALSE;
5000         } else {
5001                 size_t linelen = strlen(line);
5002                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5004                 if (!blame)
5005                         return FALSE;
5007                 blame->commit = NULL;
5008                 strncpy(blame->text, line, linelen);
5009                 blame->text[linelen] = 0;
5010                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5011         }
5014 static bool
5015 match_blame_header(const char *name, char **line)
5017         size_t namelen = strlen(name);
5018         bool matched = !strncmp(name, *line, namelen);
5020         if (matched)
5021                 *line += namelen;
5023         return matched;
5026 static bool
5027 blame_read(struct view *view, char *line)
5029         static struct blame_commit *commit = NULL;
5030         static int blamed = 0;
5031         static bool read_file = TRUE;
5033         if (read_file)
5034                 return blame_read_file(view, line, &read_file);
5036         if (!line) {
5037                 /* Reset all! */
5038                 commit = NULL;
5039                 blamed = 0;
5040                 read_file = TRUE;
5041                 string_format(view->ref, "%s", view->vid);
5042                 if (view_is_displayed(view)) {
5043                         update_view_title(view);
5044                         redraw_view_from(view, 0);
5045                 }
5046                 return TRUE;
5047         }
5049         if (!commit) {
5050                 commit = parse_blame_commit(view, line, &blamed);
5051                 string_format(view->ref, "%s %2d%%", view->vid,
5052                               view->lines ? blamed * 100 / view->lines : 0);
5054         } else if (match_blame_header("author ", &line)) {
5055                 commit->author = get_author(line);
5057         } else if (match_blame_header("author-time ", &line)) {
5058                 parse_timesec(&commit->time, line);
5060         } else if (match_blame_header("author-tz ", &line)) {
5061                 parse_timezone(&commit->time, line);
5063         } else if (match_blame_header("summary ", &line)) {
5064                 string_ncopy(commit->title, line, strlen(line));
5066         } else if (match_blame_header("previous ", &line)) {
5067                 commit->has_previous = TRUE;
5069         } else if (match_blame_header("filename ", &line)) {
5070                 string_ncopy(commit->filename, line, strlen(line));
5071                 commit = NULL;
5072         }
5074         return TRUE;
5077 static bool
5078 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5080         struct blame *blame = line->data;
5081         struct time *time = NULL;
5082         const char *id = NULL, *author = NULL;
5083         char text[SIZEOF_STR];
5085         if (blame->commit && *blame->commit->filename) {
5086                 id = blame->commit->id;
5087                 author = blame->commit->author;
5088                 time = &blame->commit->time;
5089         }
5091         if (opt_date && draw_date(view, time))
5092                 return TRUE;
5094         if (opt_author && draw_author(view, author))
5095                 return TRUE;
5097         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5098                 return TRUE;
5100         if (draw_lineno(view, lineno))
5101                 return TRUE;
5103         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5104         draw_text(view, LINE_DEFAULT, text, TRUE);
5105         return TRUE;
5108 static bool
5109 check_blame_commit(struct blame *blame, bool check_null_id)
5111         if (!blame->commit)
5112                 report("Commit data not loaded yet");
5113         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5114                 report("No commit exist for the selected line");
5115         else
5116                 return TRUE;
5117         return FALSE;
5120 static void
5121 setup_blame_parent_line(struct view *view, struct blame *blame)
5123         const char *diff_tree_argv[] = {
5124                 "git", "diff-tree", "-U0", blame->commit->id,
5125                         "--", blame->commit->filename, NULL
5126         };
5127         struct io io = {};
5128         int parent_lineno = -1;
5129         int blamed_lineno = -1;
5130         char *line;
5132         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5133                 return;
5135         while ((line = io_get(&io, '\n', TRUE))) {
5136                 if (*line == '@') {
5137                         char *pos = strchr(line, '+');
5139                         parent_lineno = atoi(line + 4);
5140                         if (pos)
5141                                 blamed_lineno = atoi(pos + 1);
5143                 } else if (*line == '+' && parent_lineno != -1) {
5144                         if (blame->lineno == blamed_lineno - 1 &&
5145                             !strcmp(blame->text, line + 1)) {
5146                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5147                                 break;
5148                         }
5149                         blamed_lineno++;
5150                 }
5151         }
5153         io_done(&io);
5156 static enum request
5157 blame_request(struct view *view, enum request request, struct line *line)
5159         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5160         struct blame *blame = line->data;
5162         switch (request) {
5163         case REQ_VIEW_BLAME:
5164                 if (check_blame_commit(blame, TRUE)) {
5165                         string_copy(opt_ref, blame->commit->id);
5166                         string_copy(opt_file, blame->commit->filename);
5167                         if (blame->lineno)
5168                                 view->lineno = blame->lineno;
5169                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5170                 }
5171                 break;
5173         case REQ_PARENT:
5174                 if (check_blame_commit(blame, TRUE) &&
5175                     select_commit_parent(blame->commit->id, opt_ref,
5176                                          blame->commit->filename)) {
5177                         string_copy(opt_file, blame->commit->filename);
5178                         setup_blame_parent_line(view, blame);
5179                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5180                 }
5181                 break;
5183         case REQ_ENTER:
5184                 if (!check_blame_commit(blame, FALSE))
5185                         break;
5187                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5188                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5189                         break;
5191                 if (!strcmp(blame->commit->id, NULL_ID)) {
5192                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5193                         const char *diff_index_argv[] = {
5194                                 "git", "diff-index", "--root", "--patch-with-stat",
5195                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5196                         };
5198                         if (!blame->commit->has_previous) {
5199                                 diff_index_argv[1] = "diff";
5200                                 diff_index_argv[2] = "--no-color";
5201                                 diff_index_argv[6] = "--";
5202                                 diff_index_argv[7] = "/dev/null";
5203                         }
5205                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5206                                 report("Failed to allocate diff command");
5207                                 break;
5208                         }
5209                         flags |= OPEN_PREPARED;
5210                 }
5212                 open_view(view, REQ_VIEW_DIFF, flags);
5213                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5214                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5215                 break;
5217         default:
5218                 return request;
5219         }
5221         return REQ_NONE;
5224 static bool
5225 blame_grep(struct view *view, struct line *line)
5227         struct blame *blame = line->data;
5228         struct blame_commit *commit = blame->commit;
5229         const char *text[] = {
5230                 blame->text,
5231                 commit ? commit->title : "",
5232                 commit ? commit->id : "",
5233                 commit && opt_author ? commit->author : "",
5234                 commit ? mkdate(&commit->time, opt_date) : "",
5235                 NULL
5236         };
5238         return grep_text(view, text);
5241 static void
5242 blame_select(struct view *view, struct line *line)
5244         struct blame *blame = line->data;
5245         struct blame_commit *commit = blame->commit;
5247         if (!commit)
5248                 return;
5250         if (!strcmp(commit->id, NULL_ID))
5251                 string_ncopy(ref_commit, "HEAD", 4);
5252         else
5253                 string_copy_rev(ref_commit, commit->id);
5256 static struct view_ops blame_ops = {
5257         "line",
5258         NULL,
5259         blame_open,
5260         blame_read,
5261         blame_draw,
5262         blame_request,
5263         blame_grep,
5264         blame_select,
5265 };
5267 /*
5268  * Branch backend
5269  */
5271 struct branch {
5272         const char *author;             /* Author of the last commit. */
5273         struct time time;               /* Date of the last activity. */
5274         const struct ref *ref;          /* Name and commit ID information. */
5275 };
5277 static const struct ref branch_all;
5279 static const enum sort_field branch_sort_fields[] = {
5280         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5281 };
5282 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5284 static int
5285 branch_compare(const void *l1, const void *l2)
5287         const struct branch *branch1 = ((const struct line *) l1)->data;
5288         const struct branch *branch2 = ((const struct line *) l2)->data;
5290         switch (get_sort_field(branch_sort_state)) {
5291         case ORDERBY_DATE:
5292                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5294         case ORDERBY_AUTHOR:
5295                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5297         case ORDERBY_NAME:
5298         default:
5299                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5300         }
5303 static bool
5304 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5306         struct branch *branch = line->data;
5307         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5309         if (opt_date && draw_date(view, &branch->time))
5310                 return TRUE;
5312         if (opt_author && draw_author(view, branch->author))
5313                 return TRUE;
5315         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5316         return TRUE;
5319 static enum request
5320 branch_request(struct view *view, enum request request, struct line *line)
5322         struct branch *branch = line->data;
5324         switch (request) {
5325         case REQ_REFRESH:
5326                 load_refs();
5327                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5328                 return REQ_NONE;
5330         case REQ_TOGGLE_SORT_FIELD:
5331         case REQ_TOGGLE_SORT_ORDER:
5332                 sort_view(view, request, &branch_sort_state, branch_compare);
5333                 return REQ_NONE;
5335         case REQ_ENTER:
5336                 if (branch->ref == &branch_all) {
5337                         const char *all_branches_argv[] = {
5338                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5339                                       "--topo-order", "--all", NULL
5340                         };
5341                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5343                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5344                                 report("Failed to load view of all branches");
5345                                 return REQ_NONE;
5346                         }
5347                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5348                 } else {
5349                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5350                 }
5351                 return REQ_NONE;
5353         default:
5354                 return request;
5355         }
5358 static bool
5359 branch_read(struct view *view, char *line)
5361         static char id[SIZEOF_REV];
5362         struct branch *reference;
5363         size_t i;
5365         if (!line)
5366                 return TRUE;
5368         switch (get_line_type(line)) {
5369         case LINE_COMMIT:
5370                 string_copy_rev(id, line + STRING_SIZE("commit "));
5371                 return TRUE;
5373         case LINE_AUTHOR:
5374                 for (i = 0, reference = NULL; i < view->lines; i++) {
5375                         struct branch *branch = view->line[i].data;
5377                         if (strcmp(branch->ref->id, id))
5378                                 continue;
5380                         view->line[i].dirty = TRUE;
5381                         if (reference) {
5382                                 branch->author = reference->author;
5383                                 branch->time = reference->time;
5384                                 continue;
5385                         }
5387                         parse_author_line(line + STRING_SIZE("author "),
5388                                           &branch->author, &branch->time);
5389                         reference = branch;
5390                 }
5391                 return TRUE;
5393         default:
5394                 return TRUE;
5395         }
5399 static bool
5400 branch_open_visitor(void *data, const struct ref *ref)
5402         struct view *view = data;
5403         struct branch *branch;
5405         if (ref->tag || ref->ltag || ref->remote)
5406                 return TRUE;
5408         branch = calloc(1, sizeof(*branch));
5409         if (!branch)
5410                 return FALSE;
5412         branch->ref = ref;
5413         return !!add_line_data(view, branch, LINE_DEFAULT);
5416 static bool
5417 branch_open(struct view *view)
5419         const char *branch_log[] = {
5420                 "git", "log", "--no-color", "--pretty=raw",
5421                         "--simplify-by-decoration", "--all", NULL
5422         };
5424         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5425                 report("Failed to load branch data");
5426                 return TRUE;
5427         }
5429         setup_update(view, view->id);
5430         branch_open_visitor(view, &branch_all);
5431         foreach_ref(branch_open_visitor, view);
5432         view->p_restore = TRUE;
5434         return TRUE;
5437 static bool
5438 branch_grep(struct view *view, struct line *line)
5440         struct branch *branch = line->data;
5441         const char *text[] = {
5442                 branch->ref->name,
5443                 branch->author,
5444                 NULL
5445         };
5447         return grep_text(view, text);
5450 static void
5451 branch_select(struct view *view, struct line *line)
5453         struct branch *branch = line->data;
5455         string_copy_rev(view->ref, branch->ref->id);
5456         string_copy_rev(ref_commit, branch->ref->id);
5457         string_copy_rev(ref_head, branch->ref->id);
5458         string_copy_rev(ref_branch, branch->ref->name);
5461 static struct view_ops branch_ops = {
5462         "branch",
5463         NULL,
5464         branch_open,
5465         branch_read,
5466         branch_draw,
5467         branch_request,
5468         branch_grep,
5469         branch_select,
5470 };
5472 /*
5473  * Status backend
5474  */
5476 struct status {
5477         char status;
5478         struct {
5479                 mode_t mode;
5480                 char rev[SIZEOF_REV];
5481                 char name[SIZEOF_STR];
5482         } old;
5483         struct {
5484                 mode_t mode;
5485                 char rev[SIZEOF_REV];
5486                 char name[SIZEOF_STR];
5487         } new;
5488 };
5490 static char status_onbranch[SIZEOF_STR];
5491 static struct status stage_status;
5492 static enum line_type stage_line_type;
5493 static size_t stage_chunks;
5494 static int *stage_chunk;
5496 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5498 /* This should work even for the "On branch" line. */
5499 static inline bool
5500 status_has_none(struct view *view, struct line *line)
5502         return line < view->line + view->lines && !line[1].data;
5505 /* Get fields from the diff line:
5506  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5507  */
5508 static inline bool
5509 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5511         const char *old_mode = buf +  1;
5512         const char *new_mode = buf +  8;
5513         const char *old_rev  = buf + 15;
5514         const char *new_rev  = buf + 56;
5515         const char *status   = buf + 97;
5517         if (bufsize < 98 ||
5518             old_mode[-1] != ':' ||
5519             new_mode[-1] != ' ' ||
5520             old_rev[-1]  != ' ' ||
5521             new_rev[-1]  != ' ' ||
5522             status[-1]   != ' ')
5523                 return FALSE;
5525         file->status = *status;
5527         string_copy_rev(file->old.rev, old_rev);
5528         string_copy_rev(file->new.rev, new_rev);
5530         file->old.mode = strtoul(old_mode, NULL, 8);
5531         file->new.mode = strtoul(new_mode, NULL, 8);
5533         file->old.name[0] = file->new.name[0] = 0;
5535         return TRUE;
5538 static bool
5539 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5541         struct status *unmerged = NULL;
5542         char *buf;
5543         struct io io = {};
5545         if (!io_run(&io, argv, opt_cdup, IO_RD))
5546                 return FALSE;
5548         add_line_data(view, NULL, type);
5550         while ((buf = io_get(&io, 0, TRUE))) {
5551                 struct status *file = unmerged;
5553                 if (!file) {
5554                         file = calloc(1, sizeof(*file));
5555                         if (!file || !add_line_data(view, file, type))
5556                                 goto error_out;
5557                 }
5559                 /* Parse diff info part. */
5560                 if (status) {
5561                         file->status = status;
5562                         if (status == 'A')
5563                                 string_copy(file->old.rev, NULL_ID);
5565                 } else if (!file->status || file == unmerged) {
5566                         if (!status_get_diff(file, buf, strlen(buf)))
5567                                 goto error_out;
5569                         buf = io_get(&io, 0, TRUE);
5570                         if (!buf)
5571                                 break;
5573                         /* Collapse all modified entries that follow an
5574                          * associated unmerged entry. */
5575                         if (unmerged == file) {
5576                                 unmerged->status = 'U';
5577                                 unmerged = NULL;
5578                         } else if (file->status == 'U') {
5579                                 unmerged = file;
5580                         }
5581                 }
5583                 /* Grab the old name for rename/copy. */
5584                 if (!*file->old.name &&
5585                     (file->status == 'R' || file->status == 'C')) {
5586                         string_ncopy(file->old.name, buf, strlen(buf));
5588                         buf = io_get(&io, 0, TRUE);
5589                         if (!buf)
5590                                 break;
5591                 }
5593                 /* git-ls-files just delivers a NUL separated list of
5594                  * file names similar to the second half of the
5595                  * git-diff-* output. */
5596                 string_ncopy(file->new.name, buf, strlen(buf));
5597                 if (!*file->old.name)
5598                         string_copy(file->old.name, file->new.name);
5599                 file = NULL;
5600         }
5602         if (io_error(&io)) {
5603 error_out:
5604                 io_done(&io);
5605                 return FALSE;
5606         }
5608         if (!view->line[view->lines - 1].data)
5609                 add_line_data(view, NULL, LINE_STAT_NONE);
5611         io_done(&io);
5612         return TRUE;
5615 /* Don't show unmerged entries in the staged section. */
5616 static const char *status_diff_index_argv[] = {
5617         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5618                              "--cached", "-M", "HEAD", NULL
5619 };
5621 static const char *status_diff_files_argv[] = {
5622         "git", "diff-files", "-z", NULL
5623 };
5625 static const char *status_list_other_argv[] = {
5626         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5627 };
5629 static const char *status_list_no_head_argv[] = {
5630         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5631 };
5633 static const char *update_index_argv[] = {
5634         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5635 };
5637 /* Restore the previous line number to stay in the context or select a
5638  * line with something that can be updated. */
5639 static void
5640 status_restore(struct view *view)
5642         if (view->p_lineno >= view->lines)
5643                 view->p_lineno = view->lines - 1;
5644         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5645                 view->p_lineno++;
5646         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5647                 view->p_lineno--;
5649         /* If the above fails, always skip the "On branch" line. */
5650         if (view->p_lineno < view->lines)
5651                 view->lineno = view->p_lineno;
5652         else
5653                 view->lineno = 1;
5655         if (view->lineno < view->offset)
5656                 view->offset = view->lineno;
5657         else if (view->offset + view->height <= view->lineno)
5658                 view->offset = view->lineno - view->height + 1;
5660         view->p_restore = FALSE;
5663 static void
5664 status_update_onbranch(void)
5666         static const char *paths[][2] = {
5667                 { "rebase-apply/rebasing",      "Rebasing" },
5668                 { "rebase-apply/applying",      "Applying mailbox" },
5669                 { "rebase-apply/",              "Rebasing mailbox" },
5670                 { "rebase-merge/interactive",   "Interactive rebase" },
5671                 { "rebase-merge/",              "Rebase merge" },
5672                 { "MERGE_HEAD",                 "Merging" },
5673                 { "BISECT_LOG",                 "Bisecting" },
5674                 { "HEAD",                       "On branch" },
5675         };
5676         char buf[SIZEOF_STR];
5677         struct stat stat;
5678         int i;
5680         if (is_initial_commit()) {
5681                 string_copy(status_onbranch, "Initial commit");
5682                 return;
5683         }
5685         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5686                 char *head = opt_head;
5688                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5689                     lstat(buf, &stat) < 0)
5690                         continue;
5692                 if (!*opt_head) {
5693                         struct io io = {};
5695                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5696                             io_read_buf(&io, buf, sizeof(buf))) {
5697                                 head = buf;
5698                                 if (!prefixcmp(head, "refs/heads/"))
5699                                         head += STRING_SIZE("refs/heads/");
5700                         }
5701                 }
5703                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5704                         string_copy(status_onbranch, opt_head);
5705                 return;
5706         }
5708         string_copy(status_onbranch, "Not currently on any branch");
5711 /* First parse staged info using git-diff-index(1), then parse unstaged
5712  * info using git-diff-files(1), and finally untracked files using
5713  * git-ls-files(1). */
5714 static bool
5715 status_open(struct view *view)
5717         reset_view(view);
5719         add_line_data(view, NULL, LINE_STAT_HEAD);
5720         status_update_onbranch();
5722         io_run_bg(update_index_argv);
5724         if (is_initial_commit()) {
5725                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5726                         return FALSE;
5727         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5728                 return FALSE;
5729         }
5731         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5732             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5733                 return FALSE;
5735         /* Restore the exact position or use the specialized restore
5736          * mode? */
5737         if (!view->p_restore)
5738                 status_restore(view);
5739         return TRUE;
5742 static bool
5743 status_draw(struct view *view, struct line *line, unsigned int lineno)
5745         struct status *status = line->data;
5746         enum line_type type;
5747         const char *text;
5749         if (!status) {
5750                 switch (line->type) {
5751                 case LINE_STAT_STAGED:
5752                         type = LINE_STAT_SECTION;
5753                         text = "Changes to be committed:";
5754                         break;
5756                 case LINE_STAT_UNSTAGED:
5757                         type = LINE_STAT_SECTION;
5758                         text = "Changed but not updated:";
5759                         break;
5761                 case LINE_STAT_UNTRACKED:
5762                         type = LINE_STAT_SECTION;
5763                         text = "Untracked files:";
5764                         break;
5766                 case LINE_STAT_NONE:
5767                         type = LINE_DEFAULT;
5768                         text = "  (no files)";
5769                         break;
5771                 case LINE_STAT_HEAD:
5772                         type = LINE_STAT_HEAD;
5773                         text = status_onbranch;
5774                         break;
5776                 default:
5777                         return FALSE;
5778                 }
5779         } else {
5780                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5782                 buf[0] = status->status;
5783                 if (draw_text(view, line->type, buf, TRUE))
5784                         return TRUE;
5785                 type = LINE_DEFAULT;
5786                 text = status->new.name;
5787         }
5789         draw_text(view, type, text, TRUE);
5790         return TRUE;
5793 static enum request
5794 status_load_error(struct view *view, struct view *stage, const char *path)
5796         if (displayed_views() == 2 || display[current_view] != view)
5797                 maximize_view(view);
5798         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5799         return REQ_NONE;
5802 static enum request
5803 status_enter(struct view *view, struct line *line)
5805         struct status *status = line->data;
5806         const char *oldpath = status ? status->old.name : NULL;
5807         /* Diffs for unmerged entries are empty when passing the new
5808          * path, so leave it empty. */
5809         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5810         const char *info;
5811         enum open_flags split;
5812         struct view *stage = VIEW(REQ_VIEW_STAGE);
5814         if (line->type == LINE_STAT_NONE ||
5815             (!status && line[1].type == LINE_STAT_NONE)) {
5816                 report("No file to diff");
5817                 return REQ_NONE;
5818         }
5820         switch (line->type) {
5821         case LINE_STAT_STAGED:
5822                 if (is_initial_commit()) {
5823                         const char *no_head_diff_argv[] = {
5824                                 "git", "diff", "--no-color", "--patch-with-stat",
5825                                         "--", "/dev/null", newpath, NULL
5826                         };
5828                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5829                                 return status_load_error(view, stage, newpath);
5830                 } else {
5831                         const char *index_show_argv[] = {
5832                                 "git", "diff-index", "--root", "--patch-with-stat",
5833                                         "-C", "-M", "--cached", "HEAD", "--",
5834                                         oldpath, newpath, NULL
5835                         };
5837                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5838                                 return status_load_error(view, stage, newpath);
5839                 }
5841                 if (status)
5842                         info = "Staged changes to %s";
5843                 else
5844                         info = "Staged changes";
5845                 break;
5847         case LINE_STAT_UNSTAGED:
5848         {
5849                 const char *files_show_argv[] = {
5850                         "git", "diff-files", "--root", "--patch-with-stat",
5851                                 "-C", "-M", "--", oldpath, newpath, NULL
5852                 };
5854                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5855                         return status_load_error(view, stage, newpath);
5856                 if (status)
5857                         info = "Unstaged changes to %s";
5858                 else
5859                         info = "Unstaged changes";
5860                 break;
5861         }
5862         case LINE_STAT_UNTRACKED:
5863                 if (!newpath) {
5864                         report("No file to show");
5865                         return REQ_NONE;
5866                 }
5868                 if (!suffixcmp(status->new.name, -1, "/")) {
5869                         report("Cannot display a directory");
5870                         return REQ_NONE;
5871                 }
5873                 if (!prepare_update_file(stage, newpath))
5874                         return status_load_error(view, stage, newpath);
5875                 info = "Untracked file %s";
5876                 break;
5878         case LINE_STAT_HEAD:
5879                 return REQ_NONE;
5881         default:
5882                 die("line type %d not handled in switch", line->type);
5883         }
5885         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5886         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5887         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5888                 if (status) {
5889                         stage_status = *status;
5890                 } else {
5891                         memset(&stage_status, 0, sizeof(stage_status));
5892                 }
5894                 stage_line_type = line->type;
5895                 stage_chunks = 0;
5896                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5897         }
5899         return REQ_NONE;
5902 static bool
5903 status_exists(struct status *status, enum line_type type)
5905         struct view *view = VIEW(REQ_VIEW_STATUS);
5906         unsigned long lineno;
5908         for (lineno = 0; lineno < view->lines; lineno++) {
5909                 struct line *line = &view->line[lineno];
5910                 struct status *pos = line->data;
5912                 if (line->type != type)
5913                         continue;
5914                 if (!pos && (!status || !status->status) && line[1].data) {
5915                         select_view_line(view, lineno);
5916                         return TRUE;
5917                 }
5918                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5919                         select_view_line(view, lineno);
5920                         return TRUE;
5921                 }
5922         }
5924         return FALSE;
5928 static bool
5929 status_update_prepare(struct io *io, enum line_type type)
5931         const char *staged_argv[] = {
5932                 "git", "update-index", "-z", "--index-info", NULL
5933         };
5934         const char *others_argv[] = {
5935                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5936         };
5938         switch (type) {
5939         case LINE_STAT_STAGED:
5940                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5942         case LINE_STAT_UNSTAGED:
5943         case LINE_STAT_UNTRACKED:
5944                 return io_run(io, others_argv, opt_cdup, IO_WR);
5946         default:
5947                 die("line type %d not handled in switch", type);
5948                 return FALSE;
5949         }
5952 static bool
5953 status_update_write(struct io *io, struct status *status, enum line_type type)
5955         char buf[SIZEOF_STR];
5956         size_t bufsize = 0;
5958         switch (type) {
5959         case LINE_STAT_STAGED:
5960                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5961                                         status->old.mode,
5962                                         status->old.rev,
5963                                         status->old.name, 0))
5964                         return FALSE;
5965                 break;
5967         case LINE_STAT_UNSTAGED:
5968         case LINE_STAT_UNTRACKED:
5969                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5970                         return FALSE;
5971                 break;
5973         default:
5974                 die("line type %d not handled in switch", type);
5975         }
5977         return io_write(io, buf, bufsize);
5980 static bool
5981 status_update_file(struct status *status, enum line_type type)
5983         struct io io = {};
5984         bool result;
5986         if (!status_update_prepare(&io, type))
5987                 return FALSE;
5989         result = status_update_write(&io, status, type);
5990         return io_done(&io) && result;
5993 static bool
5994 status_update_files(struct view *view, struct line *line)
5996         char buf[sizeof(view->ref)];
5997         struct io io = {};
5998         bool result = TRUE;
5999         struct line *pos = view->line + view->lines;
6000         int files = 0;
6001         int file, done;
6002         int cursor_y = -1, cursor_x = -1;
6004         if (!status_update_prepare(&io, line->type))
6005                 return FALSE;
6007         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6008                 files++;
6010         string_copy(buf, view->ref);
6011         getsyx(cursor_y, cursor_x);
6012         for (file = 0, done = 5; result && file < files; line++, file++) {
6013                 int almost_done = file * 100 / files;
6015                 if (almost_done > done) {
6016                         done = almost_done;
6017                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6018                                       file, files, done);
6019                         update_view_title(view);
6020                         setsyx(cursor_y, cursor_x);
6021                         doupdate();
6022                 }
6023                 result = status_update_write(&io, line->data, line->type);
6024         }
6025         string_copy(view->ref, buf);
6027         return io_done(&io) && result;
6030 static bool
6031 status_update(struct view *view)
6033         struct line *line = &view->line[view->lineno];
6035         assert(view->lines);
6037         if (!line->data) {
6038                 /* This should work even for the "On branch" line. */
6039                 if (line < view->line + view->lines && !line[1].data) {
6040                         report("Nothing to update");
6041                         return FALSE;
6042                 }
6044                 if (!status_update_files(view, line + 1)) {
6045                         report("Failed to update file status");
6046                         return FALSE;
6047                 }
6049         } else if (!status_update_file(line->data, line->type)) {
6050                 report("Failed to update file status");
6051                 return FALSE;
6052         }
6054         return TRUE;
6057 static bool
6058 status_revert(struct status *status, enum line_type type, bool has_none)
6060         if (!status || type != LINE_STAT_UNSTAGED) {
6061                 if (type == LINE_STAT_STAGED) {
6062                         report("Cannot revert changes to staged files");
6063                 } else if (type == LINE_STAT_UNTRACKED) {
6064                         report("Cannot revert changes to untracked files");
6065                 } else if (has_none) {
6066                         report("Nothing to revert");
6067                 } else {
6068                         report("Cannot revert changes to multiple files");
6069                 }
6071         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6072                 char mode[10] = "100644";
6073                 const char *reset_argv[] = {
6074                         "git", "update-index", "--cacheinfo", mode,
6075                                 status->old.rev, status->old.name, NULL
6076                 };
6077                 const char *checkout_argv[] = {
6078                         "git", "checkout", "--", status->old.name, NULL
6079                 };
6081                 if (status->status == 'U') {
6082                         string_format(mode, "%5o", status->old.mode);
6084                         if (status->old.mode == 0 && status->new.mode == 0) {
6085                                 reset_argv[2] = "--force-remove";
6086                                 reset_argv[3] = status->old.name;
6087                                 reset_argv[4] = NULL;
6088                         }
6090                         if (!io_run_fg(reset_argv, opt_cdup))
6091                                 return FALSE;
6092                         if (status->old.mode == 0 && status->new.mode == 0)
6093                                 return TRUE;
6094                 }
6096                 return io_run_fg(checkout_argv, opt_cdup);
6097         }
6099         return FALSE;
6102 static enum request
6103 status_request(struct view *view, enum request request, struct line *line)
6105         struct status *status = line->data;
6107         switch (request) {
6108         case REQ_STATUS_UPDATE:
6109                 if (!status_update(view))
6110                         return REQ_NONE;
6111                 break;
6113         case REQ_STATUS_REVERT:
6114                 if (!status_revert(status, line->type, status_has_none(view, line)))
6115                         return REQ_NONE;
6116                 break;
6118         case REQ_STATUS_MERGE:
6119                 if (!status || status->status != 'U') {
6120                         report("Merging only possible for files with unmerged status ('U').");
6121                         return REQ_NONE;
6122                 }
6123                 open_mergetool(status->new.name);
6124                 break;
6126         case REQ_EDIT:
6127                 if (!status)
6128                         return request;
6129                 if (status->status == 'D') {
6130                         report("File has been deleted.");
6131                         return REQ_NONE;
6132                 }
6134                 open_editor(status->new.name);
6135                 break;
6137         case REQ_VIEW_BLAME:
6138                 if (status)
6139                         opt_ref[0] = 0;
6140                 return request;
6142         case REQ_ENTER:
6143                 /* After returning the status view has been split to
6144                  * show the stage view. No further reloading is
6145                  * necessary. */
6146                 return status_enter(view, line);
6148         case REQ_REFRESH:
6149                 /* Simply reload the view. */
6150                 break;
6152         default:
6153                 return request;
6154         }
6156         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6158         return REQ_NONE;
6161 static void
6162 status_select(struct view *view, struct line *line)
6164         struct status *status = line->data;
6165         char file[SIZEOF_STR] = "all files";
6166         const char *text;
6167         const char *key;
6169         if (status && !string_format(file, "'%s'", status->new.name))
6170                 return;
6172         if (!status && line[1].type == LINE_STAT_NONE)
6173                 line++;
6175         switch (line->type) {
6176         case LINE_STAT_STAGED:
6177                 text = "Press %s to unstage %s for commit";
6178                 break;
6180         case LINE_STAT_UNSTAGED:
6181                 text = "Press %s to stage %s for commit";
6182                 break;
6184         case LINE_STAT_UNTRACKED:
6185                 text = "Press %s to stage %s for addition";
6186                 break;
6188         case LINE_STAT_HEAD:
6189         case LINE_STAT_NONE:
6190                 text = "Nothing to update";
6191                 break;
6193         default:
6194                 die("line type %d not handled in switch", line->type);
6195         }
6197         if (status && status->status == 'U') {
6198                 text = "Press %s to resolve conflict in %s";
6199                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6201         } else {
6202                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6203         }
6205         string_format(view->ref, text, key, file);
6206         if (status)
6207                 string_copy(opt_file, status->new.name);
6210 static bool
6211 status_grep(struct view *view, struct line *line)
6213         struct status *status = line->data;
6215         if (status) {
6216                 const char buf[2] = { status->status, 0 };
6217                 const char *text[] = { status->new.name, buf, NULL };
6219                 return grep_text(view, text);
6220         }
6222         return FALSE;
6225 static struct view_ops status_ops = {
6226         "file",
6227         NULL,
6228         status_open,
6229         NULL,
6230         status_draw,
6231         status_request,
6232         status_grep,
6233         status_select,
6234 };
6237 static bool
6238 stage_diff_write(struct io *io, struct line *line, struct line *end)
6240         while (line < end) {
6241                 if (!io_write(io, line->data, strlen(line->data)) ||
6242                     !io_write(io, "\n", 1))
6243                         return FALSE;
6244                 line++;
6245                 if (line->type == LINE_DIFF_CHUNK ||
6246                     line->type == LINE_DIFF_HEADER)
6247                         break;
6248         }
6250         return TRUE;
6253 static struct line *
6254 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6256         for (; view->line < line; line--)
6257                 if (line->type == type)
6258                         return line;
6260         return NULL;
6263 static bool
6264 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6266         const char *apply_argv[SIZEOF_ARG] = {
6267                 "git", "apply", "--whitespace=nowarn", NULL
6268         };
6269         struct line *diff_hdr;
6270         struct io io = {};
6271         int argc = 3;
6273         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6274         if (!diff_hdr)
6275                 return FALSE;
6277         if (!revert)
6278                 apply_argv[argc++] = "--cached";
6279         if (revert || stage_line_type == LINE_STAT_STAGED)
6280                 apply_argv[argc++] = "-R";
6281         apply_argv[argc++] = "-";
6282         apply_argv[argc++] = NULL;
6283         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6284                 return FALSE;
6286         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6287             !stage_diff_write(&io, chunk, view->line + view->lines))
6288                 chunk = NULL;
6290         io_done(&io);
6291         io_run_bg(update_index_argv);
6293         return chunk ? TRUE : FALSE;
6296 static bool
6297 stage_update(struct view *view, struct line *line)
6299         struct line *chunk = NULL;
6301         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6302                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6304         if (chunk) {
6305                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6306                         report("Failed to apply chunk");
6307                         return FALSE;
6308                 }
6310         } else if (!stage_status.status) {
6311                 view = VIEW(REQ_VIEW_STATUS);
6313                 for (line = view->line; line < view->line + view->lines; line++)
6314                         if (line->type == stage_line_type)
6315                                 break;
6317                 if (!status_update_files(view, line + 1)) {
6318                         report("Failed to update files");
6319                         return FALSE;
6320                 }
6322         } else if (!status_update_file(&stage_status, stage_line_type)) {
6323                 report("Failed to update file");
6324                 return FALSE;
6325         }
6327         return TRUE;
6330 static bool
6331 stage_revert(struct view *view, struct line *line)
6333         struct line *chunk = NULL;
6335         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6336                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6338         if (chunk) {
6339                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6340                         return FALSE;
6342                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6343                         report("Failed to revert chunk");
6344                         return FALSE;
6345                 }
6346                 return TRUE;
6348         } else {
6349                 return status_revert(stage_status.status ? &stage_status : NULL,
6350                                      stage_line_type, FALSE);
6351         }
6355 static void
6356 stage_next(struct view *view, struct line *line)
6358         int i;
6360         if (!stage_chunks) {
6361                 for (line = view->line; line < view->line + view->lines; line++) {
6362                         if (line->type != LINE_DIFF_CHUNK)
6363                                 continue;
6365                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6366                                 report("Allocation failure");
6367                                 return;
6368                         }
6370                         stage_chunk[stage_chunks++] = line - view->line;
6371                 }
6372         }
6374         for (i = 0; i < stage_chunks; i++) {
6375                 if (stage_chunk[i] > view->lineno) {
6376                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6377                         report("Chunk %d of %d", i + 1, stage_chunks);
6378                         return;
6379                 }
6380         }
6382         report("No next chunk found");
6385 static enum request
6386 stage_request(struct view *view, enum request request, struct line *line)
6388         switch (request) {
6389         case REQ_STATUS_UPDATE:
6390                 if (!stage_update(view, line))
6391                         return REQ_NONE;
6392                 break;
6394         case REQ_STATUS_REVERT:
6395                 if (!stage_revert(view, line))
6396                         return REQ_NONE;
6397                 break;
6399         case REQ_STAGE_NEXT:
6400                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6401                         report("File is untracked; press %s to add",
6402                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6403                         return REQ_NONE;
6404                 }
6405                 stage_next(view, line);
6406                 return REQ_NONE;
6408         case REQ_EDIT:
6409                 if (!stage_status.new.name[0])
6410                         return request;
6411                 if (stage_status.status == 'D') {
6412                         report("File has been deleted.");
6413                         return REQ_NONE;
6414                 }
6416                 open_editor(stage_status.new.name);
6417                 break;
6419         case REQ_REFRESH:
6420                 /* Reload everything ... */
6421                 break;
6423         case REQ_VIEW_BLAME:
6424                 if (stage_status.new.name[0]) {
6425                         string_copy(opt_file, stage_status.new.name);
6426                         opt_ref[0] = 0;
6427                 }
6428                 return request;
6430         case REQ_ENTER:
6431                 return pager_request(view, request, line);
6433         default:
6434                 return request;
6435         }
6437         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6438         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6440         /* Check whether the staged entry still exists, and close the
6441          * stage view if it doesn't. */
6442         if (!status_exists(&stage_status, stage_line_type)) {
6443                 status_restore(VIEW(REQ_VIEW_STATUS));
6444                 return REQ_VIEW_CLOSE;
6445         }
6447         if (stage_line_type == LINE_STAT_UNTRACKED) {
6448                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6449                         report("Cannot display a directory");
6450                         return REQ_NONE;
6451                 }
6453                 if (!prepare_update_file(view, stage_status.new.name)) {
6454                         report("Failed to open file: %s", strerror(errno));
6455                         return REQ_NONE;
6456                 }
6457         }
6458         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6460         return REQ_NONE;
6463 static struct view_ops stage_ops = {
6464         "line",
6465         NULL,
6466         NULL,
6467         pager_read,
6468         pager_draw,
6469         stage_request,
6470         pager_grep,
6471         pager_select,
6472 };
6475 /*
6476  * Revision graph
6477  */
6479 struct commit {
6480         char id[SIZEOF_REV];            /* SHA1 ID. */
6481         char title[128];                /* First line of the commit message. */
6482         const char *author;             /* Author of the commit. */
6483         struct time time;               /* Date from the author ident. */
6484         struct ref_list *refs;          /* Repository references. */
6485         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6486         size_t graph_size;              /* The width of the graph array. */
6487         bool has_parents;               /* Rewritten --parents seen. */
6488 };
6490 /* Size of rev graph with no  "padding" columns */
6491 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6493 struct rev_graph {
6494         struct rev_graph *prev, *next, *parents;
6495         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6496         size_t size;
6497         struct commit *commit;
6498         size_t pos;
6499         unsigned int boundary:1;
6500 };
6502 /* Parents of the commit being visualized. */
6503 static struct rev_graph graph_parents[4];
6505 /* The current stack of revisions on the graph. */
6506 static struct rev_graph graph_stacks[4] = {
6507         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6508         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6509         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6510         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6511 };
6513 static inline bool
6514 graph_parent_is_merge(struct rev_graph *graph)
6516         return graph->parents->size > 1;
6519 static inline void
6520 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6522         struct commit *commit = graph->commit;
6524         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6525                 commit->graph[commit->graph_size++] = symbol;
6528 static void
6529 clear_rev_graph(struct rev_graph *graph)
6531         graph->boundary = 0;
6532         graph->size = graph->pos = 0;
6533         graph->commit = NULL;
6534         memset(graph->parents, 0, sizeof(*graph->parents));
6537 static void
6538 done_rev_graph(struct rev_graph *graph)
6540         if (graph_parent_is_merge(graph) &&
6541             graph->pos < graph->size - 1 &&
6542             graph->next->size == graph->size + graph->parents->size - 1) {
6543                 size_t i = graph->pos + graph->parents->size - 1;
6545                 graph->commit->graph_size = i * 2;
6546                 while (i < graph->next->size - 1) {
6547                         append_to_rev_graph(graph, ' ');
6548                         append_to_rev_graph(graph, '\\');
6549                         i++;
6550                 }
6551         }
6553         clear_rev_graph(graph);
6556 static void
6557 push_rev_graph(struct rev_graph *graph, const char *parent)
6559         int i;
6561         /* "Collapse" duplicate parents lines.
6562          *
6563          * FIXME: This needs to also update update the drawn graph but
6564          * for now it just serves as a method for pruning graph lines. */
6565         for (i = 0; i < graph->size; i++)
6566                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6567                         return;
6569         if (graph->size < SIZEOF_REVITEMS) {
6570                 string_copy_rev(graph->rev[graph->size++], parent);
6571         }
6574 static chtype
6575 get_rev_graph_symbol(struct rev_graph *graph)
6577         chtype symbol;
6579         if (graph->boundary)
6580                 symbol = REVGRAPH_BOUND;
6581         else if (graph->parents->size == 0)
6582                 symbol = REVGRAPH_INIT;
6583         else if (graph_parent_is_merge(graph))
6584                 symbol = REVGRAPH_MERGE;
6585         else if (graph->pos >= graph->size)
6586                 symbol = REVGRAPH_BRANCH;
6587         else
6588                 symbol = REVGRAPH_COMMIT;
6590         return symbol;
6593 static void
6594 draw_rev_graph(struct rev_graph *graph)
6596         struct rev_filler {
6597                 chtype separator, line;
6598         };
6599         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6600         static struct rev_filler fillers[] = {
6601                 { ' ',  '|' },
6602                 { '`',  '.' },
6603                 { '\'', ' ' },
6604                 { '/',  ' ' },
6605         };
6606         chtype symbol = get_rev_graph_symbol(graph);
6607         struct rev_filler *filler;
6608         size_t i;
6610         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6611         filler = &fillers[DEFAULT];
6613         for (i = 0; i < graph->pos; i++) {
6614                 append_to_rev_graph(graph, filler->line);
6615                 if (graph_parent_is_merge(graph->prev) &&
6616                     graph->prev->pos == i)
6617                         filler = &fillers[RSHARP];
6619                 append_to_rev_graph(graph, filler->separator);
6620         }
6622         /* Place the symbol for this revision. */
6623         append_to_rev_graph(graph, symbol);
6625         if (graph->prev->size > graph->size)
6626                 filler = &fillers[RDIAG];
6627         else
6628                 filler = &fillers[DEFAULT];
6630         i++;
6632         for (; i < graph->size; i++) {
6633                 append_to_rev_graph(graph, filler->separator);
6634                 append_to_rev_graph(graph, filler->line);
6635                 if (graph_parent_is_merge(graph->prev) &&
6636                     i < graph->prev->pos + graph->parents->size)
6637                         filler = &fillers[RSHARP];
6638                 if (graph->prev->size > graph->size)
6639                         filler = &fillers[LDIAG];
6640         }
6642         if (graph->prev->size > graph->size) {
6643                 append_to_rev_graph(graph, filler->separator);
6644                 if (filler->line != ' ')
6645                         append_to_rev_graph(graph, filler->line);
6646         }
6649 /* Prepare the next rev graph */
6650 static void
6651 prepare_rev_graph(struct rev_graph *graph)
6653         size_t i;
6655         /* First, traverse all lines of revisions up to the active one. */
6656         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6657                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6658                         break;
6660                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6661         }
6663         /* Interleave the new revision parent(s). */
6664         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6665                 push_rev_graph(graph->next, graph->parents->rev[i]);
6667         /* Lastly, put any remaining revisions. */
6668         for (i = graph->pos + 1; i < graph->size; i++)
6669                 push_rev_graph(graph->next, graph->rev[i]);
6672 static void
6673 update_rev_graph(struct view *view, struct rev_graph *graph)
6675         /* If this is the finalizing update ... */
6676         if (graph->commit)
6677                 prepare_rev_graph(graph);
6679         /* Graph visualization needs a one rev look-ahead,
6680          * so the first update doesn't visualize anything. */
6681         if (!graph->prev->commit)
6682                 return;
6684         if (view->lines > 2)
6685                 view->line[view->lines - 3].dirty = 1;
6686         if (view->lines > 1)
6687                 view->line[view->lines - 2].dirty = 1;
6688         draw_rev_graph(graph->prev);
6689         done_rev_graph(graph->prev->prev);
6693 /*
6694  * Main view backend
6695  */
6697 static const char *main_argv[SIZEOF_ARG] = {
6698         "git", "log", "--no-color", "--pretty=raw", "--parents",
6699                       "--topo-order", "%(head)", NULL
6700 };
6702 static bool
6703 main_draw(struct view *view, struct line *line, unsigned int lineno)
6705         struct commit *commit = line->data;
6707         if (!commit->author)
6708                 return FALSE;
6710         if (opt_date && draw_date(view, &commit->time))
6711                 return TRUE;
6713         if (opt_author && draw_author(view, commit->author))
6714                 return TRUE;
6716         if (opt_rev_graph && commit->graph_size &&
6717             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6718                 return TRUE;
6720         if (opt_show_refs && commit->refs) {
6721                 size_t i;
6723                 for (i = 0; i < commit->refs->size; i++) {
6724                         struct ref *ref = commit->refs->refs[i];
6725                         enum line_type type;
6727                         if (ref->head)
6728                                 type = LINE_MAIN_HEAD;
6729                         else if (ref->ltag)
6730                                 type = LINE_MAIN_LOCAL_TAG;
6731                         else if (ref->tag)
6732                                 type = LINE_MAIN_TAG;
6733                         else if (ref->tracked)
6734                                 type = LINE_MAIN_TRACKED;
6735                         else if (ref->remote)
6736                                 type = LINE_MAIN_REMOTE;
6737                         else
6738                                 type = LINE_MAIN_REF;
6740                         if (draw_text(view, type, "[", TRUE) ||
6741                             draw_text(view, type, ref->name, TRUE) ||
6742                             draw_text(view, type, "]", TRUE))
6743                                 return TRUE;
6745                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6746                                 return TRUE;
6747                 }
6748         }
6750         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6751         return TRUE;
6754 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6755 static bool
6756 main_read(struct view *view, char *line)
6758         static struct rev_graph *graph = graph_stacks;
6759         enum line_type type;
6760         struct commit *commit;
6762         if (!line) {
6763                 int i;
6765                 if (!view->lines && !view->parent)
6766                         die("No revisions match the given arguments.");
6767                 if (view->lines > 0) {
6768                         commit = view->line[view->lines - 1].data;
6769                         view->line[view->lines - 1].dirty = 1;
6770                         if (!commit->author) {
6771                                 view->lines--;
6772                                 free(commit);
6773                                 graph->commit = NULL;
6774                         }
6775                 }
6776                 update_rev_graph(view, graph);
6778                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6779                         clear_rev_graph(&graph_stacks[i]);
6780                 return TRUE;
6781         }
6783         type = get_line_type(line);
6784         if (type == LINE_COMMIT) {
6785                 commit = calloc(1, sizeof(struct commit));
6786                 if (!commit)
6787                         return FALSE;
6789                 line += STRING_SIZE("commit ");
6790                 if (*line == '-') {
6791                         graph->boundary = 1;
6792                         line++;
6793                 }
6795                 string_copy_rev(commit->id, line);
6796                 commit->refs = get_ref_list(commit->id);
6797                 graph->commit = commit;
6798                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6800                 while ((line = strchr(line, ' '))) {
6801                         line++;
6802                         push_rev_graph(graph->parents, line);
6803                         commit->has_parents = TRUE;
6804                 }
6805                 return TRUE;
6806         }
6808         if (!view->lines)
6809                 return TRUE;
6810         commit = view->line[view->lines - 1].data;
6812         switch (type) {
6813         case LINE_PARENT:
6814                 if (commit->has_parents)
6815                         break;
6816                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6817                 break;
6819         case LINE_AUTHOR:
6820                 parse_author_line(line + STRING_SIZE("author "),
6821                                   &commit->author, &commit->time);
6822                 update_rev_graph(view, graph);
6823                 graph = graph->next;
6824                 break;
6826         default:
6827                 /* Fill in the commit title if it has not already been set. */
6828                 if (commit->title[0])
6829                         break;
6831                 /* Require titles to start with a non-space character at the
6832                  * offset used by git log. */
6833                 if (strncmp(line, "    ", 4))
6834                         break;
6835                 line += 4;
6836                 /* Well, if the title starts with a whitespace character,
6837                  * try to be forgiving.  Otherwise we end up with no title. */
6838                 while (isspace(*line))
6839                         line++;
6840                 if (*line == '\0')
6841                         break;
6842                 /* FIXME: More graceful handling of titles; append "..." to
6843                  * shortened titles, etc. */
6845                 string_expand(commit->title, sizeof(commit->title), line, 1);
6846                 view->line[view->lines - 1].dirty = 1;
6847         }
6849         return TRUE;
6852 static enum request
6853 main_request(struct view *view, enum request request, struct line *line)
6855         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6857         switch (request) {
6858         case REQ_ENTER:
6859                 open_view(view, REQ_VIEW_DIFF, flags);
6860                 break;
6861         case REQ_REFRESH:
6862                 load_refs();
6863                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6864                 break;
6865         default:
6866                 return request;
6867         }
6869         return REQ_NONE;
6872 static bool
6873 grep_refs(struct ref_list *list, regex_t *regex)
6875         regmatch_t pmatch;
6876         size_t i;
6878         if (!opt_show_refs || !list)
6879                 return FALSE;
6881         for (i = 0; i < list->size; i++) {
6882                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6883                         return TRUE;
6884         }
6886         return FALSE;
6889 static bool
6890 main_grep(struct view *view, struct line *line)
6892         struct commit *commit = line->data;
6893         const char *text[] = {
6894                 commit->title,
6895                 opt_author ? commit->author : "",
6896                 mkdate(&commit->time, opt_date),
6897                 NULL
6898         };
6900         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6903 static void
6904 main_select(struct view *view, struct line *line)
6906         struct commit *commit = line->data;
6908         string_copy_rev(view->ref, commit->id);
6909         string_copy_rev(ref_commit, view->ref);
6912 static struct view_ops main_ops = {
6913         "commit",
6914         main_argv,
6915         NULL,
6916         main_read,
6917         main_draw,
6918         main_request,
6919         main_grep,
6920         main_select,
6921 };
6924 /*
6925  * Status management
6926  */
6928 /* Whether or not the curses interface has been initialized. */
6929 static bool cursed = FALSE;
6931 /* Terminal hacks and workarounds. */
6932 static bool use_scroll_redrawwin;
6933 static bool use_scroll_status_wclear;
6935 /* The status window is used for polling keystrokes. */
6936 static WINDOW *status_win;
6938 /* Reading from the prompt? */
6939 static bool input_mode = FALSE;
6941 static bool status_empty = FALSE;
6943 /* Update status and title window. */
6944 static void
6945 report(const char *msg, ...)
6947         struct view *view = display[current_view];
6949         if (input_mode)
6950                 return;
6952         if (!view) {
6953                 char buf[SIZEOF_STR];
6954                 va_list args;
6956                 va_start(args, msg);
6957                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6958                         buf[sizeof(buf) - 1] = 0;
6959                         buf[sizeof(buf) - 2] = '.';
6960                         buf[sizeof(buf) - 3] = '.';
6961                         buf[sizeof(buf) - 4] = '.';
6962                 }
6963                 va_end(args);
6964                 die("%s", buf);
6965         }
6967         if (!status_empty || *msg) {
6968                 va_list args;
6970                 va_start(args, msg);
6972                 wmove(status_win, 0, 0);
6973                 if (view->has_scrolled && use_scroll_status_wclear)
6974                         wclear(status_win);
6975                 if (*msg) {
6976                         vwprintw(status_win, msg, args);
6977                         status_empty = FALSE;
6978                 } else {
6979                         status_empty = TRUE;
6980                 }
6981                 wclrtoeol(status_win);
6982                 wnoutrefresh(status_win);
6984                 va_end(args);
6985         }
6987         update_view_title(view);
6990 static void
6991 init_display(void)
6993         const char *term;
6994         int x, y;
6996         /* Initialize the curses library */
6997         if (isatty(STDIN_FILENO)) {
6998                 cursed = !!initscr();
6999                 opt_tty = stdin;
7000         } else {
7001                 /* Leave stdin and stdout alone when acting as a pager. */
7002                 opt_tty = fopen("/dev/tty", "r+");
7003                 if (!opt_tty)
7004                         die("Failed to open /dev/tty");
7005                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7006         }
7008         if (!cursed)
7009                 die("Failed to initialize curses");
7011         nonl();         /* Disable conversion and detect newlines from input. */
7012         cbreak();       /* Take input chars one at a time, no wait for \n */
7013         noecho();       /* Don't echo input */
7014         leaveok(stdscr, FALSE);
7016         if (has_colors())
7017                 init_colors();
7019         getmaxyx(stdscr, y, x);
7020         status_win = newwin(1, 0, y - 1, 0);
7021         if (!status_win)
7022                 die("Failed to create status window");
7024         /* Enable keyboard mapping */
7025         keypad(status_win, TRUE);
7026         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7028         TABSIZE = opt_tab_size;
7030         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7031         if (term && !strcmp(term, "gnome-terminal")) {
7032                 /* In the gnome-terminal-emulator, the message from
7033                  * scrolling up one line when impossible followed by
7034                  * scrolling down one line causes corruption of the
7035                  * status line. This is fixed by calling wclear. */
7036                 use_scroll_status_wclear = TRUE;
7037                 use_scroll_redrawwin = FALSE;
7039         } else if (term && !strcmp(term, "xrvt-xpm")) {
7040                 /* No problems with full optimizations in xrvt-(unicode)
7041                  * and aterm. */
7042                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7044         } else {
7045                 /* When scrolling in (u)xterm the last line in the
7046                  * scrolling direction will update slowly. */
7047                 use_scroll_redrawwin = TRUE;
7048                 use_scroll_status_wclear = FALSE;
7049         }
7052 static int
7053 get_input(int prompt_position)
7055         struct view *view;
7056         int i, key, cursor_y, cursor_x;
7057         bool loading = FALSE;
7059         if (prompt_position)
7060                 input_mode = TRUE;
7062         while (TRUE) {
7063                 foreach_view (view, i) {
7064                         update_view(view);
7065                         if (view_is_displayed(view) && view->has_scrolled &&
7066                             use_scroll_redrawwin)
7067                                 redrawwin(view->win);
7068                         view->has_scrolled = FALSE;
7069                         if (view->pipe)
7070                                 loading = TRUE;
7071                 }
7073                 /* Update the cursor position. */
7074                 if (prompt_position) {
7075                         getbegyx(status_win, cursor_y, cursor_x);
7076                         cursor_x = prompt_position;
7077                 } else {
7078                         view = display[current_view];
7079                         getbegyx(view->win, cursor_y, cursor_x);
7080                         cursor_x = view->width - 1;
7081                         cursor_y += view->lineno - view->offset;
7082                 }
7083                 setsyx(cursor_y, cursor_x);
7085                 /* Refresh, accept single keystroke of input */
7086                 doupdate();
7087                 nodelay(status_win, loading);
7088                 key = wgetch(status_win);
7090                 /* wgetch() with nodelay() enabled returns ERR when
7091                  * there's no input. */
7092                 if (key == ERR) {
7094                 } else if (key == KEY_RESIZE) {
7095                         int height, width;
7097                         getmaxyx(stdscr, height, width);
7099                         wresize(status_win, 1, width);
7100                         mvwin(status_win, height - 1, 0);
7101                         wnoutrefresh(status_win);
7102                         resize_display();
7103                         redraw_display(TRUE);
7105                 } else {
7106                         input_mode = FALSE;
7107                         return key;
7108                 }
7109         }
7112 static char *
7113 prompt_input(const char *prompt, input_handler handler, void *data)
7115         enum input_status status = INPUT_OK;
7116         static char buf[SIZEOF_STR];
7117         size_t pos = 0;
7119         buf[pos] = 0;
7121         while (status == INPUT_OK || status == INPUT_SKIP) {
7122                 int key;
7124                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7125                 wclrtoeol(status_win);
7127                 key = get_input(pos + 1);
7128                 switch (key) {
7129                 case KEY_RETURN:
7130                 case KEY_ENTER:
7131                 case '\n':
7132                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7133                         break;
7135                 case KEY_BACKSPACE:
7136                         if (pos > 0)
7137                                 buf[--pos] = 0;
7138                         else
7139                                 status = INPUT_CANCEL;
7140                         break;
7142                 case KEY_ESC:
7143                         status = INPUT_CANCEL;
7144                         break;
7146                 default:
7147                         if (pos >= sizeof(buf)) {
7148                                 report("Input string too long");
7149                                 return NULL;
7150                         }
7152                         status = handler(data, buf, key);
7153                         if (status == INPUT_OK)
7154                                 buf[pos++] = (char) key;
7155                 }
7156         }
7158         /* Clear the status window */
7159         status_empty = FALSE;
7160         report("");
7162         if (status == INPUT_CANCEL)
7163                 return NULL;
7165         buf[pos++] = 0;
7167         return buf;
7170 static enum input_status
7171 prompt_yesno_handler(void *data, char *buf, int c)
7173         if (c == 'y' || c == 'Y')
7174                 return INPUT_STOP;
7175         if (c == 'n' || c == 'N')
7176                 return INPUT_CANCEL;
7177         return INPUT_SKIP;
7180 static bool
7181 prompt_yesno(const char *prompt)
7183         char prompt2[SIZEOF_STR];
7185         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7186                 return FALSE;
7188         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7191 static enum input_status
7192 read_prompt_handler(void *data, char *buf, int c)
7194         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7197 static char *
7198 read_prompt(const char *prompt)
7200         return prompt_input(prompt, read_prompt_handler, NULL);
7203 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7205         enum input_status status = INPUT_OK;
7206         int size = 0;
7208         while (items[size].text)
7209                 size++;
7211         while (status == INPUT_OK) {
7212                 const struct menu_item *item = &items[*selected];
7213                 int key;
7214                 int i;
7216                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7217                           prompt, *selected + 1, size);
7218                 if (item->hotkey)
7219                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7220                 wprintw(status_win, "%s", item->text);
7221                 wclrtoeol(status_win);
7223                 key = get_input(COLS - 1);
7224                 switch (key) {
7225                 case KEY_RETURN:
7226                 case KEY_ENTER:
7227                 case '\n':
7228                         status = INPUT_STOP;
7229                         break;
7231                 case KEY_LEFT:
7232                 case KEY_UP:
7233                         *selected = *selected - 1;
7234                         if (*selected < 0)
7235                                 *selected = size - 1;
7236                         break;
7238                 case KEY_RIGHT:
7239                 case KEY_DOWN:
7240                         *selected = (*selected + 1) % size;
7241                         break;
7243                 case KEY_ESC:
7244                         status = INPUT_CANCEL;
7245                         break;
7247                 default:
7248                         for (i = 0; items[i].text; i++)
7249                                 if (items[i].hotkey == key) {
7250                                         *selected = i;
7251                                         status = INPUT_STOP;
7252                                         break;
7253                                 }
7254                 }
7255         }
7257         /* Clear the status window */
7258         status_empty = FALSE;
7259         report("");
7261         return status != INPUT_CANCEL;
7264 /*
7265  * Repository properties
7266  */
7268 static struct ref **refs = NULL;
7269 static size_t refs_size = 0;
7270 static struct ref *refs_head = NULL;
7272 static struct ref_list **ref_lists = NULL;
7273 static size_t ref_lists_size = 0;
7275 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7276 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7277 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7279 static int
7280 compare_refs(const void *ref1_, const void *ref2_)
7282         const struct ref *ref1 = *(const struct ref **)ref1_;
7283         const struct ref *ref2 = *(const struct ref **)ref2_;
7285         if (ref1->tag != ref2->tag)
7286                 return ref2->tag - ref1->tag;
7287         if (ref1->ltag != ref2->ltag)
7288                 return ref2->ltag - ref2->ltag;
7289         if (ref1->head != ref2->head)
7290                 return ref2->head - ref1->head;
7291         if (ref1->tracked != ref2->tracked)
7292                 return ref2->tracked - ref1->tracked;
7293         if (ref1->remote != ref2->remote)
7294                 return ref2->remote - ref1->remote;
7295         return strcmp(ref1->name, ref2->name);
7298 static void
7299 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7301         size_t i;
7303         for (i = 0; i < refs_size; i++)
7304                 if (!visitor(data, refs[i]))
7305                         break;
7308 static struct ref *
7309 get_ref_head()
7311         return refs_head;
7314 static struct ref_list *
7315 get_ref_list(const char *id)
7317         struct ref_list *list;
7318         size_t i;
7320         for (i = 0; i < ref_lists_size; i++)
7321                 if (!strcmp(id, ref_lists[i]->id))
7322                         return ref_lists[i];
7324         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7325                 return NULL;
7326         list = calloc(1, sizeof(*list));
7327         if (!list)
7328                 return NULL;
7330         for (i = 0; i < refs_size; i++) {
7331                 if (!strcmp(id, refs[i]->id) &&
7332                     realloc_refs_list(&list->refs, list->size, 1))
7333                         list->refs[list->size++] = refs[i];
7334         }
7336         if (!list->refs) {
7337                 free(list);
7338                 return NULL;
7339         }
7341         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7342         ref_lists[ref_lists_size++] = list;
7343         return list;
7346 static int
7347 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7349         struct ref *ref = NULL;
7350         bool tag = FALSE;
7351         bool ltag = FALSE;
7352         bool remote = FALSE;
7353         bool tracked = FALSE;
7354         bool head = FALSE;
7355         int from = 0, to = refs_size - 1;
7357         if (!prefixcmp(name, "refs/tags/")) {
7358                 if (!suffixcmp(name, namelen, "^{}")) {
7359                         namelen -= 3;
7360                         name[namelen] = 0;
7361                 } else {
7362                         ltag = TRUE;
7363                 }
7365                 tag = TRUE;
7366                 namelen -= STRING_SIZE("refs/tags/");
7367                 name    += STRING_SIZE("refs/tags/");
7369         } else if (!prefixcmp(name, "refs/remotes/")) {
7370                 remote = TRUE;
7371                 namelen -= STRING_SIZE("refs/remotes/");
7372                 name    += STRING_SIZE("refs/remotes/");
7373                 tracked  = !strcmp(opt_remote, name);
7375         } else if (!prefixcmp(name, "refs/heads/")) {
7376                 namelen -= STRING_SIZE("refs/heads/");
7377                 name    += STRING_SIZE("refs/heads/");
7378                 if (!strncmp(opt_head, name, namelen))
7379                         return OK;
7381         } else if (!strcmp(name, "HEAD")) {
7382                 head     = TRUE;
7383                 if (*opt_head) {
7384                         namelen  = strlen(opt_head);
7385                         name     = opt_head;
7386                 }
7387         }
7389         /* If we are reloading or it's an annotated tag, replace the
7390          * previous SHA1 with the resolved commit id; relies on the fact
7391          * git-ls-remote lists the commit id of an annotated tag right
7392          * before the commit id it points to. */
7393         while (from <= to) {
7394                 size_t pos = (to + from) / 2;
7395                 int cmp = strcmp(name, refs[pos]->name);
7397                 if (!cmp) {
7398                         ref = refs[pos];
7399                         break;
7400                 }
7402                 if (cmp < 0)
7403                         to = pos - 1;
7404                 else
7405                         from = pos + 1;
7406         }
7408         if (!ref) {
7409                 if (!realloc_refs(&refs, refs_size, 1))
7410                         return ERR;
7411                 ref = calloc(1, sizeof(*ref) + namelen);
7412                 if (!ref)
7413                         return ERR;
7414                 memmove(refs + from + 1, refs + from,
7415                         (refs_size - from) * sizeof(*refs));
7416                 refs[from] = ref;
7417                 strncpy(ref->name, name, namelen);
7418                 refs_size++;
7419         }
7421         ref->head = head;
7422         ref->tag = tag;
7423         ref->ltag = ltag;
7424         ref->remote = remote;
7425         ref->tracked = tracked;
7426         string_copy_rev(ref->id, id);
7428         if (head)
7429                 refs_head = ref;
7430         return OK;
7433 static int
7434 load_refs(void)
7436         const char *head_argv[] = {
7437                 "git", "symbolic-ref", "HEAD", NULL
7438         };
7439         static const char *ls_remote_argv[SIZEOF_ARG] = {
7440                 "git", "ls-remote", opt_git_dir, NULL
7441         };
7442         static bool init = FALSE;
7443         size_t i;
7445         if (!init) {
7446                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7447                         die("TIG_LS_REMOTE contains too many arguments");
7448                 init = TRUE;
7449         }
7451         if (!*opt_git_dir)
7452                 return OK;
7454         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7455             !prefixcmp(opt_head, "refs/heads/")) {
7456                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7458                 memmove(opt_head, offset, strlen(offset) + 1);
7459         }
7461         refs_head = NULL;
7462         for (i = 0; i < refs_size; i++)
7463                 refs[i]->id[0] = 0;
7465         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7466                 return ERR;
7468         /* Update the ref lists to reflect changes. */
7469         for (i = 0; i < ref_lists_size; i++) {
7470                 struct ref_list *list = ref_lists[i];
7471                 size_t old, new;
7473                 for (old = new = 0; old < list->size; old++)
7474                         if (!strcmp(list->id, list->refs[old]->id))
7475                                 list->refs[new++] = list->refs[old];
7476                 list->size = new;
7477         }
7479         return OK;
7482 static void
7483 set_remote_branch(const char *name, const char *value, size_t valuelen)
7485         if (!strcmp(name, ".remote")) {
7486                 string_ncopy(opt_remote, value, valuelen);
7488         } else if (*opt_remote && !strcmp(name, ".merge")) {
7489                 size_t from = strlen(opt_remote);
7491                 if (!prefixcmp(value, "refs/heads/"))
7492                         value += STRING_SIZE("refs/heads/");
7494                 if (!string_format_from(opt_remote, &from, "/%s", value))
7495                         opt_remote[0] = 0;
7496         }
7499 static void
7500 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7502         const char *argv[SIZEOF_ARG] = { name, "=" };
7503         int argc = 1 + (cmd == option_set_command);
7504         int error = ERR;
7506         if (!argv_from_string(argv, &argc, value))
7507                 config_msg = "Too many option arguments";
7508         else
7509                 error = cmd(argc, argv);
7511         if (error == ERR)
7512                 warn("Option 'tig.%s': %s", name, config_msg);
7515 static bool
7516 set_environment_variable(const char *name, const char *value)
7518         size_t len = strlen(name) + 1 + strlen(value) + 1;
7519         char *env = malloc(len);
7521         if (env &&
7522             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7523             putenv(env) == 0)
7524                 return TRUE;
7525         free(env);
7526         return FALSE;
7529 static void
7530 set_work_tree(const char *value)
7532         char cwd[SIZEOF_STR];
7534         if (!getcwd(cwd, sizeof(cwd)))
7535                 die("Failed to get cwd path: %s", strerror(errno));
7536         if (chdir(opt_git_dir) < 0)
7537                 die("Failed to chdir(%s): %s", strerror(errno));
7538         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7539                 die("Failed to get git path: %s", strerror(errno));
7540         if (chdir(cwd) < 0)
7541                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7542         if (chdir(value) < 0)
7543                 die("Failed to chdir(%s): %s", value, strerror(errno));
7544         if (!getcwd(cwd, sizeof(cwd)))
7545                 die("Failed to get cwd path: %s", strerror(errno));
7546         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7547                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7548         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7549                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7550         opt_is_inside_work_tree = TRUE;
7553 static int
7554 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7556         if (!strcmp(name, "i18n.commitencoding"))
7557                 string_ncopy(opt_encoding, value, valuelen);
7559         else if (!strcmp(name, "core.editor"))
7560                 string_ncopy(opt_editor, value, valuelen);
7562         else if (!strcmp(name, "core.worktree"))
7563                 set_work_tree(value);
7565         else if (!prefixcmp(name, "tig.color."))
7566                 set_repo_config_option(name + 10, value, option_color_command);
7568         else if (!prefixcmp(name, "tig.bind."))
7569                 set_repo_config_option(name + 9, value, option_bind_command);
7571         else if (!prefixcmp(name, "tig."))
7572                 set_repo_config_option(name + 4, value, option_set_command);
7574         else if (*opt_head && !prefixcmp(name, "branch.") &&
7575                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7576                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7578         return OK;
7581 static int
7582 load_git_config(void)
7584         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7586         return io_run_load(config_list_argv, "=", read_repo_config_option);
7589 static int
7590 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7592         if (!opt_git_dir[0]) {
7593                 string_ncopy(opt_git_dir, name, namelen);
7595         } else if (opt_is_inside_work_tree == -1) {
7596                 /* This can be 3 different values depending on the
7597                  * version of git being used. If git-rev-parse does not
7598                  * understand --is-inside-work-tree it will simply echo
7599                  * the option else either "true" or "false" is printed.
7600                  * Default to true for the unknown case. */
7601                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7603         } else if (*name == '.') {
7604                 string_ncopy(opt_cdup, name, namelen);
7606         } else {
7607                 string_ncopy(opt_prefix, name, namelen);
7608         }
7610         return OK;
7613 static int
7614 load_repo_info(void)
7616         const char *rev_parse_argv[] = {
7617                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7618                         "--show-cdup", "--show-prefix", NULL
7619         };
7621         return io_run_load(rev_parse_argv, "=", read_repo_info);
7625 /*
7626  * Main
7627  */
7629 static const char usage[] =
7630 "tig " TIG_VERSION " (" __DATE__ ")\n"
7631 "\n"
7632 "Usage: tig        [options] [revs] [--] [paths]\n"
7633 "   or: tig show   [options] [revs] [--] [paths]\n"
7634 "   or: tig blame  [rev] path\n"
7635 "   or: tig status\n"
7636 "   or: tig <      [git command output]\n"
7637 "\n"
7638 "Options:\n"
7639 "  -v, --version   Show version and exit\n"
7640 "  -h, --help      Show help message and exit";
7642 static void __NORETURN
7643 quit(int sig)
7645         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7646         if (cursed)
7647                 endwin();
7648         exit(0);
7651 static void __NORETURN
7652 die(const char *err, ...)
7654         va_list args;
7656         endwin();
7658         va_start(args, err);
7659         fputs("tig: ", stderr);
7660         vfprintf(stderr, err, args);
7661         fputs("\n", stderr);
7662         va_end(args);
7664         exit(1);
7667 static void
7668 warn(const char *msg, ...)
7670         va_list args;
7672         va_start(args, msg);
7673         fputs("tig warning: ", stderr);
7674         vfprintf(stderr, msg, args);
7675         fputs("\n", stderr);
7676         va_end(args);
7679 static enum request
7680 parse_options(int argc, const char *argv[])
7682         enum request request = REQ_VIEW_MAIN;
7683         const char *subcommand;
7684         bool seen_dashdash = FALSE;
7685         /* XXX: This is vulnerable to the user overriding options
7686          * required for the main view parser. */
7687         const char *custom_argv[SIZEOF_ARG] = {
7688                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7689                         "--topo-order", NULL
7690         };
7691         int i, j = 6;
7693         if (!isatty(STDIN_FILENO)) {
7694                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7695                 return REQ_VIEW_PAGER;
7696         }
7698         if (argc <= 1)
7699                 return REQ_NONE;
7701         subcommand = argv[1];
7702         if (!strcmp(subcommand, "status")) {
7703                 if (argc > 2)
7704                         warn("ignoring arguments after `%s'", subcommand);
7705                 return REQ_VIEW_STATUS;
7707         } else if (!strcmp(subcommand, "blame")) {
7708                 if (argc <= 2 || argc > 4)
7709                         die("invalid number of options to blame\n\n%s", usage);
7711                 i = 2;
7712                 if (argc == 4) {
7713                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7714                         i++;
7715                 }
7717                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7718                 return REQ_VIEW_BLAME;
7720         } else if (!strcmp(subcommand, "show")) {
7721                 request = REQ_VIEW_DIFF;
7723         } else {
7724                 subcommand = NULL;
7725         }
7727         if (subcommand) {
7728                 custom_argv[1] = subcommand;
7729                 j = 2;
7730         }
7732         for (i = 1 + !!subcommand; i < argc; i++) {
7733                 const char *opt = argv[i];
7735                 if (seen_dashdash || !strcmp(opt, "--")) {
7736                         seen_dashdash = TRUE;
7738                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7739                         printf("tig version %s\n", TIG_VERSION);
7740                         quit(0);
7742                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7743                         printf("%s\n", usage);
7744                         quit(0);
7745                 }
7747                 custom_argv[j++] = opt;
7748                 if (j >= ARRAY_SIZE(custom_argv))
7749                         die("command too long");
7750         }
7752         if (!prepare_update(VIEW(request), custom_argv, NULL))
7753                 die("Failed to format arguments");
7755         return request;
7758 int
7759 main(int argc, const char *argv[])
7761         const char *codeset = "UTF-8";
7762         enum request request = parse_options(argc, argv);
7763         struct view *view;
7764         size_t i;
7766         signal(SIGINT, quit);
7767         signal(SIGPIPE, SIG_IGN);
7769         if (setlocale(LC_ALL, "")) {
7770                 codeset = nl_langinfo(CODESET);
7771         }
7773         if (load_repo_info() == ERR)
7774                 die("Failed to load repo info.");
7776         if (load_options() == ERR)
7777                 die("Failed to load user config.");
7779         if (load_git_config() == ERR)
7780                 die("Failed to load repo config.");
7782         /* Require a git repository unless when running in pager mode. */
7783         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7784                 die("Not a git repository");
7786         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7787                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7788                 if (opt_iconv_in == ICONV_NONE)
7789                         die("Failed to initialize character set conversion");
7790         }
7792         if (codeset && strcmp(codeset, "UTF-8")) {
7793                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7794                 if (opt_iconv_out == ICONV_NONE)
7795                         die("Failed to initialize character set conversion");
7796         }
7798         if (load_refs() == ERR)
7799                 die("Failed to load refs.");
7801         foreach_view (view, i)
7802                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7803                         die("Too many arguments in the `%s` environment variable",
7804                             view->cmd_env);
7806         init_display();
7808         if (request != REQ_NONE)
7809                 open_view(NULL, request, OPEN_PREPARED);
7810         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7812         while (view_driver(display[current_view], request)) {
7813                 int key = get_input(0);
7815                 view = display[current_view];
7816                 request = get_keybinding(view->keymap, key);
7818                 /* Some low-level request handling. This keeps access to
7819                  * status_win restricted. */
7820                 switch (request) {
7821                 case REQ_NONE:
7822                         report("Unknown key, press %s for help",
7823                                get_key(view->keymap, REQ_VIEW_HELP));
7824                         break;
7825                 case REQ_PROMPT:
7826                 {
7827                         char *cmd = read_prompt(":");
7829                         if (cmd && isdigit(*cmd)) {
7830                                 int lineno = view->lineno + 1;
7832                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7833                                         select_view_line(view, lineno - 1);
7834                                         report("");
7835                                 } else {
7836                                         report("Unable to parse '%s' as a line number", cmd);
7837                                 }
7839                         } else if (cmd) {
7840                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7841                                 const char *argv[SIZEOF_ARG] = { "git" };
7842                                 int argc = 1;
7844                                 /* When running random commands, initially show the
7845                                  * command in the title. However, it maybe later be
7846                                  * overwritten if a commit line is selected. */
7847                                 string_ncopy(next->ref, cmd, strlen(cmd));
7849                                 if (!argv_from_string(argv, &argc, cmd)) {
7850                                         report("Too many arguments");
7851                                 } else if (!prepare_update(next, argv, NULL)) {
7852                                         report("Failed to format command");
7853                                 } else {
7854                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7855                                 }
7856                         }
7858                         request = REQ_NONE;
7859                         break;
7860                 }
7861                 case REQ_SEARCH:
7862                 case REQ_SEARCH_BACK:
7863                 {
7864                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7865                         char *search = read_prompt(prompt);
7867                         if (search)
7868                                 string_ncopy(opt_search, search, strlen(search));
7869                         else if (*opt_search)
7870                                 request = request == REQ_SEARCH ?
7871                                         REQ_FIND_NEXT :
7872                                         REQ_FIND_PREV;
7873                         else
7874                                 request = REQ_NONE;
7875                         break;
7876                 }
7877                 default:
7878                         break;
7879                 }
7880         }
7882         quit(0);
7884         return 0;