Code

Introduce view->type member
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int head:1;    /* Is it the current HEAD? */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int ltag:1;    /* If so, is the tag local? */
127         unsigned int remote:1;  /* Is it a remote ref? */
128         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129         char name[1];           /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         size_t size;            /* Number of refs. */
135         struct ref **refs;      /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151         INPUT_OK,
152         INPUT_SKIP,
153         INPUT_STOP,
154         INPUT_CANCEL
155 };
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163         int hotkey;
164         const char *text;
165         void *data;
166 };
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
170 /*
171  * Allocation helpers ... Entering macro hell to never be seen again.
172  */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
175 static type *                                                                   \
176 name(type **mem, size_t size, size_t increase)                                  \
177 {                                                                               \
178         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
179         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180         type *tmp = *mem;                                                       \
181                                                                                 \
182         if (mem == NULL || num_chunks != num_chunks_new) {                      \
183                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184                 if (tmp)                                                        \
185                         *mem = tmp;                                             \
186         }                                                                       \
187                                                                                 \
188         return tmp;                                                             \
191 /*
192  * String helpers
193  */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198         if (srclen > dstlen - 1)
199                 srclen = dstlen - 1;
201         strncpy(dst, src, srclen);
202         dst[srclen] = 0;
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211         string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
222         size_t size, pos;
224         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225                 if (src[pos] == '\t') {
226                         size_t expanded = tabsize - (size % tabsize);
228                         if (expanded + size >= dstlen - 1)
229                                 expanded = dstlen - size - 1;
230                         memcpy(dst + size, "        ", expanded);
231                         size += expanded;
232                 } else {
233                         dst[size++] = src[pos];
234                 }
235         }
237         dst[size] = 0;
240 static char *
241 chomp_string(char *name)
243         int namelen;
245         while (isspace(*name))
246                 name++;
248         namelen = strlen(name) - 1;
249         while (namelen > 0 && isspace(name[namelen]))
250                 name[namelen--] = 0;
252         return name;
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
258         va_list args;
259         size_t pos = bufpos ? *bufpos : 0;
261         va_start(args, fmt);
262         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263         va_end(args);
265         if (bufpos)
266                 *bufpos = pos;
268         return pos >= bufsize ? FALSE : TRUE;
271 #define string_format(buf, fmt, args...) \
272         string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275         string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
280         size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284         /* Diff-Header == DIFF_HEADER */
285         for (i = 0; i < len; i++) {
286                 if (toupper(str1[i]) == toupper(str2[i]))
287                         continue;
289                 if (string_enum_sep(str1[i]) &&
290                     string_enum_sep(str2[i]))
291                         continue;
293                 return str1[i] - str2[i];
294         }
296         return 0;
299 #define enum_equals(entry, str, len) \
300         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303         const char *name;
304         int namelen;
305         int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
313         static char buf[SIZEOF_STR];
314         int bufpos;
316         for (bufpos = 0; bufpos <= namelen; bufpos++) {
317                 buf[bufpos] = tolower(name[bufpos]);
318                 if (buf[bufpos] == '_')
319                         buf[bufpos] = '-';
320         }
322         buf[bufpos] = 0;
323         return buf;
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
331         size_t namelen = strlen(name);
332         int i;
334         for (i = 0; i < map_size; i++)
335                 if (enum_equals(map[i], name, namelen)) {
336                         *value = map[i].value;
337                         return TRUE;
338                 }
340         return FALSE;
343 #define map_enum(attr, map, name) \
344         map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347         strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
352         size_t len = slen >= 0 ? slen : strlen(str);
353         size_t suffixlen = strlen(suffix);
355         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
359 /*
360  * Unicode / UTF-8 handling
361  *
362  * NOTE: Much of the following code for dealing with Unicode is derived from
363  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
365  */
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
370         if (c >= 0x1100 &&
371            (c <= 0x115f                         /* Hangul Jamo */
372             || c == 0x2329
373             || c == 0x232a
374             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
375                                                 /* CJK ... Yi */
376             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
377             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
378             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
379             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
380             || (c >= 0xffe0  && c <= 0xffe6)
381             || (c >= 0x20000 && c <= 0x2fffd)
382             || (c >= 0x30000 && c <= 0x3fffd)))
383                 return 2;
385         if (c == '\t')
386                 return tab_size;
388         return 1;
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392  * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
396         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
397         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
398         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
399         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
400         2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
401         3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
402 };
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
407         int c = *(unsigned char *) string;
409         return utf8_bytes[c];
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
416         unsigned long unicode;
418         switch (length) {
419         case 1:
420                 unicode  =   string[0];
421                 break;
422         case 2:
423                 unicode  =  (string[0] & 0x1f) << 6;
424                 unicode +=  (string[1] & 0x3f);
425                 break;
426         case 3:
427                 unicode  =  (string[0] & 0x0f) << 12;
428                 unicode += ((string[1] & 0x3f) << 6);
429                 unicode +=  (string[2] & 0x3f);
430                 break;
431         case 4:
432                 unicode  =  (string[0] & 0x0f) << 18;
433                 unicode += ((string[1] & 0x3f) << 12);
434                 unicode += ((string[2] & 0x3f) << 6);
435                 unicode +=  (string[3] & 0x3f);
436                 break;
437         case 5:
438                 unicode  =  (string[0] & 0x0f) << 24;
439                 unicode += ((string[1] & 0x3f) << 18);
440                 unicode += ((string[2] & 0x3f) << 12);
441                 unicode += ((string[3] & 0x3f) << 6);
442                 unicode +=  (string[4] & 0x3f);
443                 break;
444         case 6:
445                 unicode  =  (string[0] & 0x01) << 30;
446                 unicode += ((string[1] & 0x3f) << 24);
447                 unicode += ((string[2] & 0x3f) << 18);
448                 unicode += ((string[3] & 0x3f) << 12);
449                 unicode += ((string[4] & 0x3f) << 6);
450                 unicode +=  (string[5] & 0x3f);
451                 break;
452         default:
453                 return 0;
454         }
456         /* Invalid characters could return the special 0xfffd value but NUL
457          * should be just as good. */
458         return unicode > 0xffff ? 0 : unicode;
461 /* Calculates how much of string can be shown within the given maximum width
462  * and sets trimmed parameter to non-zero value if all of string could not be
463  * shown. If the reserve flag is TRUE, it will reserve at least one
464  * trailing character, which can be useful when drawing a delimiter.
465  *
466  * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
470         const char *string = *start;
471         const char *end = strchr(string, '\0');
472         unsigned char last_bytes = 0;
473         size_t last_ucwidth = 0;
475         *width = 0;
476         *trimmed = 0;
478         while (string < end) {
479                 unsigned char bytes = utf8_char_length(string, end);
480                 size_t ucwidth;
481                 unsigned long unicode;
483                 if (string + bytes > end)
484                         break;
486                 /* Change representation to figure out whether
487                  * it is a single- or double-width character. */
489                 unicode = utf8_to_unicode(string, bytes);
490                 /* FIXME: Graceful handling of invalid Unicode character. */
491                 if (!unicode)
492                         break;
494                 ucwidth = unicode_width(unicode, tab_size);
495                 if (skip > 0) {
496                         skip -= ucwidth <= skip ? ucwidth : skip;
497                         *start += bytes;
498                 }
499                 *width  += ucwidth;
500                 if (*width > max_width) {
501                         *trimmed = 1;
502                         *width -= ucwidth;
503                         if (reserve && *width == max_width) {
504                                 string -= last_bytes;
505                                 *width -= last_ucwidth;
506                         }
507                         break;
508                 }
510                 string  += bytes;
511                 last_bytes = ucwidth ? bytes : 0;
512                 last_ucwidth = ucwidth;
513         }
515         return string - *start;
519 #define DATE_INFO \
520         DATE_(NO), \
521         DATE_(DEFAULT), \
522         DATE_(RELATIVE), \
523         DATE_(SHORT)
525 enum date {
526 #define DATE_(name) DATE_##name
527         DATE_INFO
528 #undef  DATE_
529 };
531 static const struct enum_map date_map[] = {
532 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
533         DATE_INFO
534 #undef  DATE_
535 };
537 struct time {
538         time_t sec;
539         int tz;
540 };
542 static inline int timecmp(const struct time *t1, const struct time *t2)
544         return t1->sec - t2->sec;
547 static const char *
548 mkdate(const struct time *time, enum date date)
550         static char buf[DATE_COLS + 1];
551         static const struct enum_map reldate[] = {
552                 { "second", 1,                  60 * 2 },
553                 { "minute", 60,                 60 * 60 * 2 },
554                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
555                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
556                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
557                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
558         };
559         struct tm tm;
561         if (!date || !time || !time->sec)
562                 return "";
564         if (date == DATE_RELATIVE) {
565                 struct timeval now;
566                 time_t date = time->sec + time->tz;
567                 time_t seconds;
568                 int i;
570                 gettimeofday(&now, NULL);
571                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
572                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
573                         if (seconds >= reldate[i].value)
574                                 continue;
576                         seconds /= reldate[i].namelen;
577                         if (!string_format(buf, "%ld %s%s %s",
578                                            seconds, reldate[i].name,
579                                            seconds > 1 ? "s" : "",
580                                            now.tv_sec >= date ? "ago" : "ahead"))
581                                 break;
582                         return buf;
583                 }
584         }
586         gmtime_r(&time->sec, &tm);
587         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
591 #define AUTHOR_VALUES \
592         AUTHOR_(NO), \
593         AUTHOR_(FULL), \
594         AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598         AUTHOR_VALUES,
599 #undef  AUTHOR_
600         AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605         AUTHOR_VALUES
606 #undef  AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
612         static char initials[AUTHOR_COLS * 6 + 1];
613         size_t pos = 0;
614         const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618         memset(initials, 0, sizeof(initials));
619         while (author < end) {
620                 unsigned char bytes;
621                 size_t i;
623                 while (is_initial_sep(*author))
624                         author++;
626                 bytes = utf8_char_length(author, end);
627                 if (bytes < sizeof(initials) - 1 - pos) {
628                         while (bytes--) {
629                                 initials[pos++] = *author++;
630                         }
631                 }
633                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634                         if (i < sizeof(initials) - 1)
635                                 initials[i++] = *author;
636                 }
638                 initials[i++] = 0;
639         }
641         return initials;
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
648         int valuelen;
650         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651                 bool advance = cmd[valuelen] != 0;
653                 cmd[valuelen] = 0;
654                 argv[(*argc)++] = chomp_string(cmd);
655                 cmd = chomp_string(cmd + valuelen + advance);
656         }
658         if (*argc < SIZEOF_ARG)
659                 argv[*argc] = NULL;
660         return *argc < SIZEOF_ARG;
663 static bool
664 argv_from_env(const char **argv, const char *name)
666         char *env = argv ? getenv(name) : NULL;
667         int argc = 0;
669         if (env && *env)
670                 env = strdup(env);
671         return !env || argv_from_string(argv, &argc, env);
675 /*
676  * Executing external commands.
677  */
679 enum io_type {
680         IO_FD,                  /* File descriptor based IO. */
681         IO_BG,                  /* Execute command in the background. */
682         IO_FG,                  /* Execute command with same std{in,out,err}. */
683         IO_RD,                  /* Read only fork+exec IO. */
684         IO_WR,                  /* Write only fork+exec IO. */
685         IO_AP,                  /* Append fork+exec output to file. */
686 };
688 struct io {
689         enum io_type type;      /* The requested type of pipe. */
690         const char *dir;        /* Directory from which to execute. */
691         pid_t pid;              /* PID of spawned process. */
692         int pipe;               /* Pipe end for reading or writing. */
693         int error;              /* Error status. */
694         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
695         char *buf;              /* Read buffer. */
696         size_t bufalloc;        /* Allocated buffer size. */
697         size_t bufsize;         /* Buffer content size. */
698         char *bufpos;           /* Current buffer position. */
699         unsigned int eof:1;     /* Has end of file been reached. */
700 };
702 static void
703 io_reset(struct io *io)
705         io->pipe = -1;
706         io->pid = 0;
707         io->buf = io->bufpos = NULL;
708         io->bufalloc = io->bufsize = 0;
709         io->error = 0;
710         io->eof = 0;
713 static void
714 io_init(struct io *io, const char *dir, enum io_type type)
716         io_reset(io);
717         io->type = type;
718         io->dir = dir;
721 static bool
722 io_format(struct io *io, const char *dir, enum io_type type,
723           const char *argv[], enum format_flags flags)
725         io_init(io, dir, type);
726         return format_argv(io->argv, argv, flags);
729 static bool
730 io_open(struct io *io, const char *fmt, ...)
732         char name[SIZEOF_STR] = "";
733         bool fits;
734         va_list args;
736         io_init(io, NULL, IO_FD);
738         va_start(args, fmt);
739         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
740         va_end(args);
742         if (!fits) {
743                 io->error = ENAMETOOLONG;
744                 return FALSE;
745         }
746         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
747         if (io->pipe == -1)
748                 io->error = errno;
749         return io->pipe != -1;
752 static bool
753 io_kill(struct io *io)
755         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
758 static bool
759 io_done(struct io *io)
761         pid_t pid = io->pid;
763         if (io->pipe != -1)
764                 close(io->pipe);
765         free(io->buf);
766         io_reset(io);
768         while (pid > 0) {
769                 int status;
770                 pid_t waiting = waitpid(pid, &status, 0);
772                 if (waiting < 0) {
773                         if (errno == EINTR)
774                                 continue;
775                         io->error = errno;
776                         return FALSE;
777                 }
779                 return waiting == pid &&
780                        !WIFSIGNALED(status) &&
781                        WIFEXITED(status) &&
782                        !WEXITSTATUS(status);
783         }
785         return TRUE;
788 static bool
789 io_start(struct io *io)
791         int pipefds[2] = { -1, -1 };
793         if (io->type == IO_FD)
794                 return TRUE;
796         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
797                 io->error = errno;
798                 return FALSE;
799         } else if (io->type == IO_AP) {
800                 pipefds[1] = io->pipe;
801         }
803         if ((io->pid = fork())) {
804                 if (io->pid == -1)
805                         io->error = errno;
806                 if (pipefds[!(io->type == IO_WR)] != -1)
807                         close(pipefds[!(io->type == IO_WR)]);
808                 if (io->pid != -1) {
809                         io->pipe = pipefds[!!(io->type == IO_WR)];
810                         return TRUE;
811                 }
813         } else {
814                 if (io->type != IO_FG) {
815                         int devnull = open("/dev/null", O_RDWR);
816                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
817                         int writefd = (io->type == IO_RD || io->type == IO_AP)
818                                                         ? pipefds[1] : devnull;
820                         dup2(readfd,  STDIN_FILENO);
821                         dup2(writefd, STDOUT_FILENO);
822                         dup2(devnull, STDERR_FILENO);
824                         close(devnull);
825                         if (pipefds[0] != -1)
826                                 close(pipefds[0]);
827                         if (pipefds[1] != -1)
828                                 close(pipefds[1]);
829                 }
831                 if (io->dir && *io->dir && chdir(io->dir) == -1)
832                         exit(errno);
834                 execvp(io->argv[0], (char *const*) io->argv);
835                 exit(errno);
836         }
838         if (pipefds[!!(io->type == IO_WR)] != -1)
839                 close(pipefds[!!(io->type == IO_WR)]);
840         return FALSE;
843 static bool
844 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
846         io_init(io, dir, type);
847         if (!format_argv(io->argv, argv, FORMAT_NONE))
848                 return FALSE;
849         return io_start(io);
852 static int
853 io_complete(struct io *io)
855         return io_start(io) && io_done(io);
858 static int
859 io_run_bg(const char **argv)
861         struct io io = {};
863         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
864                 return FALSE;
865         return io_complete(&io);
868 static bool
869 io_run_fg(const char **argv, const char *dir)
871         struct io io = {};
873         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
874                 return FALSE;
875         return io_complete(&io);
878 static bool
879 io_run_append(const char **argv, enum format_flags flags, int fd)
881         struct io io = {};
883         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
884                 close(fd);
885                 return FALSE;
886         }
888         io.pipe = fd;
889         return io_complete(&io);
892 static bool
893 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
895         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
898 static bool
899 io_eof(struct io *io)
901         return io->eof;
904 static int
905 io_error(struct io *io)
907         return io->error;
910 static char *
911 io_strerror(struct io *io)
913         return strerror(io->error);
916 static bool
917 io_can_read(struct io *io)
919         struct timeval tv = { 0, 500 };
920         fd_set fds;
922         FD_ZERO(&fds);
923         FD_SET(io->pipe, &fds);
925         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
928 static ssize_t
929 io_read(struct io *io, void *buf, size_t bufsize)
931         do {
932                 ssize_t readsize = read(io->pipe, buf, bufsize);
934                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
935                         continue;
936                 else if (readsize == -1)
937                         io->error = errno;
938                 else if (readsize == 0)
939                         io->eof = 1;
940                 return readsize;
941         } while (1);
944 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
946 static char *
947 io_get(struct io *io, int c, bool can_read)
949         char *eol;
950         ssize_t readsize;
952         while (TRUE) {
953                 if (io->bufsize > 0) {
954                         eol = memchr(io->bufpos, c, io->bufsize);
955                         if (eol) {
956                                 char *line = io->bufpos;
958                                 *eol = 0;
959                                 io->bufpos = eol + 1;
960                                 io->bufsize -= io->bufpos - line;
961                                 return line;
962                         }
963                 }
965                 if (io_eof(io)) {
966                         if (io->bufsize) {
967                                 io->bufpos[io->bufsize] = 0;
968                                 io->bufsize = 0;
969                                 return io->bufpos;
970                         }
971                         return NULL;
972                 }
974                 if (!can_read)
975                         return NULL;
977                 if (io->bufsize > 0 && io->bufpos > io->buf)
978                         memmove(io->buf, io->bufpos, io->bufsize);
980                 if (io->bufalloc == io->bufsize) {
981                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
982                                 return NULL;
983                         io->bufalloc += BUFSIZ;
984                 }
986                 io->bufpos = io->buf;
987                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
988                 if (io_error(io))
989                         return NULL;
990                 io->bufsize += readsize;
991         }
994 static bool
995 io_write(struct io *io, const void *buf, size_t bufsize)
997         size_t written = 0;
999         while (!io_error(io) && written < bufsize) {
1000                 ssize_t size;
1002                 size = write(io->pipe, buf + written, bufsize - written);
1003                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1004                         continue;
1005                 else if (size == -1)
1006                         io->error = errno;
1007                 else
1008                         written += size;
1009         }
1011         return written == bufsize;
1014 static bool
1015 io_read_buf(struct io *io, char buf[], size_t bufsize)
1017         char *result = io_get(io, '\n', TRUE);
1019         if (result) {
1020                 result = chomp_string(result);
1021                 string_ncopy_do(buf, bufsize, result, strlen(result));
1022         }
1024         return io_done(io) && result;
1027 static bool
1028 io_run_buf(const char **argv, char buf[], size_t bufsize)
1030         struct io io = {};
1032         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1033             && io_read_buf(&io, buf, bufsize);
1036 static int
1037 io_load(struct io *io, const char *separators,
1038         int (*read_property)(char *, size_t, char *, size_t))
1040         char *name;
1041         int state = OK;
1043         if (!io_start(io))
1044                 return ERR;
1046         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1047                 char *value;
1048                 size_t namelen;
1049                 size_t valuelen;
1051                 name = chomp_string(name);
1052                 namelen = strcspn(name, separators);
1054                 if (name[namelen]) {
1055                         name[namelen] = 0;
1056                         value = chomp_string(name + namelen + 1);
1057                         valuelen = strlen(value);
1059                 } else {
1060                         value = "";
1061                         valuelen = 0;
1062                 }
1064                 state = read_property(name, namelen, value, valuelen);
1065         }
1067         if (state != ERR && io_error(io))
1068                 state = ERR;
1069         io_done(io);
1071         return state;
1074 static int
1075 io_run_load(const char **argv, const char *separators,
1076             int (*read_property)(char *, size_t, char *, size_t))
1078         struct io io = {};
1080         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1081                 ? io_load(&io, separators, read_property) : ERR;
1085 /*
1086  * User requests
1087  */
1089 #define REQ_INFO \
1090         /* XXX: Keep the view request first and in sync with views[]. */ \
1091         REQ_GROUP("View switching") \
1092         REQ_(VIEW_MAIN,         "Show main view"), \
1093         REQ_(VIEW_DIFF,         "Show diff view"), \
1094         REQ_(VIEW_LOG,          "Show log view"), \
1095         REQ_(VIEW_TREE,         "Show tree view"), \
1096         REQ_(VIEW_BLOB,         "Show blob view"), \
1097         REQ_(VIEW_BLAME,        "Show blame view"), \
1098         REQ_(VIEW_BRANCH,       "Show branch view"), \
1099         REQ_(VIEW_HELP,         "Show help page"), \
1100         REQ_(VIEW_PAGER,        "Show pager view"), \
1101         REQ_(VIEW_STATUS,       "Show status view"), \
1102         REQ_(VIEW_STAGE,        "Show stage view"), \
1103         \
1104         REQ_GROUP("View manipulation") \
1105         REQ_(ENTER,             "Enter current line and scroll"), \
1106         REQ_(NEXT,              "Move to next"), \
1107         REQ_(PREVIOUS,          "Move to previous"), \
1108         REQ_(PARENT,            "Move to parent"), \
1109         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1110         REQ_(REFRESH,           "Reload and refresh"), \
1111         REQ_(MAXIMIZE,          "Maximize the current view"), \
1112         REQ_(VIEW_CLOSE,        "Close the current view"), \
1113         REQ_(QUIT,              "Close all views and quit"), \
1114         \
1115         REQ_GROUP("View specific requests") \
1116         REQ_(STATUS_UPDATE,     "Update file status"), \
1117         REQ_(STATUS_REVERT,     "Revert file changes"), \
1118         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1119         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1120         \
1121         REQ_GROUP("Cursor navigation") \
1122         REQ_(MOVE_UP,           "Move cursor one line up"), \
1123         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1124         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1125         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1126         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1127         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1128         \
1129         REQ_GROUP("Scrolling") \
1130         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1131         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1132         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1133         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1134         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1135         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1136         \
1137         REQ_GROUP("Searching") \
1138         REQ_(SEARCH,            "Search the view"), \
1139         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1140         REQ_(FIND_NEXT,         "Find next search match"), \
1141         REQ_(FIND_PREV,         "Find previous search match"), \
1142         \
1143         REQ_GROUP("Option manipulation") \
1144         REQ_(OPTIONS,           "Open option menu"), \
1145         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1146         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1147         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1149         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1150         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1151         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1153         \
1154         REQ_GROUP("Misc") \
1155         REQ_(PROMPT,            "Bring up the prompt"), \
1156         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1157         REQ_(SHOW_VERSION,      "Show version information"), \
1158         REQ_(STOP_LOADING,      "Stop all loading views"), \
1159         REQ_(EDIT,              "Open in editor"), \
1160         REQ_(NONE,              "Do nothing")
1163 /* User action requests. */
1164 enum request {
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168         /* Offset all requests to avoid conflicts with ncurses getch values. */
1169         REQ_OFFSET = KEY_MAX + 1,
1170         REQ_INFO
1172 #undef  REQ_GROUP
1173 #undef  REQ_
1174 };
1176 struct request_info {
1177         enum request request;
1178         const char *name;
1179         int namelen;
1180         const char *help;
1181 };
1183 static const struct request_info req_info[] = {
1184 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1185 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1186         REQ_INFO
1187 #undef  REQ_GROUP
1188 #undef  REQ_
1189 };
1191 static enum request
1192 get_request(const char *name)
1194         int namelen = strlen(name);
1195         int i;
1197         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1198                 if (enum_equals(req_info[i], name, namelen))
1199                         return req_info[i].request;
1201         return REQ_NONE;
1205 /*
1206  * Options
1207  */
1209 /* Option and state variables. */
1210 static enum date opt_date               = DATE_DEFAULT;
1211 static enum author opt_author           = AUTHOR_DEFAULT;
1212 static bool opt_line_number             = FALSE;
1213 static bool opt_line_graphics           = TRUE;
1214 static bool opt_rev_graph               = FALSE;
1215 static bool opt_show_refs               = TRUE;
1216 static int opt_num_interval             = 5;
1217 static double opt_hscroll               = 0.50;
1218 static double opt_scale_split_view      = 2.0 / 3.0;
1219 static int opt_tab_size                 = 8;
1220 static int opt_author_cols              = AUTHOR_COLS;
1221 static char opt_path[SIZEOF_STR]        = "";
1222 static char opt_file[SIZEOF_STR]        = "";
1223 static char opt_ref[SIZEOF_REF]         = "";
1224 static char opt_head[SIZEOF_REF]        = "";
1225 static char opt_remote[SIZEOF_REF]      = "";
1226 static char opt_encoding[20]            = "UTF-8";
1227 static iconv_t opt_iconv_in             = ICONV_NONE;
1228 static iconv_t opt_iconv_out            = ICONV_NONE;
1229 static char opt_search[SIZEOF_STR]      = "";
1230 static char opt_cdup[SIZEOF_STR]        = "";
1231 static char opt_prefix[SIZEOF_STR]      = "";
1232 static char opt_git_dir[SIZEOF_STR]     = "";
1233 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1234 static char opt_editor[SIZEOF_STR]      = "";
1235 static FILE *opt_tty                    = NULL;
1237 #define is_initial_commit()     (!get_ref_head())
1238 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1241 /*
1242  * Line-oriented content detection.
1243  */
1245 #define LINE_INFO \
1246 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1247 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1248 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1249 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1250 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1251 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1252 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1253 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1254 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1255 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1260 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1261 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1262 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1263 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1264 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1265 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1266 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1267 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1268 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1269 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1270 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1271 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1272 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1273 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1275 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1276 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1277 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1278 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1279 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1280 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1281 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1282 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1283 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1284 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1285 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1286 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1287 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1288 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1289 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1290 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1291 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1292 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1293 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1294 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1295 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1296 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1297 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1298 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1299 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1301 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1302 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1304 enum line_type {
1305 #define LINE(type, line, fg, bg, attr) \
1306         LINE_##type
1307         LINE_INFO,
1308         LINE_NONE
1309 #undef  LINE
1310 };
1312 struct line_info {
1313         const char *name;       /* Option name. */
1314         int namelen;            /* Size of option name. */
1315         const char *line;       /* The start of line to match. */
1316         int linelen;            /* Size of string to match. */
1317         int fg, bg, attr;       /* Color and text attributes for the lines. */
1318 };
1320 static struct line_info line_info[] = {
1321 #define LINE(type, line, fg, bg, attr) \
1322         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1323         LINE_INFO
1324 #undef  LINE
1325 };
1327 static enum line_type
1328 get_line_type(const char *line)
1330         int linelen = strlen(line);
1331         enum line_type type;
1333         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1334                 /* Case insensitive search matches Signed-off-by lines better. */
1335                 if (linelen >= line_info[type].linelen &&
1336                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1337                         return type;
1339         return LINE_DEFAULT;
1342 static inline int
1343 get_line_attr(enum line_type type)
1345         assert(type < ARRAY_SIZE(line_info));
1346         return COLOR_PAIR(type) | line_info[type].attr;
1349 static struct line_info *
1350 get_line_info(const char *name)
1352         size_t namelen = strlen(name);
1353         enum line_type type;
1355         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1356                 if (enum_equals(line_info[type], name, namelen))
1357                         return &line_info[type];
1359         return NULL;
1362 static void
1363 init_colors(void)
1365         int default_bg = line_info[LINE_DEFAULT].bg;
1366         int default_fg = line_info[LINE_DEFAULT].fg;
1367         enum line_type type;
1369         start_color();
1371         if (assume_default_colors(default_fg, default_bg) == ERR) {
1372                 default_bg = COLOR_BLACK;
1373                 default_fg = COLOR_WHITE;
1374         }
1376         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1377                 struct line_info *info = &line_info[type];
1378                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1379                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1381                 init_pair(type, fg, bg);
1382         }
1385 struct line {
1386         enum line_type type;
1388         /* State flags */
1389         unsigned int selected:1;
1390         unsigned int dirty:1;
1391         unsigned int cleareol:1;
1392         unsigned int other:16;
1394         void *data;             /* User data */
1395 };
1398 /*
1399  * Keys
1400  */
1402 struct keybinding {
1403         int alias;
1404         enum request request;
1405 };
1407 static const struct keybinding default_keybindings[] = {
1408         /* View switching */
1409         { 'm',          REQ_VIEW_MAIN },
1410         { 'd',          REQ_VIEW_DIFF },
1411         { 'l',          REQ_VIEW_LOG },
1412         { 't',          REQ_VIEW_TREE },
1413         { 'f',          REQ_VIEW_BLOB },
1414         { 'B',          REQ_VIEW_BLAME },
1415         { 'H',          REQ_VIEW_BRANCH },
1416         { 'p',          REQ_VIEW_PAGER },
1417         { 'h',          REQ_VIEW_HELP },
1418         { 'S',          REQ_VIEW_STATUS },
1419         { 'c',          REQ_VIEW_STAGE },
1421         /* View manipulation */
1422         { 'q',          REQ_VIEW_CLOSE },
1423         { KEY_TAB,      REQ_VIEW_NEXT },
1424         { KEY_RETURN,   REQ_ENTER },
1425         { KEY_UP,       REQ_PREVIOUS },
1426         { KEY_DOWN,     REQ_NEXT },
1427         { 'R',          REQ_REFRESH },
1428         { KEY_F(5),     REQ_REFRESH },
1429         { 'O',          REQ_MAXIMIZE },
1431         /* Cursor navigation */
1432         { 'k',          REQ_MOVE_UP },
1433         { 'j',          REQ_MOVE_DOWN },
1434         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1435         { KEY_END,      REQ_MOVE_LAST_LINE },
1436         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1437         { ' ',          REQ_MOVE_PAGE_DOWN },
1438         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1439         { 'b',          REQ_MOVE_PAGE_UP },
1440         { '-',          REQ_MOVE_PAGE_UP },
1442         /* Scrolling */
1443         { KEY_LEFT,     REQ_SCROLL_LEFT },
1444         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1445         { KEY_IC,       REQ_SCROLL_LINE_UP },
1446         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1447         { 'w',          REQ_SCROLL_PAGE_UP },
1448         { 's',          REQ_SCROLL_PAGE_DOWN },
1450         /* Searching */
1451         { '/',          REQ_SEARCH },
1452         { '?',          REQ_SEARCH_BACK },
1453         { 'n',          REQ_FIND_NEXT },
1454         { 'N',          REQ_FIND_PREV },
1456         /* Misc */
1457         { 'Q',          REQ_QUIT },
1458         { 'z',          REQ_STOP_LOADING },
1459         { 'v',          REQ_SHOW_VERSION },
1460         { 'r',          REQ_SCREEN_REDRAW },
1461         { 'o',          REQ_OPTIONS },
1462         { '.',          REQ_TOGGLE_LINENO },
1463         { 'D',          REQ_TOGGLE_DATE },
1464         { 'A',          REQ_TOGGLE_AUTHOR },
1465         { 'g',          REQ_TOGGLE_REV_GRAPH },
1466         { 'F',          REQ_TOGGLE_REFS },
1467         { 'I',          REQ_TOGGLE_SORT_ORDER },
1468         { 'i',          REQ_TOGGLE_SORT_FIELD },
1469         { ':',          REQ_PROMPT },
1470         { 'u',          REQ_STATUS_UPDATE },
1471         { '!',          REQ_STATUS_REVERT },
1472         { 'M',          REQ_STATUS_MERGE },
1473         { '@',          REQ_STAGE_NEXT },
1474         { ',',          REQ_PARENT },
1475         { 'e',          REQ_EDIT },
1476 };
1478 #define KEYMAP_INFO \
1479         KEYMAP_(GENERIC), \
1480         KEYMAP_(MAIN), \
1481         KEYMAP_(DIFF), \
1482         KEYMAP_(LOG), \
1483         KEYMAP_(TREE), \
1484         KEYMAP_(BLOB), \
1485         KEYMAP_(BLAME), \
1486         KEYMAP_(BRANCH), \
1487         KEYMAP_(PAGER), \
1488         KEYMAP_(HELP), \
1489         KEYMAP_(STATUS), \
1490         KEYMAP_(STAGE)
1492 enum keymap {
1493 #define KEYMAP_(name) KEYMAP_##name
1494         KEYMAP_INFO
1495 #undef  KEYMAP_
1496 };
1498 static const struct enum_map keymap_table[] = {
1499 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1500         KEYMAP_INFO
1501 #undef  KEYMAP_
1502 };
1504 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1506 struct keybinding_table {
1507         struct keybinding *data;
1508         size_t size;
1509 };
1511 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1513 static void
1514 add_keybinding(enum keymap keymap, enum request request, int key)
1516         struct keybinding_table *table = &keybindings[keymap];
1518         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1519         if (!table->data)
1520                 die("Failed to allocate keybinding");
1521         table->data[table->size].alias = key;
1522         table->data[table->size++].request = request;
1525 /* Looks for a key binding first in the given map, then in the generic map, and
1526  * lastly in the default keybindings. */
1527 static enum request
1528 get_keybinding(enum keymap keymap, int key)
1530         size_t i;
1532         for (i = 0; i < keybindings[keymap].size; i++)
1533                 if (keybindings[keymap].data[i].alias == key)
1534                         return keybindings[keymap].data[i].request;
1536         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1537                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1538                         return keybindings[KEYMAP_GENERIC].data[i].request;
1540         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1541                 if (default_keybindings[i].alias == key)
1542                         return default_keybindings[i].request;
1544         return (enum request) key;
1548 struct key {
1549         const char *name;
1550         int value;
1551 };
1553 static const struct key key_table[] = {
1554         { "Enter",      KEY_RETURN },
1555         { "Space",      ' ' },
1556         { "Backspace",  KEY_BACKSPACE },
1557         { "Tab",        KEY_TAB },
1558         { "Escape",     KEY_ESC },
1559         { "Left",       KEY_LEFT },
1560         { "Right",      KEY_RIGHT },
1561         { "Up",         KEY_UP },
1562         { "Down",       KEY_DOWN },
1563         { "Insert",     KEY_IC },
1564         { "Delete",     KEY_DC },
1565         { "Hash",       '#' },
1566         { "Home",       KEY_HOME },
1567         { "End",        KEY_END },
1568         { "PageUp",     KEY_PPAGE },
1569         { "PageDown",   KEY_NPAGE },
1570         { "F1",         KEY_F(1) },
1571         { "F2",         KEY_F(2) },
1572         { "F3",         KEY_F(3) },
1573         { "F4",         KEY_F(4) },
1574         { "F5",         KEY_F(5) },
1575         { "F6",         KEY_F(6) },
1576         { "F7",         KEY_F(7) },
1577         { "F8",         KEY_F(8) },
1578         { "F9",         KEY_F(9) },
1579         { "F10",        KEY_F(10) },
1580         { "F11",        KEY_F(11) },
1581         { "F12",        KEY_F(12) },
1582 };
1584 static int
1585 get_key_value(const char *name)
1587         int i;
1589         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1590                 if (!strcasecmp(key_table[i].name, name))
1591                         return key_table[i].value;
1593         if (strlen(name) == 1 && isprint(*name))
1594                 return (int) *name;
1596         return ERR;
1599 static const char *
1600 get_key_name(int key_value)
1602         static char key_char[] = "'X'";
1603         const char *seq = NULL;
1604         int key;
1606         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1607                 if (key_table[key].value == key_value)
1608                         seq = key_table[key].name;
1610         if (seq == NULL &&
1611             key_value < 127 &&
1612             isprint(key_value)) {
1613                 key_char[1] = (char) key_value;
1614                 seq = key_char;
1615         }
1617         return seq ? seq : "(no key)";
1620 static bool
1621 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1623         const char *sep = *pos > 0 ? ", " : "";
1624         const char *keyname = get_key_name(keybinding->alias);
1626         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1629 static bool
1630 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1631                            enum keymap keymap, bool all)
1633         int i;
1635         for (i = 0; i < keybindings[keymap].size; i++) {
1636                 if (keybindings[keymap].data[i].request == request) {
1637                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1638                                 return FALSE;
1639                         if (!all)
1640                                 break;
1641                 }
1642         }
1644         return TRUE;
1647 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1649 static const char *
1650 get_keys(enum keymap keymap, enum request request, bool all)
1652         static char buf[BUFSIZ];
1653         size_t pos = 0;
1654         int i;
1656         buf[pos] = 0;
1658         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1659                 return "Too many keybindings!";
1660         if (pos > 0 && !all)
1661                 return buf;
1663         if (keymap != KEYMAP_GENERIC) {
1664                 /* Only the generic keymap includes the default keybindings when
1665                  * listing all keys. */
1666                 if (all)
1667                         return buf;
1669                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1670                         return "Too many keybindings!";
1671                 if (pos)
1672                         return buf;
1673         }
1675         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1676                 if (default_keybindings[i].request == request) {
1677                         if (!append_key(buf, &pos, &default_keybindings[i]))
1678                                 return "Too many keybindings!";
1679                         if (!all)
1680                                 return buf;
1681                 }
1682         }
1684         return buf;
1687 struct run_request {
1688         enum keymap keymap;
1689         int key;
1690         const char *argv[SIZEOF_ARG];
1691 };
1693 static struct run_request *run_request;
1694 static size_t run_requests;
1696 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1698 static enum request
1699 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1701         struct run_request *req;
1703         if (argc >= ARRAY_SIZE(req->argv) - 1)
1704                 return REQ_NONE;
1706         if (!realloc_run_requests(&run_request, run_requests, 1))
1707                 return REQ_NONE;
1709         req = &run_request[run_requests];
1710         req->keymap = keymap;
1711         req->key = key;
1712         req->argv[0] = NULL;
1714         if (!format_argv(req->argv, argv, FORMAT_NONE))
1715                 return REQ_NONE;
1717         return REQ_NONE + ++run_requests;
1720 static struct run_request *
1721 get_run_request(enum request request)
1723         if (request <= REQ_NONE)
1724                 return NULL;
1725         return &run_request[request - REQ_NONE - 1];
1728 static void
1729 add_builtin_run_requests(void)
1731         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1732         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1733         const char *commit[] = { "git", "commit", NULL };
1734         const char *gc[] = { "git", "gc", NULL };
1735         struct {
1736                 enum keymap keymap;
1737                 int key;
1738                 int argc;
1739                 const char **argv;
1740         } reqs[] = {
1741                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1742                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1743                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1744                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1745         };
1746         int i;
1748         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1749                 enum request req;
1751                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1752                 if (req != REQ_NONE)
1753                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1754         }
1757 /*
1758  * User config file handling.
1759  */
1761 static int   config_lineno;
1762 static bool  config_errors;
1763 static const char *config_msg;
1765 static const struct enum_map color_map[] = {
1766 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1767         COLOR_MAP(DEFAULT),
1768         COLOR_MAP(BLACK),
1769         COLOR_MAP(BLUE),
1770         COLOR_MAP(CYAN),
1771         COLOR_MAP(GREEN),
1772         COLOR_MAP(MAGENTA),
1773         COLOR_MAP(RED),
1774         COLOR_MAP(WHITE),
1775         COLOR_MAP(YELLOW),
1776 };
1778 static const struct enum_map attr_map[] = {
1779 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1780         ATTR_MAP(NORMAL),
1781         ATTR_MAP(BLINK),
1782         ATTR_MAP(BOLD),
1783         ATTR_MAP(DIM),
1784         ATTR_MAP(REVERSE),
1785         ATTR_MAP(STANDOUT),
1786         ATTR_MAP(UNDERLINE),
1787 };
1789 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1791 static int parse_step(double *opt, const char *arg)
1793         *opt = atoi(arg);
1794         if (!strchr(arg, '%'))
1795                 return OK;
1797         /* "Shift down" so 100% and 1 does not conflict. */
1798         *opt = (*opt - 1) / 100;
1799         if (*opt >= 1.0) {
1800                 *opt = 0.99;
1801                 config_msg = "Step value larger than 100%";
1802                 return ERR;
1803         }
1804         if (*opt < 0.0) {
1805                 *opt = 1;
1806                 config_msg = "Invalid step value";
1807                 return ERR;
1808         }
1809         return OK;
1812 static int
1813 parse_int(int *opt, const char *arg, int min, int max)
1815         int value = atoi(arg);
1817         if (min <= value && value <= max) {
1818                 *opt = value;
1819                 return OK;
1820         }
1822         config_msg = "Integer value out of bound";
1823         return ERR;
1826 static bool
1827 set_color(int *color, const char *name)
1829         if (map_enum(color, color_map, name))
1830                 return TRUE;
1831         if (!prefixcmp(name, "color"))
1832                 return parse_int(color, name + 5, 0, 255) == OK;
1833         return FALSE;
1836 /* Wants: object fgcolor bgcolor [attribute] */
1837 static int
1838 option_color_command(int argc, const char *argv[])
1840         struct line_info *info;
1842         if (argc < 3) {
1843                 config_msg = "Wrong number of arguments given to color command";
1844                 return ERR;
1845         }
1847         info = get_line_info(argv[0]);
1848         if (!info) {
1849                 static const struct enum_map obsolete[] = {
1850                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1851                         ENUM_MAP("main-date",   LINE_DATE),
1852                         ENUM_MAP("main-author", LINE_AUTHOR),
1853                 };
1854                 int index;
1856                 if (!map_enum(&index, obsolete, argv[0])) {
1857                         config_msg = "Unknown color name";
1858                         return ERR;
1859                 }
1860                 info = &line_info[index];
1861         }
1863         if (!set_color(&info->fg, argv[1]) ||
1864             !set_color(&info->bg, argv[2])) {
1865                 config_msg = "Unknown color";
1866                 return ERR;
1867         }
1869         info->attr = 0;
1870         while (argc-- > 3) {
1871                 int attr;
1873                 if (!set_attribute(&attr, argv[argc])) {
1874                         config_msg = "Unknown attribute";
1875                         return ERR;
1876                 }
1877                 info->attr |= attr;
1878         }
1880         return OK;
1883 static int parse_bool(bool *opt, const char *arg)
1885         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1886                 ? TRUE : FALSE;
1887         return OK;
1890 static int parse_enum_do(unsigned int *opt, const char *arg,
1891                          const struct enum_map *map, size_t map_size)
1893         bool is_true;
1895         assert(map_size > 1);
1897         if (map_enum_do(map, map_size, (int *) opt, arg))
1898                 return OK;
1900         if (parse_bool(&is_true, arg) != OK)
1901                 return ERR;
1903         *opt = is_true ? map[1].value : map[0].value;
1904         return OK;
1907 #define parse_enum(opt, arg, map) \
1908         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1910 static int
1911 parse_string(char *opt, const char *arg, size_t optsize)
1913         int arglen = strlen(arg);
1915         switch (arg[0]) {
1916         case '\"':
1917         case '\'':
1918                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1919                         config_msg = "Unmatched quotation";
1920                         return ERR;
1921                 }
1922                 arg += 1; arglen -= 2;
1923         default:
1924                 string_ncopy_do(opt, optsize, arg, arglen);
1925                 return OK;
1926         }
1929 /* Wants: name = value */
1930 static int
1931 option_set_command(int argc, const char *argv[])
1933         if (argc != 3) {
1934                 config_msg = "Wrong number of arguments given to set command";
1935                 return ERR;
1936         }
1938         if (strcmp(argv[1], "=")) {
1939                 config_msg = "No value assigned";
1940                 return ERR;
1941         }
1943         if (!strcmp(argv[0], "show-author"))
1944                 return parse_enum(&opt_author, argv[2], author_map);
1946         if (!strcmp(argv[0], "show-date"))
1947                 return parse_enum(&opt_date, argv[2], date_map);
1949         if (!strcmp(argv[0], "show-rev-graph"))
1950                 return parse_bool(&opt_rev_graph, argv[2]);
1952         if (!strcmp(argv[0], "show-refs"))
1953                 return parse_bool(&opt_show_refs, argv[2]);
1955         if (!strcmp(argv[0], "show-line-numbers"))
1956                 return parse_bool(&opt_line_number, argv[2]);
1958         if (!strcmp(argv[0], "line-graphics"))
1959                 return parse_bool(&opt_line_graphics, argv[2]);
1961         if (!strcmp(argv[0], "line-number-interval"))
1962                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1964         if (!strcmp(argv[0], "author-width"))
1965                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1967         if (!strcmp(argv[0], "horizontal-scroll"))
1968                 return parse_step(&opt_hscroll, argv[2]);
1970         if (!strcmp(argv[0], "split-view-height"))
1971                 return parse_step(&opt_scale_split_view, argv[2]);
1973         if (!strcmp(argv[0], "tab-size"))
1974                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1976         if (!strcmp(argv[0], "commit-encoding"))
1977                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1979         config_msg = "Unknown variable name";
1980         return ERR;
1983 /* Wants: mode request key */
1984 static int
1985 option_bind_command(int argc, const char *argv[])
1987         enum request request;
1988         int keymap = -1;
1989         int key;
1991         if (argc < 3) {
1992                 config_msg = "Wrong number of arguments given to bind command";
1993                 return ERR;
1994         }
1996         if (set_keymap(&keymap, argv[0]) == ERR) {
1997                 config_msg = "Unknown key map";
1998                 return ERR;
1999         }
2001         key = get_key_value(argv[1]);
2002         if (key == ERR) {
2003                 config_msg = "Unknown key";
2004                 return ERR;
2005         }
2007         request = get_request(argv[2]);
2008         if (request == REQ_NONE) {
2009                 static const struct enum_map obsolete[] = {
2010                         ENUM_MAP("cherry-pick",         REQ_NONE),
2011                         ENUM_MAP("screen-resize",       REQ_NONE),
2012                         ENUM_MAP("tree-parent",         REQ_PARENT),
2013                 };
2014                 int alias;
2016                 if (map_enum(&alias, obsolete, argv[2])) {
2017                         if (alias != REQ_NONE)
2018                                 add_keybinding(keymap, alias, key);
2019                         config_msg = "Obsolete request name";
2020                         return ERR;
2021                 }
2022         }
2023         if (request == REQ_NONE && *argv[2]++ == '!')
2024                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2025         if (request == REQ_NONE) {
2026                 config_msg = "Unknown request name";
2027                 return ERR;
2028         }
2030         add_keybinding(keymap, request, key);
2032         return OK;
2035 static int
2036 set_option(const char *opt, char *value)
2038         const char *argv[SIZEOF_ARG];
2039         int argc = 0;
2041         if (!argv_from_string(argv, &argc, value)) {
2042                 config_msg = "Too many option arguments";
2043                 return ERR;
2044         }
2046         if (!strcmp(opt, "color"))
2047                 return option_color_command(argc, argv);
2049         if (!strcmp(opt, "set"))
2050                 return option_set_command(argc, argv);
2052         if (!strcmp(opt, "bind"))
2053                 return option_bind_command(argc, argv);
2055         config_msg = "Unknown option command";
2056         return ERR;
2059 static int
2060 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2062         int status = OK;
2064         config_lineno++;
2065         config_msg = "Internal error";
2067         /* Check for comment markers, since read_properties() will
2068          * only ensure opt and value are split at first " \t". */
2069         optlen = strcspn(opt, "#");
2070         if (optlen == 0)
2071                 return OK;
2073         if (opt[optlen] != 0) {
2074                 config_msg = "No option value";
2075                 status = ERR;
2077         }  else {
2078                 /* Look for comment endings in the value. */
2079                 size_t len = strcspn(value, "#");
2081                 if (len < valuelen) {
2082                         valuelen = len;
2083                         value[valuelen] = 0;
2084                 }
2086                 status = set_option(opt, value);
2087         }
2089         if (status == ERR) {
2090                 warn("Error on line %d, near '%.*s': %s",
2091                      config_lineno, (int) optlen, opt, config_msg);
2092                 config_errors = TRUE;
2093         }
2095         /* Always keep going if errors are encountered. */
2096         return OK;
2099 static void
2100 load_option_file(const char *path)
2102         struct io io = {};
2104         /* It's OK that the file doesn't exist. */
2105         if (!io_open(&io, "%s", path))
2106                 return;
2108         config_lineno = 0;
2109         config_errors = FALSE;
2111         if (io_load(&io, " \t", read_option) == ERR ||
2112             config_errors == TRUE)
2113                 warn("Errors while loading %s.", path);
2116 static int
2117 load_options(void)
2119         const char *home = getenv("HOME");
2120         const char *tigrc_user = getenv("TIGRC_USER");
2121         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2122         char buf[SIZEOF_STR];
2124         add_builtin_run_requests();
2126         if (!tigrc_system)
2127                 tigrc_system = SYSCONFDIR "/tigrc";
2128         load_option_file(tigrc_system);
2130         if (!tigrc_user) {
2131                 if (!home || !string_format(buf, "%s/.tigrc", home))
2132                         return ERR;
2133                 tigrc_user = buf;
2134         }
2135         load_option_file(tigrc_user);
2137         return OK;
2141 /*
2142  * The viewer
2143  */
2145 struct view;
2146 struct view_ops;
2148 /* The display array of active views and the index of the current view. */
2149 static struct view *display[2];
2150 static unsigned int current_view;
2152 #define foreach_displayed_view(view, i) \
2153         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2155 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2157 /* Current head and commit ID */
2158 static char ref_blob[SIZEOF_REF]        = "";
2159 static char ref_commit[SIZEOF_REF]      = "HEAD";
2160 static char ref_head[SIZEOF_REF]        = "HEAD";
2161 static char ref_branch[SIZEOF_REF]      = "";
2163 enum view_type {
2164         VIEW_MAIN,
2165         VIEW_DIFF,
2166         VIEW_LOG,
2167         VIEW_TREE,
2168         VIEW_BLOB,
2169         VIEW_BLAME,
2170         VIEW_BRANCH,
2171         VIEW_HELP,
2172         VIEW_PAGER,
2173         VIEW_STATUS,
2174         VIEW_STAGE,
2175 };
2177 struct view {
2178         enum view_type type;    /* View type */
2179         const char *name;       /* View name */
2180         const char *cmd_env;    /* Command line set via environment */
2181         const char *id;         /* Points to either of ref_{head,commit,blob} */
2183         struct view_ops *ops;   /* View operations */
2185         enum keymap keymap;     /* What keymap does this view have */
2186         bool git_dir;           /* Whether the view requires a git directory. */
2187         bool refresh;           /* Whether the view supports refreshing. */
2189         char ref[SIZEOF_REF];   /* Hovered commit reference */
2190         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2192         int height, width;      /* The width and height of the main window */
2193         WINDOW *win;            /* The main window */
2194         WINDOW *title;          /* The title window living below the main window */
2196         /* Navigation */
2197         unsigned long offset;   /* Offset of the window top */
2198         unsigned long yoffset;  /* Offset from the window side. */
2199         unsigned long lineno;   /* Current line number */
2200         unsigned long p_offset; /* Previous offset of the window top */
2201         unsigned long p_yoffset;/* Previous offset from the window side */
2202         unsigned long p_lineno; /* Previous current line number */
2203         bool p_restore;         /* Should the previous position be restored. */
2205         /* Searching */
2206         char grep[SIZEOF_STR];  /* Search string */
2207         regex_t *regex;         /* Pre-compiled regexp */
2209         /* If non-NULL, points to the view that opened this view. If this view
2210          * is closed tig will switch back to the parent view. */
2211         struct view *parent;
2213         /* Buffering */
2214         size_t lines;           /* Total number of lines */
2215         struct line *line;      /* Line index */
2216         unsigned int digits;    /* Number of digits in the lines member. */
2218         /* Drawing */
2219         struct line *curline;   /* Line currently being drawn. */
2220         enum line_type curtype; /* Attribute currently used for drawing. */
2221         unsigned long col;      /* Column when drawing. */
2222         bool has_scrolled;      /* View was scrolled. */
2224         /* Loading */
2225         struct io io;
2226         struct io *pipe;
2227         time_t start_time;
2228         time_t update_secs;
2229 };
2231 struct view_ops {
2232         /* What type of content being displayed. Used in the title bar. */
2233         const char *type;
2234         /* Default command arguments. */
2235         const char **argv;
2236         /* Open and reads in all view content. */
2237         bool (*open)(struct view *view);
2238         /* Read one line; updates view->line. */
2239         bool (*read)(struct view *view, char *data);
2240         /* Draw one line; @lineno must be < view->height. */
2241         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2242         /* Depending on view handle a special requests. */
2243         enum request (*request)(struct view *view, enum request request, struct line *line);
2244         /* Search for regexp in a line. */
2245         bool (*grep)(struct view *view, struct line *line);
2246         /* Select line */
2247         void (*select)(struct view *view, struct line *line);
2248         /* Prepare view for loading */
2249         bool (*prepare)(struct view *view);
2250 };
2252 static struct view_ops blame_ops;
2253 static struct view_ops blob_ops;
2254 static struct view_ops diff_ops;
2255 static struct view_ops help_ops;
2256 static struct view_ops log_ops;
2257 static struct view_ops main_ops;
2258 static struct view_ops pager_ops;
2259 static struct view_ops stage_ops;
2260 static struct view_ops status_ops;
2261 static struct view_ops tree_ops;
2262 static struct view_ops branch_ops;
2264 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2265         { type, name, #env, ref, ops, map, git, refresh }
2267 #define VIEW_(id, name, ops, git, refresh, ref) \
2268         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2270 static struct view views[] = {
2271         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  TRUE,  ref_head),
2272         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  FALSE, ref_commit),
2273         VIEW_(LOG,    "log",    &log_ops,    TRUE,  TRUE,  ref_head),
2274         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  FALSE, ref_commit),
2275         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  FALSE, ref_blob),
2276         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  FALSE, ref_commit),
2277         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  TRUE,  ref_head),
2278         VIEW_(HELP,   "help",   &help_ops,   FALSE, FALSE, ""),
2279         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, FALSE, "stdin"),
2280         VIEW_(STATUS, "status", &status_ops, TRUE,  TRUE,  ""),
2281         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  TRUE,  ""),
2282 };
2284 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2286 #define foreach_view(view, i) \
2287         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2289 #define view_is_displayed(view) \
2290         (view == display[0] || view == display[1])
2292 #define view_has_parent(view, child_type, parent_type) \
2293         (view->type == child_type && view->parent && view->parent->type == parent_type)
2295 static inline void
2296 set_view_attr(struct view *view, enum line_type type)
2298         if (!view->curline->selected && view->curtype != type) {
2299                 (void) wattrset(view->win, get_line_attr(type));
2300                 wchgat(view->win, -1, 0, type, NULL);
2301                 view->curtype = type;
2302         }
2305 static int
2306 draw_chars(struct view *view, enum line_type type, const char *string,
2307            int max_len, bool use_tilde)
2309         static char out_buffer[BUFSIZ * 2];
2310         int len = 0;
2311         int col = 0;
2312         int trimmed = FALSE;
2313         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2315         if (max_len <= 0)
2316                 return 0;
2318         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2320         set_view_attr(view, type);
2321         if (len > 0) {
2322                 if (opt_iconv_out != ICONV_NONE) {
2323                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2324                         size_t inlen = len + 1;
2326                         char *outbuf = out_buffer;
2327                         size_t outlen = sizeof(out_buffer);
2329                         size_t ret;
2331                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2332                         if (ret != (size_t) -1) {
2333                                 string = out_buffer;
2334                                 len = sizeof(out_buffer) - outlen;
2335                         }
2336                 }
2338                 waddnstr(view->win, string, len);
2339         }
2340         if (trimmed && use_tilde) {
2341                 set_view_attr(view, LINE_DELIMITER);
2342                 waddch(view->win, '~');
2343                 col++;
2344         }
2346         return col;
2349 static int
2350 draw_space(struct view *view, enum line_type type, int max, int spaces)
2352         static char space[] = "                    ";
2353         int col = 0;
2355         spaces = MIN(max, spaces);
2357         while (spaces > 0) {
2358                 int len = MIN(spaces, sizeof(space) - 1);
2360                 col += draw_chars(view, type, space, len, FALSE);
2361                 spaces -= len;
2362         }
2364         return col;
2367 static bool
2368 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2370         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2371         return view->width + view->yoffset <= view->col;
2374 static bool
2375 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2377         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2378         int max = view->width + view->yoffset - view->col;
2379         int i;
2381         if (max < size)
2382                 size = max;
2384         set_view_attr(view, type);
2385         /* Using waddch() instead of waddnstr() ensures that
2386          * they'll be rendered correctly for the cursor line. */
2387         for (i = skip; i < size; i++)
2388                 waddch(view->win, graphic[i]);
2390         view->col += size;
2391         if (size < max && skip <= size)
2392                 waddch(view->win, ' ');
2393         view->col++;
2395         return view->width + view->yoffset <= view->col;
2398 static bool
2399 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2401         int max = MIN(view->width + view->yoffset - view->col, len);
2402         int col;
2404         if (text)
2405                 col = draw_chars(view, type, text, max - 1, trim);
2406         else
2407                 col = draw_space(view, type, max - 1, max - 1);
2409         view->col += col;
2410         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2411         return view->width + view->yoffset <= view->col;
2414 static bool
2415 draw_date(struct view *view, struct time *time)
2417         const char *date = mkdate(time, opt_date);
2418         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2420         return draw_field(view, LINE_DATE, date, cols, FALSE);
2423 static bool
2424 draw_author(struct view *view, const char *author)
2426         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2427         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2429         if (abbreviate && author)
2430                 author = get_author_initials(author);
2432         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2435 static bool
2436 draw_mode(struct view *view, mode_t mode)
2438         const char *str;
2440         if (S_ISDIR(mode))
2441                 str = "drwxr-xr-x";
2442         else if (S_ISLNK(mode))
2443                 str = "lrwxrwxrwx";
2444         else if (S_ISGITLINK(mode))
2445                 str = "m---------";
2446         else if (S_ISREG(mode) && mode & S_IXUSR)
2447                 str = "-rwxr-xr-x";
2448         else if (S_ISREG(mode))
2449                 str = "-rw-r--r--";
2450         else
2451                 str = "----------";
2453         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2456 static bool
2457 draw_lineno(struct view *view, unsigned int lineno)
2459         char number[10];
2460         int digits3 = view->digits < 3 ? 3 : view->digits;
2461         int max = MIN(view->width + view->yoffset - view->col, digits3);
2462         char *text = NULL;
2463         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2465         lineno += view->offset + 1;
2466         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2467                 static char fmt[] = "%1ld";
2469                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2470                 if (string_format(number, fmt, lineno))
2471                         text = number;
2472         }
2473         if (text)
2474                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2475         else
2476                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2477         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2480 static bool
2481 draw_view_line(struct view *view, unsigned int lineno)
2483         struct line *line;
2484         bool selected = (view->offset + lineno == view->lineno);
2486         assert(view_is_displayed(view));
2488         if (view->offset + lineno >= view->lines)
2489                 return FALSE;
2491         line = &view->line[view->offset + lineno];
2493         wmove(view->win, lineno, 0);
2494         if (line->cleareol)
2495                 wclrtoeol(view->win);
2496         view->col = 0;
2497         view->curline = line;
2498         view->curtype = LINE_NONE;
2499         line->selected = FALSE;
2500         line->dirty = line->cleareol = 0;
2502         if (selected) {
2503                 set_view_attr(view, LINE_CURSOR);
2504                 line->selected = TRUE;
2505                 view->ops->select(view, line);
2506         }
2508         return view->ops->draw(view, line, lineno);
2511 static void
2512 redraw_view_dirty(struct view *view)
2514         bool dirty = FALSE;
2515         int lineno;
2517         for (lineno = 0; lineno < view->height; lineno++) {
2518                 if (view->offset + lineno >= view->lines)
2519                         break;
2520                 if (!view->line[view->offset + lineno].dirty)
2521                         continue;
2522                 dirty = TRUE;
2523                 if (!draw_view_line(view, lineno))
2524                         break;
2525         }
2527         if (!dirty)
2528                 return;
2529         wnoutrefresh(view->win);
2532 static void
2533 redraw_view_from(struct view *view, int lineno)
2535         assert(0 <= lineno && lineno < view->height);
2537         for (; lineno < view->height; lineno++) {
2538                 if (!draw_view_line(view, lineno))
2539                         break;
2540         }
2542         wnoutrefresh(view->win);
2545 static void
2546 redraw_view(struct view *view)
2548         werase(view->win);
2549         redraw_view_from(view, 0);
2553 static void
2554 update_view_title(struct view *view)
2556         char buf[SIZEOF_STR];
2557         char state[SIZEOF_STR];
2558         size_t bufpos = 0, statelen = 0;
2560         assert(view_is_displayed(view));
2562         if (view->type != VIEW_STATUS && view->lines) {
2563                 unsigned int view_lines = view->offset + view->height;
2564                 unsigned int lines = view->lines
2565                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2566                                    : 0;
2568                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2569                                    view->ops->type,
2570                                    view->lineno + 1,
2571                                    view->lines,
2572                                    lines);
2574         }
2576         if (view->pipe) {
2577                 time_t secs = time(NULL) - view->start_time;
2579                 /* Three git seconds are a long time ... */
2580                 if (secs > 2)
2581                         string_format_from(state, &statelen, " loading %lds", secs);
2582         }
2584         string_format_from(buf, &bufpos, "[%s]", view->name);
2585         if (*view->ref && bufpos < view->width) {
2586                 size_t refsize = strlen(view->ref);
2587                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2589                 if (minsize < view->width)
2590                         refsize = view->width - minsize + 7;
2591                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2592         }
2594         if (statelen && bufpos < view->width) {
2595                 string_format_from(buf, &bufpos, "%s", state);
2596         }
2598         if (view == display[current_view])
2599                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2600         else
2601                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2603         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2604         wclrtoeol(view->title);
2605         wnoutrefresh(view->title);
2608 static int
2609 apply_step(double step, int value)
2611         if (step >= 1)
2612                 return (int) step;
2613         value *= step + 0.01;
2614         return value ? value : 1;
2617 static void
2618 resize_display(void)
2620         int offset, i;
2621         struct view *base = display[0];
2622         struct view *view = display[1] ? display[1] : display[0];
2624         /* Setup window dimensions */
2626         getmaxyx(stdscr, base->height, base->width);
2628         /* Make room for the status window. */
2629         base->height -= 1;
2631         if (view != base) {
2632                 /* Horizontal split. */
2633                 view->width   = base->width;
2634                 view->height  = apply_step(opt_scale_split_view, base->height);
2635                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2636                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2637                 base->height -= view->height;
2639                 /* Make room for the title bar. */
2640                 view->height -= 1;
2641         }
2643         /* Make room for the title bar. */
2644         base->height -= 1;
2646         offset = 0;
2648         foreach_displayed_view (view, i) {
2649                 if (!view->win) {
2650                         view->win = newwin(view->height, 0, offset, 0);
2651                         if (!view->win)
2652                                 die("Failed to create %s view", view->name);
2654                         scrollok(view->win, FALSE);
2656                         view->title = newwin(1, 0, offset + view->height, 0);
2657                         if (!view->title)
2658                                 die("Failed to create title window");
2660                 } else {
2661                         wresize(view->win, view->height, view->width);
2662                         mvwin(view->win,   offset, 0);
2663                         mvwin(view->title, offset + view->height, 0);
2664                 }
2666                 offset += view->height + 1;
2667         }
2670 static void
2671 redraw_display(bool clear)
2673         struct view *view;
2674         int i;
2676         foreach_displayed_view (view, i) {
2677                 if (clear)
2678                         wclear(view->win);
2679                 redraw_view(view);
2680                 update_view_title(view);
2681         }
2684 static void
2685 toggle_enum_option_do(unsigned int *opt, const char *help,
2686                       const struct enum_map *map, size_t size)
2688         *opt = (*opt + 1) % size;
2689         redraw_display(FALSE);
2690         report("Displaying %s %s", enum_name(map[*opt]), help);
2693 #define toggle_enum_option(opt, help, map) \
2694         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2696 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2697 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2699 static void
2700 toggle_view_option(bool *option, const char *help)
2702         *option = !*option;
2703         redraw_display(FALSE);
2704         report("%sabling %s", *option ? "En" : "Dis", help);
2707 static void
2708 open_option_menu(void)
2710         const struct menu_item menu[] = {
2711                 { '.', "line numbers", &opt_line_number },
2712                 { 'D', "date display", &opt_date },
2713                 { 'A', "author display", &opt_author },
2714                 { 'g', "revision graph display", &opt_rev_graph },
2715                 { 'F', "reference display", &opt_show_refs },
2716                 { 0 }
2717         };
2718         int selected = 0;
2720         if (prompt_menu("Toggle option", menu, &selected)) {
2721                 if (menu[selected].data == &opt_date)
2722                         toggle_date();
2723                 else if (menu[selected].data == &opt_author)
2724                         toggle_author();
2725                 else
2726                         toggle_view_option(menu[selected].data, menu[selected].text);
2727         }
2730 static void
2731 maximize_view(struct view *view)
2733         memset(display, 0, sizeof(display));
2734         current_view = 0;
2735         display[current_view] = view;
2736         resize_display();
2737         redraw_display(FALSE);
2738         report("");
2742 /*
2743  * Navigation
2744  */
2746 static bool
2747 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2749         if (lineno >= view->lines)
2750                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2752         if (offset > lineno || offset + view->height <= lineno) {
2753                 unsigned long half = view->height / 2;
2755                 if (lineno > half)
2756                         offset = lineno - half;
2757                 else
2758                         offset = 0;
2759         }
2761         if (offset != view->offset || lineno != view->lineno) {
2762                 view->offset = offset;
2763                 view->lineno = lineno;
2764                 return TRUE;
2765         }
2767         return FALSE;
2770 /* Scrolling backend */
2771 static void
2772 do_scroll_view(struct view *view, int lines)
2774         bool redraw_current_line = FALSE;
2776         /* The rendering expects the new offset. */
2777         view->offset += lines;
2779         assert(0 <= view->offset && view->offset < view->lines);
2780         assert(lines);
2782         /* Move current line into the view. */
2783         if (view->lineno < view->offset) {
2784                 view->lineno = view->offset;
2785                 redraw_current_line = TRUE;
2786         } else if (view->lineno >= view->offset + view->height) {
2787                 view->lineno = view->offset + view->height - 1;
2788                 redraw_current_line = TRUE;
2789         }
2791         assert(view->offset <= view->lineno && view->lineno < view->lines);
2793         /* Redraw the whole screen if scrolling is pointless. */
2794         if (view->height < ABS(lines)) {
2795                 redraw_view(view);
2797         } else {
2798                 int line = lines > 0 ? view->height - lines : 0;
2799                 int end = line + ABS(lines);
2801                 scrollok(view->win, TRUE);
2802                 wscrl(view->win, lines);
2803                 scrollok(view->win, FALSE);
2805                 while (line < end && draw_view_line(view, line))
2806                         line++;
2808                 if (redraw_current_line)
2809                         draw_view_line(view, view->lineno - view->offset);
2810                 wnoutrefresh(view->win);
2811         }
2813         view->has_scrolled = TRUE;
2814         report("");
2817 /* Scroll frontend */
2818 static void
2819 scroll_view(struct view *view, enum request request)
2821         int lines = 1;
2823         assert(view_is_displayed(view));
2825         switch (request) {
2826         case REQ_SCROLL_LEFT:
2827                 if (view->yoffset == 0) {
2828                         report("Cannot scroll beyond the first column");
2829                         return;
2830                 }
2831                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2832                         view->yoffset = 0;
2833                 else
2834                         view->yoffset -= apply_step(opt_hscroll, view->width);
2835                 redraw_view_from(view, 0);
2836                 report("");
2837                 return;
2838         case REQ_SCROLL_RIGHT:
2839                 view->yoffset += apply_step(opt_hscroll, view->width);
2840                 redraw_view(view);
2841                 report("");
2842                 return;
2843         case REQ_SCROLL_PAGE_DOWN:
2844                 lines = view->height;
2845         case REQ_SCROLL_LINE_DOWN:
2846                 if (view->offset + lines > view->lines)
2847                         lines = view->lines - view->offset;
2849                 if (lines == 0 || view->offset + view->height >= view->lines) {
2850                         report("Cannot scroll beyond the last line");
2851                         return;
2852                 }
2853                 break;
2855         case REQ_SCROLL_PAGE_UP:
2856                 lines = view->height;
2857         case REQ_SCROLL_LINE_UP:
2858                 if (lines > view->offset)
2859                         lines = view->offset;
2861                 if (lines == 0) {
2862                         report("Cannot scroll beyond the first line");
2863                         return;
2864                 }
2866                 lines = -lines;
2867                 break;
2869         default:
2870                 die("request %d not handled in switch", request);
2871         }
2873         do_scroll_view(view, lines);
2876 /* Cursor moving */
2877 static void
2878 move_view(struct view *view, enum request request)
2880         int scroll_steps = 0;
2881         int steps;
2883         switch (request) {
2884         case REQ_MOVE_FIRST_LINE:
2885                 steps = -view->lineno;
2886                 break;
2888         case REQ_MOVE_LAST_LINE:
2889                 steps = view->lines - view->lineno - 1;
2890                 break;
2892         case REQ_MOVE_PAGE_UP:
2893                 steps = view->height > view->lineno
2894                       ? -view->lineno : -view->height;
2895                 break;
2897         case REQ_MOVE_PAGE_DOWN:
2898                 steps = view->lineno + view->height >= view->lines
2899                       ? view->lines - view->lineno - 1 : view->height;
2900                 break;
2902         case REQ_MOVE_UP:
2903                 steps = -1;
2904                 break;
2906         case REQ_MOVE_DOWN:
2907                 steps = 1;
2908                 break;
2910         default:
2911                 die("request %d not handled in switch", request);
2912         }
2914         if (steps <= 0 && view->lineno == 0) {
2915                 report("Cannot move beyond the first line");
2916                 return;
2918         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2919                 report("Cannot move beyond the last line");
2920                 return;
2921         }
2923         /* Move the current line */
2924         view->lineno += steps;
2925         assert(0 <= view->lineno && view->lineno < view->lines);
2927         /* Check whether the view needs to be scrolled */
2928         if (view->lineno < view->offset ||
2929             view->lineno >= view->offset + view->height) {
2930                 scroll_steps = steps;
2931                 if (steps < 0 && -steps > view->offset) {
2932                         scroll_steps = -view->offset;
2934                 } else if (steps > 0) {
2935                         if (view->lineno == view->lines - 1 &&
2936                             view->lines > view->height) {
2937                                 scroll_steps = view->lines - view->offset - 1;
2938                                 if (scroll_steps >= view->height)
2939                                         scroll_steps -= view->height - 1;
2940                         }
2941                 }
2942         }
2944         if (!view_is_displayed(view)) {
2945                 view->offset += scroll_steps;
2946                 assert(0 <= view->offset && view->offset < view->lines);
2947                 view->ops->select(view, &view->line[view->lineno]);
2948                 return;
2949         }
2951         /* Repaint the old "current" line if we be scrolling */
2952         if (ABS(steps) < view->height)
2953                 draw_view_line(view, view->lineno - steps - view->offset);
2955         if (scroll_steps) {
2956                 do_scroll_view(view, scroll_steps);
2957                 return;
2958         }
2960         /* Draw the current line */
2961         draw_view_line(view, view->lineno - view->offset);
2963         wnoutrefresh(view->win);
2964         report("");
2968 /*
2969  * Searching
2970  */
2972 static void search_view(struct view *view, enum request request);
2974 static bool
2975 grep_text(struct view *view, const char *text[])
2977         regmatch_t pmatch;
2978         size_t i;
2980         for (i = 0; text[i]; i++)
2981                 if (*text[i] &&
2982                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2983                         return TRUE;
2984         return FALSE;
2987 static void
2988 select_view_line(struct view *view, unsigned long lineno)
2990         unsigned long old_lineno = view->lineno;
2991         unsigned long old_offset = view->offset;
2993         if (goto_view_line(view, view->offset, lineno)) {
2994                 if (view_is_displayed(view)) {
2995                         if (old_offset != view->offset) {
2996                                 redraw_view(view);
2997                         } else {
2998                                 draw_view_line(view, old_lineno - view->offset);
2999                                 draw_view_line(view, view->lineno - view->offset);
3000                                 wnoutrefresh(view->win);
3001                         }
3002                 } else {
3003                         view->ops->select(view, &view->line[view->lineno]);
3004                 }
3005         }
3008 static void
3009 find_next(struct view *view, enum request request)
3011         unsigned long lineno = view->lineno;
3012         int direction;
3014         if (!*view->grep) {
3015                 if (!*opt_search)
3016                         report("No previous search");
3017                 else
3018                         search_view(view, request);
3019                 return;
3020         }
3022         switch (request) {
3023         case REQ_SEARCH:
3024         case REQ_FIND_NEXT:
3025                 direction = 1;
3026                 break;
3028         case REQ_SEARCH_BACK:
3029         case REQ_FIND_PREV:
3030                 direction = -1;
3031                 break;
3033         default:
3034                 return;
3035         }
3037         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3038                 lineno += direction;
3040         /* Note, lineno is unsigned long so will wrap around in which case it
3041          * will become bigger than view->lines. */
3042         for (; lineno < view->lines; lineno += direction) {
3043                 if (view->ops->grep(view, &view->line[lineno])) {
3044                         select_view_line(view, lineno);
3045                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3046                         return;
3047                 }
3048         }
3050         report("No match found for '%s'", view->grep);
3053 static void
3054 search_view(struct view *view, enum request request)
3056         int regex_err;
3058         if (view->regex) {
3059                 regfree(view->regex);
3060                 *view->grep = 0;
3061         } else {
3062                 view->regex = calloc(1, sizeof(*view->regex));
3063                 if (!view->regex)
3064                         return;
3065         }
3067         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3068         if (regex_err != 0) {
3069                 char buf[SIZEOF_STR] = "unknown error";
3071                 regerror(regex_err, view->regex, buf, sizeof(buf));
3072                 report("Search failed: %s", buf);
3073                 return;
3074         }
3076         string_copy(view->grep, opt_search);
3078         find_next(view, request);
3081 /*
3082  * Incremental updating
3083  */
3085 static void
3086 reset_view(struct view *view)
3088         int i;
3090         for (i = 0; i < view->lines; i++)
3091                 free(view->line[i].data);
3092         free(view->line);
3094         view->p_offset = view->offset;
3095         view->p_yoffset = view->yoffset;
3096         view->p_lineno = view->lineno;
3098         view->line = NULL;
3099         view->offset = 0;
3100         view->yoffset = 0;
3101         view->lines  = 0;
3102         view->lineno = 0;
3103         view->vid[0] = 0;
3104         view->update_secs = 0;
3107 static void
3108 free_argv(const char *argv[])
3110         int argc;
3112         for (argc = 0; argv[argc]; argc++)
3113                 free((void *) argv[argc]);
3116 static const char *
3117 format_arg(const char *name)
3119         static struct {
3120                 const char *name;
3121                 size_t namelen;
3122                 const char *value;
3123                 const char *value_if_empty;
3124         } vars[] = {
3125 #define FORMAT_VAR(name, value, value_if_empty) \
3126         { name, STRING_SIZE(name), value, value_if_empty }
3127                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3128                 FORMAT_VAR("%(file)",           opt_file,       ""),
3129                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3130                 FORMAT_VAR("%(head)",           ref_head,       ""),
3131                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3132                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3133                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3134         };
3135         int i;
3137         for (i = 0; i < ARRAY_SIZE(vars); i++)
3138                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3139                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3141         report("Unknown replacement: `%s`", name);
3142         return NULL;
3145 static bool
3146 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3148         char buf[SIZEOF_STR];
3149         int argc;
3150         bool noreplace = flags == FORMAT_NONE;
3152         free_argv(dst_argv);
3154         for (argc = 0; src_argv[argc]; argc++) {
3155                 const char *arg = src_argv[argc];
3156                 size_t bufpos = 0;
3158                 while (arg) {
3159                         char *next = strstr(arg, "%(");
3160                         int len = next - arg;
3161                         const char *value;
3163                         if (!next || noreplace) {
3164                                 len = strlen(arg);
3165                                 value = "";
3167                         } else {
3168                                 value = format_arg(next);
3170                                 if (!value) {
3171                                         return FALSE;
3172                                 }
3173                         }
3175                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3176                                 return FALSE;
3178                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3179                 }
3181                 dst_argv[argc] = strdup(buf);
3182                 if (!dst_argv[argc])
3183                         break;
3184         }
3186         dst_argv[argc] = NULL;
3188         return src_argv[argc] == NULL;
3191 static bool
3192 restore_view_position(struct view *view)
3194         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3195                 return FALSE;
3197         /* Changing the view position cancels the restoring. */
3198         /* FIXME: Changing back to the first line is not detected. */
3199         if (view->offset != 0 || view->lineno != 0) {
3200                 view->p_restore = FALSE;
3201                 return FALSE;
3202         }
3204         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3205             view_is_displayed(view))
3206                 werase(view->win);
3208         view->yoffset = view->p_yoffset;
3209         view->p_restore = FALSE;
3211         return TRUE;
3214 static void
3215 end_update(struct view *view, bool force)
3217         if (!view->pipe)
3218                 return;
3219         while (!view->ops->read(view, NULL))
3220                 if (!force)
3221                         return;
3222         if (force)
3223                 io_kill(view->pipe);
3224         io_done(view->pipe);
3225         view->pipe = NULL;
3228 static void
3229 setup_update(struct view *view, const char *vid)
3231         reset_view(view);
3232         string_copy_rev(view->vid, vid);
3233         view->pipe = &view->io;
3234         view->start_time = time(NULL);
3237 static bool
3238 prepare_update(struct view *view, const char *argv[], const char *dir)
3240         if (view->pipe)
3241                 end_update(view, TRUE);
3242         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3245 static bool
3246 prepare_update_file(struct view *view, const char *name)
3248         if (view->pipe)
3249                 end_update(view, TRUE);
3250         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3253 static bool
3254 begin_update(struct view *view, bool refresh)
3256         if (view->pipe)
3257                 end_update(view, TRUE);
3259         if (!refresh) {
3260                 if (view->ops->prepare) {
3261                         if (!view->ops->prepare(view))
3262                                 return FALSE;
3263                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3264                         return FALSE;
3265                 }
3267                 /* Put the current ref_* value to the view title ref
3268                  * member. This is needed by the blob view. Most other
3269                  * views sets it automatically after loading because the
3270                  * first line is a commit line. */
3271                 string_copy_rev(view->ref, view->id);
3272         }
3274         if (!io_start(&view->io))
3275                 return FALSE;
3277         setup_update(view, view->id);
3279         return TRUE;
3282 static bool
3283 update_view(struct view *view)
3285         char out_buffer[BUFSIZ * 2];
3286         char *line;
3287         /* Clear the view and redraw everything since the tree sorting
3288          * might have rearranged things. */
3289         bool redraw = view->lines == 0;
3290         bool can_read = TRUE;
3292         if (!view->pipe)
3293                 return TRUE;
3295         if (!io_can_read(view->pipe)) {
3296                 if (view->lines == 0 && view_is_displayed(view)) {
3297                         time_t secs = time(NULL) - view->start_time;
3299                         if (secs > 1 && secs > view->update_secs) {
3300                                 if (view->update_secs == 0)
3301                                         redraw_view(view);
3302                                 update_view_title(view);
3303                                 view->update_secs = secs;
3304                         }
3305                 }
3306                 return TRUE;
3307         }
3309         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3310                 if (opt_iconv_in != ICONV_NONE) {
3311                         ICONV_CONST char *inbuf = line;
3312                         size_t inlen = strlen(line) + 1;
3314                         char *outbuf = out_buffer;
3315                         size_t outlen = sizeof(out_buffer);
3317                         size_t ret;
3319                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3320                         if (ret != (size_t) -1)
3321                                 line = out_buffer;
3322                 }
3324                 if (!view->ops->read(view, line)) {
3325                         report("Allocation failure");
3326                         end_update(view, TRUE);
3327                         return FALSE;
3328                 }
3329         }
3331         {
3332                 unsigned long lines = view->lines;
3333                 int digits;
3335                 for (digits = 0; lines; digits++)
3336                         lines /= 10;
3338                 /* Keep the displayed view in sync with line number scaling. */
3339                 if (digits != view->digits) {
3340                         view->digits = digits;
3341                         if (opt_line_number || view->type == VIEW_BLAME)
3342                                 redraw = TRUE;
3343                 }
3344         }
3346         if (io_error(view->pipe)) {
3347                 report("Failed to read: %s", io_strerror(view->pipe));
3348                 end_update(view, TRUE);
3350         } else if (io_eof(view->pipe)) {
3351                 report("");
3352                 end_update(view, FALSE);
3353         }
3355         if (restore_view_position(view))
3356                 redraw = TRUE;
3358         if (!view_is_displayed(view))
3359                 return TRUE;
3361         if (redraw)
3362                 redraw_view_from(view, 0);
3363         else
3364                 redraw_view_dirty(view);
3366         /* Update the title _after_ the redraw so that if the redraw picks up a
3367          * commit reference in view->ref it'll be available here. */
3368         update_view_title(view);
3369         return TRUE;
3372 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3374 static struct line *
3375 add_line_data(struct view *view, void *data, enum line_type type)
3377         struct line *line;
3379         if (!realloc_lines(&view->line, view->lines, 1))
3380                 return NULL;
3382         line = &view->line[view->lines++];
3383         memset(line, 0, sizeof(*line));
3384         line->type = type;
3385         line->data = data;
3386         line->dirty = 1;
3388         return line;
3391 static struct line *
3392 add_line_text(struct view *view, const char *text, enum line_type type)
3394         char *data = text ? strdup(text) : NULL;
3396         return data ? add_line_data(view, data, type) : NULL;
3399 static struct line *
3400 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3402         char buf[SIZEOF_STR];
3403         va_list args;
3405         va_start(args, fmt);
3406         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3407                 buf[0] = 0;
3408         va_end(args);
3410         return buf[0] ? add_line_text(view, buf, type) : NULL;
3413 /*
3414  * View opening
3415  */
3417 enum open_flags {
3418         OPEN_DEFAULT = 0,       /* Use default view switching. */
3419         OPEN_SPLIT = 1,         /* Split current view. */
3420         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3421         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3422         OPEN_PREPARED = 32,     /* Open already prepared command. */
3423 };
3425 static void
3426 open_view(struct view *prev, enum request request, enum open_flags flags)
3428         bool split = !!(flags & OPEN_SPLIT);
3429         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3430         bool nomaximize = !!(flags & OPEN_REFRESH);
3431         struct view *view = VIEW(request);
3432         int nviews = displayed_views();
3433         struct view *base_view = display[0];
3435         if (view == prev && nviews == 1 && !reload) {
3436                 report("Already in %s view", view->name);
3437                 return;
3438         }
3440         if (view->git_dir && !opt_git_dir[0]) {
3441                 report("The %s view is disabled in pager view", view->name);
3442                 return;
3443         }
3445         if (split) {
3446                 display[1] = view;
3447                 current_view = 1;
3448         } else if (!nomaximize) {
3449                 /* Maximize the current view. */
3450                 memset(display, 0, sizeof(display));
3451                 current_view = 0;
3452                 display[current_view] = view;
3453         }
3455         /* No parent signals that this is the first loaded view. */
3456         if (prev && view != prev) {
3457                 view->parent = prev;
3458         }
3460         /* Resize the view when switching between split- and full-screen,
3461          * or when switching between two different full-screen views. */
3462         if (nviews != displayed_views() ||
3463             (nviews == 1 && base_view != display[0]))
3464                 resize_display();
3466         if (view->ops->open) {
3467                 if (view->pipe)
3468                         end_update(view, TRUE);
3469                 if (!view->ops->open(view)) {
3470                         report("Failed to load %s view", view->name);
3471                         return;
3472                 }
3473                 restore_view_position(view);
3475         } else if ((reload || strcmp(view->vid, view->id)) &&
3476                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3477                 report("Failed to load %s view", view->name);
3478                 return;
3479         }
3481         if (split && prev->lineno - prev->offset >= prev->height) {
3482                 /* Take the title line into account. */
3483                 int lines = prev->lineno - prev->offset - prev->height + 1;
3485                 /* Scroll the view that was split if the current line is
3486                  * outside the new limited view. */
3487                 do_scroll_view(prev, lines);
3488         }
3490         if (prev && view != prev && split && view_is_displayed(prev)) {
3491                 /* "Blur" the previous view. */
3492                 update_view_title(prev);
3493         }
3495         if (view->pipe && view->lines == 0) {
3496                 /* Clear the old view and let the incremental updating refill
3497                  * the screen. */
3498                 werase(view->win);
3499                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3500                 report("");
3501         } else if (view_is_displayed(view)) {
3502                 redraw_view(view);
3503                 report("");
3504         }
3507 static void
3508 open_external_viewer(const char *argv[], const char *dir)
3510         def_prog_mode();           /* save current tty modes */
3511         endwin();                  /* restore original tty modes */
3512         io_run_fg(argv, dir);
3513         fprintf(stderr, "Press Enter to continue");
3514         getc(opt_tty);
3515         reset_prog_mode();
3516         redraw_display(TRUE);
3519 static void
3520 open_mergetool(const char *file)
3522         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3524         open_external_viewer(mergetool_argv, opt_cdup);
3527 static void
3528 open_editor(const char *file)
3530         const char *editor_argv[] = { "vi", file, NULL };
3531         const char *editor;
3533         editor = getenv("GIT_EDITOR");
3534         if (!editor && *opt_editor)
3535                 editor = opt_editor;
3536         if (!editor)
3537                 editor = getenv("VISUAL");
3538         if (!editor)
3539                 editor = getenv("EDITOR");
3540         if (!editor)
3541                 editor = "vi";
3543         editor_argv[0] = editor;
3544         open_external_viewer(editor_argv, opt_cdup);
3547 static void
3548 open_run_request(enum request request)
3550         struct run_request *req = get_run_request(request);
3551         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3553         if (!req) {
3554                 report("Unknown run request");
3555                 return;
3556         }
3558         if (format_argv(argv, req->argv, FORMAT_ALL))
3559                 open_external_viewer(argv, NULL);
3560         free_argv(argv);
3563 /*
3564  * User request switch noodle
3565  */
3567 static int
3568 view_driver(struct view *view, enum request request)
3570         int i;
3572         if (request == REQ_NONE)
3573                 return TRUE;
3575         if (request > REQ_NONE) {
3576                 open_run_request(request);
3577                 /* FIXME: When all views can refresh always do this. */
3578                 if (view->refresh)
3579                         request = REQ_REFRESH;
3580                 else
3581                         return TRUE;
3582         }
3584         if (view && view->lines) {
3585                 request = view->ops->request(view, request, &view->line[view->lineno]);
3586                 if (request == REQ_NONE)
3587                         return TRUE;
3588         }
3590         switch (request) {
3591         case REQ_MOVE_UP:
3592         case REQ_MOVE_DOWN:
3593         case REQ_MOVE_PAGE_UP:
3594         case REQ_MOVE_PAGE_DOWN:
3595         case REQ_MOVE_FIRST_LINE:
3596         case REQ_MOVE_LAST_LINE:
3597                 move_view(view, request);
3598                 break;
3600         case REQ_SCROLL_LEFT:
3601         case REQ_SCROLL_RIGHT:
3602         case REQ_SCROLL_LINE_DOWN:
3603         case REQ_SCROLL_LINE_UP:
3604         case REQ_SCROLL_PAGE_DOWN:
3605         case REQ_SCROLL_PAGE_UP:
3606                 scroll_view(view, request);
3607                 break;
3609         case REQ_VIEW_BLAME:
3610                 if (!opt_file[0]) {
3611                         report("No file chosen, press %s to open tree view",
3612                                get_key(view->keymap, REQ_VIEW_TREE));
3613                         break;
3614                 }
3615                 open_view(view, request, OPEN_DEFAULT);
3616                 break;
3618         case REQ_VIEW_BLOB:
3619                 if (!ref_blob[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_PAGER:
3628                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3629                         report("No pager content, press %s to run command from prompt",
3630                                get_key(view->keymap, REQ_PROMPT));
3631                         break;
3632                 }
3633                 open_view(view, request, OPEN_DEFAULT);
3634                 break;
3636         case REQ_VIEW_STAGE:
3637                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3638                         report("No stage content, press %s to open the status view and choose file",
3639                                get_key(view->keymap, REQ_VIEW_STATUS));
3640                         break;
3641                 }
3642                 open_view(view, request, OPEN_DEFAULT);
3643                 break;
3645         case REQ_VIEW_STATUS:
3646                 if (opt_is_inside_work_tree == FALSE) {
3647                         report("The status view requires a working tree");
3648                         break;
3649                 }
3650                 open_view(view, request, OPEN_DEFAULT);
3651                 break;
3653         case REQ_VIEW_MAIN:
3654         case REQ_VIEW_DIFF:
3655         case REQ_VIEW_LOG:
3656         case REQ_VIEW_TREE:
3657         case REQ_VIEW_HELP:
3658         case REQ_VIEW_BRANCH:
3659                 open_view(view, request, OPEN_DEFAULT);
3660                 break;
3662         case REQ_NEXT:
3663         case REQ_PREVIOUS:
3664                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3666                 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3667                     view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3668                     view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3669                     view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3670                     view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3671                         int line;
3673                         view = view->parent;
3674                         line = view->lineno;
3675                         move_view(view, request);
3676                         if (view_is_displayed(view))
3677                                 update_view_title(view);
3678                         if (line != view->lineno)
3679                                 view->ops->request(view, REQ_ENTER,
3680                                                    &view->line[view->lineno]);
3682                 } else {
3683                         move_view(view, request);
3684                 }
3685                 break;
3687         case REQ_VIEW_NEXT:
3688         {
3689                 int nviews = displayed_views();
3690                 int next_view = (current_view + 1) % nviews;
3692                 if (next_view == current_view) {
3693                         report("Only one view is displayed");
3694                         break;
3695                 }
3697                 current_view = next_view;
3698                 /* Blur out the title of the previous view. */
3699                 update_view_title(view);
3700                 report("");
3701                 break;
3702         }
3703         case REQ_REFRESH:
3704                 report("Refreshing is not yet supported for the %s view", view->name);
3705                 break;
3707         case REQ_MAXIMIZE:
3708                 if (displayed_views() == 2)
3709                         maximize_view(view);
3710                 break;
3712         case REQ_OPTIONS:
3713                 open_option_menu();
3714                 break;
3716         case REQ_TOGGLE_LINENO:
3717                 toggle_view_option(&opt_line_number, "line numbers");
3718                 break;
3720         case REQ_TOGGLE_DATE:
3721                 toggle_date();
3722                 break;
3724         case REQ_TOGGLE_AUTHOR:
3725                 toggle_author();
3726                 break;
3728         case REQ_TOGGLE_REV_GRAPH:
3729                 toggle_view_option(&opt_rev_graph, "revision graph display");
3730                 break;
3732         case REQ_TOGGLE_REFS:
3733                 toggle_view_option(&opt_show_refs, "reference display");
3734                 break;
3736         case REQ_TOGGLE_SORT_FIELD:
3737         case REQ_TOGGLE_SORT_ORDER:
3738                 report("Sorting is not yet supported for the %s view", view->name);
3739                 break;
3741         case REQ_SEARCH:
3742         case REQ_SEARCH_BACK:
3743                 search_view(view, request);
3744                 break;
3746         case REQ_FIND_NEXT:
3747         case REQ_FIND_PREV:
3748                 find_next(view, request);
3749                 break;
3751         case REQ_STOP_LOADING:
3752                 foreach_view(view, i) {
3753                         if (view->pipe)
3754                                 report("Stopped loading the %s view", view->name),
3755                         end_update(view, TRUE);
3756                 }
3757                 break;
3759         case REQ_SHOW_VERSION:
3760                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3761                 return TRUE;
3763         case REQ_SCREEN_REDRAW:
3764                 redraw_display(TRUE);
3765                 break;
3767         case REQ_EDIT:
3768                 report("Nothing to edit");
3769                 break;
3771         case REQ_ENTER:
3772                 report("Nothing to enter");
3773                 break;
3775         case REQ_VIEW_CLOSE:
3776                 /* XXX: Mark closed views by letting view->parent point to the
3777                  * view itself. Parents to closed view should never be
3778                  * followed. */
3779                 if (view->parent &&
3780                     view->parent->parent != view->parent) {
3781                         maximize_view(view->parent);
3782                         view->parent = view;
3783                         break;
3784                 }
3785                 /* Fall-through */
3786         case REQ_QUIT:
3787                 return FALSE;
3789         default:
3790                 report("Unknown key, press %s for help",
3791                        get_key(view->keymap, REQ_VIEW_HELP));
3792                 return TRUE;
3793         }
3795         return TRUE;
3799 /*
3800  * View backend utilities
3801  */
3803 enum sort_field {
3804         ORDERBY_NAME,
3805         ORDERBY_DATE,
3806         ORDERBY_AUTHOR,
3807 };
3809 struct sort_state {
3810         const enum sort_field *fields;
3811         size_t size, current;
3812         bool reverse;
3813 };
3815 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3816 #define get_sort_field(state) ((state).fields[(state).current])
3817 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3819 static void
3820 sort_view(struct view *view, enum request request, struct sort_state *state,
3821           int (*compare)(const void *, const void *))
3823         switch (request) {
3824         case REQ_TOGGLE_SORT_FIELD:
3825                 state->current = (state->current + 1) % state->size;
3826                 break;
3828         case REQ_TOGGLE_SORT_ORDER:
3829                 state->reverse = !state->reverse;
3830                 break;
3831         default:
3832                 die("Not a sort request");
3833         }
3835         qsort(view->line, view->lines, sizeof(*view->line), compare);
3836         redraw_view(view);
3839 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3841 /* Small author cache to reduce memory consumption. It uses binary
3842  * search to lookup or find place to position new entries. No entries
3843  * are ever freed. */
3844 static const char *
3845 get_author(const char *name)
3847         static const char **authors;
3848         static size_t authors_size;
3849         int from = 0, to = authors_size - 1;
3851         while (from <= to) {
3852                 size_t pos = (to + from) / 2;
3853                 int cmp = strcmp(name, authors[pos]);
3855                 if (!cmp)
3856                         return authors[pos];
3858                 if (cmp < 0)
3859                         to = pos - 1;
3860                 else
3861                         from = pos + 1;
3862         }
3864         if (!realloc_authors(&authors, authors_size, 1))
3865                 return NULL;
3866         name = strdup(name);
3867         if (!name)
3868                 return NULL;
3870         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3871         authors[from] = name;
3872         authors_size++;
3874         return name;
3877 static void
3878 parse_timesec(struct time *time, const char *sec)
3880         time->sec = (time_t) atol(sec);
3883 static void
3884 parse_timezone(struct time *time, const char *zone)
3886         long tz;
3888         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3889         tz += ('0' - zone[2]) * 60 * 60;
3890         tz += ('0' - zone[3]) * 60;
3891         tz += ('0' - zone[4]);
3893         if (zone[0] == '-')
3894                 tz = -tz;
3896         time->tz = tz;
3897         time->sec -= tz;
3900 /* Parse author lines where the name may be empty:
3901  *      author  <email@address.tld> 1138474660 +0100
3902  */
3903 static void
3904 parse_author_line(char *ident, const char **author, struct time *time)
3906         char *nameend = strchr(ident, '<');
3907         char *emailend = strchr(ident, '>');
3909         if (nameend && emailend)
3910                 *nameend = *emailend = 0;
3911         ident = chomp_string(ident);
3912         if (!*ident) {
3913                 if (nameend)
3914                         ident = chomp_string(nameend + 1);
3915                 if (!*ident)
3916                         ident = "Unknown";
3917         }
3919         *author = get_author(ident);
3921         /* Parse epoch and timezone */
3922         if (emailend && emailend[1] == ' ') {
3923                 char *secs = emailend + 2;
3924                 char *zone = strchr(secs, ' ');
3926                 parse_timesec(time, secs);
3928                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3929                         parse_timezone(time, zone + 1);
3930         }
3933 static bool
3934 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3936         char rev[SIZEOF_REV];
3937         const char *revlist_argv[] = {
3938                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3939         };
3940         struct menu_item *items;
3941         char text[SIZEOF_STR];
3942         bool ok = TRUE;
3943         int i;
3945         items = calloc(*parents + 1, sizeof(*items));
3946         if (!items)
3947                 return FALSE;
3949         for (i = 0; i < *parents; i++) {
3950                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3951                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3952                     !(items[i].text = strdup(text))) {
3953                         ok = FALSE;
3954                         break;
3955                 }
3956         }
3958         if (ok) {
3959                 *parents = 0;
3960                 ok = prompt_menu("Select parent", items, parents);
3961         }
3962         for (i = 0; items[i].text; i++)
3963                 free((char *) items[i].text);
3964         free(items);
3965         return ok;
3968 static bool
3969 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3971         char buf[SIZEOF_STR * 4];
3972         const char *revlist_argv[] = {
3973                 "git", "log", "--no-color", "-1",
3974                         "--pretty=format:%P", id, "--", path, NULL
3975         };
3976         int parents;
3978         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3979             (parents = strlen(buf) / 40) < 0) {
3980                 report("Failed to get parent information");
3981                 return FALSE;
3983         } else if (parents == 0) {
3984                 if (path)
3985                         report("Path '%s' does not exist in the parent", path);
3986                 else
3987                         report("The selected commit has no parents");
3988                 return FALSE;
3989         }
3991         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3992                 return FALSE;
3994         string_copy_rev(rev, &buf[41 * parents]);
3995         return TRUE;
3998 /*
3999  * Pager backend
4000  */
4002 static bool
4003 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4005         char text[SIZEOF_STR];
4007         if (opt_line_number && draw_lineno(view, lineno))
4008                 return TRUE;
4010         string_expand(text, sizeof(text), line->data, opt_tab_size);
4011         draw_text(view, line->type, text, TRUE);
4012         return TRUE;
4015 static bool
4016 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4018         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4019         char ref[SIZEOF_STR];
4021         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4022                 return TRUE;
4024         /* This is the only fatal call, since it can "corrupt" the buffer. */
4025         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4026                 return FALSE;
4028         return TRUE;
4031 static void
4032 add_pager_refs(struct view *view, struct line *line)
4034         char buf[SIZEOF_STR];
4035         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4036         struct ref_list *list;
4037         size_t bufpos = 0, i;
4038         const char *sep = "Refs: ";
4039         bool is_tag = FALSE;
4041         assert(line->type == LINE_COMMIT);
4043         list = get_ref_list(commit_id);
4044         if (!list) {
4045                 if (view->type == VIEW_DIFF)
4046                         goto try_add_describe_ref;
4047                 return;
4048         }
4050         for (i = 0; i < list->size; i++) {
4051                 struct ref *ref = list->refs[i];
4052                 const char *fmt = ref->tag    ? "%s[%s]" :
4053                                   ref->remote ? "%s<%s>" : "%s%s";
4055                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4056                         return;
4057                 sep = ", ";
4058                 if (ref->tag)
4059                         is_tag = TRUE;
4060         }
4062         if (!is_tag && view->type == VIEW_DIFF) {
4063 try_add_describe_ref:
4064                 /* Add <tag>-g<commit_id> "fake" reference. */
4065                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4066                         return;
4067         }
4069         if (bufpos == 0)
4070                 return;
4072         add_line_text(view, buf, LINE_PP_REFS);
4075 static bool
4076 pager_read(struct view *view, char *data)
4078         struct line *line;
4080         if (!data)
4081                 return TRUE;
4083         line = add_line_text(view, data, get_line_type(data));
4084         if (!line)
4085                 return FALSE;
4087         if (line->type == LINE_COMMIT &&
4088             (view->type == VIEW_DIFF ||
4089              view->type == VIEW_LOG))
4090                 add_pager_refs(view, line);
4092         return TRUE;
4095 static enum request
4096 pager_request(struct view *view, enum request request, struct line *line)
4098         int split = 0;
4100         if (request != REQ_ENTER)
4101                 return request;
4103         if (line->type == LINE_COMMIT &&
4104            (view->type == VIEW_LOG ||
4105             view->type == VIEW_PAGER)) {
4106                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4107                 split = 1;
4108         }
4110         /* Always scroll the view even if it was split. That way
4111          * you can use Enter to scroll through the log view and
4112          * split open each commit diff. */
4113         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4115         /* FIXME: A minor workaround. Scrolling the view will call report("")
4116          * but if we are scrolling a non-current view this won't properly
4117          * update the view title. */
4118         if (split)
4119                 update_view_title(view);
4121         return REQ_NONE;
4124 static bool
4125 pager_grep(struct view *view, struct line *line)
4127         const char *text[] = { line->data, NULL };
4129         return grep_text(view, text);
4132 static void
4133 pager_select(struct view *view, struct line *line)
4135         if (line->type == LINE_COMMIT) {
4136                 char *text = (char *)line->data + STRING_SIZE("commit ");
4138                 if (view->type != VIEW_PAGER)
4139                         string_copy_rev(view->ref, text);
4140                 string_copy_rev(ref_commit, text);
4141         }
4144 static struct view_ops pager_ops = {
4145         "line",
4146         NULL,
4147         NULL,
4148         pager_read,
4149         pager_draw,
4150         pager_request,
4151         pager_grep,
4152         pager_select,
4153 };
4155 static const char *log_argv[SIZEOF_ARG] = {
4156         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4157 };
4159 static enum request
4160 log_request(struct view *view, enum request request, struct line *line)
4162         switch (request) {
4163         case REQ_REFRESH:
4164                 load_refs();
4165                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4166                 return REQ_NONE;
4167         default:
4168                 return pager_request(view, request, line);
4169         }
4172 static struct view_ops log_ops = {
4173         "line",
4174         log_argv,
4175         NULL,
4176         pager_read,
4177         pager_draw,
4178         log_request,
4179         pager_grep,
4180         pager_select,
4181 };
4183 static const char *diff_argv[SIZEOF_ARG] = {
4184         "git", "show", "--pretty=fuller", "--no-color", "--root",
4185                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4186 };
4188 static struct view_ops diff_ops = {
4189         "line",
4190         diff_argv,
4191         NULL,
4192         pager_read,
4193         pager_draw,
4194         pager_request,
4195         pager_grep,
4196         pager_select,
4197 };
4199 /*
4200  * Help backend
4201  */
4203 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4205 static bool
4206 help_open_keymap_title(struct view *view, enum keymap keymap)
4208         struct line *line;
4210         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4211                                help_keymap_hidden[keymap] ? '+' : '-',
4212                                enum_name(keymap_table[keymap]));
4213         if (line)
4214                 line->other = keymap;
4216         return help_keymap_hidden[keymap];
4219 static void
4220 help_open_keymap(struct view *view, enum keymap keymap)
4222         const char *group = NULL;
4223         char buf[SIZEOF_STR];
4224         size_t bufpos;
4225         bool add_title = TRUE;
4226         int i;
4228         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4229                 const char *key = NULL;
4231                 if (req_info[i].request == REQ_NONE)
4232                         continue;
4234                 if (!req_info[i].request) {
4235                         group = req_info[i].help;
4236                         continue;
4237                 }
4239                 key = get_keys(keymap, req_info[i].request, TRUE);
4240                 if (!key || !*key)
4241                         continue;
4243                 if (add_title && help_open_keymap_title(view, keymap))
4244                         return;
4245                 add_title = FALSE;
4247                 if (group) {
4248                         add_line_text(view, group, LINE_HELP_GROUP);
4249                         group = NULL;
4250                 }
4252                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4253                                 enum_name(req_info[i]), req_info[i].help);
4254         }
4256         group = "External commands:";
4258         for (i = 0; i < run_requests; i++) {
4259                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4260                 const char *key;
4261                 int argc;
4263                 if (!req || req->keymap != keymap)
4264                         continue;
4266                 key = get_key_name(req->key);
4267                 if (!*key)
4268                         key = "(no key defined)";
4270                 if (add_title && help_open_keymap_title(view, keymap))
4271                         return;
4272                 if (group) {
4273                         add_line_text(view, group, LINE_HELP_GROUP);
4274                         group = NULL;
4275                 }
4277                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4278                         if (!string_format_from(buf, &bufpos, "%s%s",
4279                                                 argc ? " " : "", req->argv[argc]))
4280                                 return;
4282                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4283         }
4286 static bool
4287 help_open(struct view *view)
4289         enum keymap keymap;
4291         reset_view(view);
4292         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4293         add_line_text(view, "", LINE_DEFAULT);
4295         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4296                 help_open_keymap(view, keymap);
4298         return TRUE;
4301 static enum request
4302 help_request(struct view *view, enum request request, struct line *line)
4304         switch (request) {
4305         case REQ_ENTER:
4306                 if (line->type == LINE_HELP_KEYMAP) {
4307                         help_keymap_hidden[line->other] =
4308                                 !help_keymap_hidden[line->other];
4309                         view->p_restore = TRUE;
4310                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4311                 }
4313                 return REQ_NONE;
4314         default:
4315                 return pager_request(view, request, line);
4316         }
4319 static struct view_ops help_ops = {
4320         "line",
4321         NULL,
4322         help_open,
4323         NULL,
4324         pager_draw,
4325         help_request,
4326         pager_grep,
4327         pager_select,
4328 };
4331 /*
4332  * Tree backend
4333  */
4335 struct tree_stack_entry {
4336         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4337         unsigned long lineno;           /* Line number to restore */
4338         char *name;                     /* Position of name in opt_path */
4339 };
4341 /* The top of the path stack. */
4342 static struct tree_stack_entry *tree_stack = NULL;
4343 unsigned long tree_lineno = 0;
4345 static void
4346 pop_tree_stack_entry(void)
4348         struct tree_stack_entry *entry = tree_stack;
4350         tree_lineno = entry->lineno;
4351         entry->name[0] = 0;
4352         tree_stack = entry->prev;
4353         free(entry);
4356 static void
4357 push_tree_stack_entry(const char *name, unsigned long lineno)
4359         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4360         size_t pathlen = strlen(opt_path);
4362         if (!entry)
4363                 return;
4365         entry->prev = tree_stack;
4366         entry->name = opt_path + pathlen;
4367         tree_stack = entry;
4369         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4370                 pop_tree_stack_entry();
4371                 return;
4372         }
4374         /* Move the current line to the first tree entry. */
4375         tree_lineno = 1;
4376         entry->lineno = lineno;
4379 /* Parse output from git-ls-tree(1):
4380  *
4381  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4382  */
4384 #define SIZEOF_TREE_ATTR \
4385         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4387 #define SIZEOF_TREE_MODE \
4388         STRING_SIZE("100644 ")
4390 #define TREE_ID_OFFSET \
4391         STRING_SIZE("100644 blob ")
4393 struct tree_entry {
4394         char id[SIZEOF_REV];
4395         mode_t mode;
4396         struct time time;               /* Date from the author ident. */
4397         const char *author;             /* Author of the commit. */
4398         char name[1];
4399 };
4401 static const char *
4402 tree_path(const struct line *line)
4404         return ((struct tree_entry *) line->data)->name;
4407 static int
4408 tree_compare_entry(const struct line *line1, const struct line *line2)
4410         if (line1->type != line2->type)
4411                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4412         return strcmp(tree_path(line1), tree_path(line2));
4415 static const enum sort_field tree_sort_fields[] = {
4416         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4417 };
4418 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4420 static int
4421 tree_compare(const void *l1, const void *l2)
4423         const struct line *line1 = (const struct line *) l1;
4424         const struct line *line2 = (const struct line *) l2;
4425         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4426         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4428         if (line1->type == LINE_TREE_HEAD)
4429                 return -1;
4430         if (line2->type == LINE_TREE_HEAD)
4431                 return 1;
4433         switch (get_sort_field(tree_sort_state)) {
4434         case ORDERBY_DATE:
4435                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4437         case ORDERBY_AUTHOR:
4438                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4440         case ORDERBY_NAME:
4441         default:
4442                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4443         }
4447 static struct line *
4448 tree_entry(struct view *view, enum line_type type, const char *path,
4449            const char *mode, const char *id)
4451         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4452         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4454         if (!entry || !line) {
4455                 free(entry);
4456                 return NULL;
4457         }
4459         strncpy(entry->name, path, strlen(path));
4460         if (mode)
4461                 entry->mode = strtoul(mode, NULL, 8);
4462         if (id)
4463                 string_copy_rev(entry->id, id);
4465         return line;
4468 static bool
4469 tree_read_date(struct view *view, char *text, bool *read_date)
4471         static const char *author_name;
4472         static struct time author_time;
4474         if (!text && *read_date) {
4475                 *read_date = FALSE;
4476                 return TRUE;
4478         } else if (!text) {
4479                 char *path = *opt_path ? opt_path : ".";
4480                 /* Find next entry to process */
4481                 const char *log_file[] = {
4482                         "git", "log", "--no-color", "--pretty=raw",
4483                                 "--cc", "--raw", view->id, "--", path, NULL
4484                 };
4485                 struct io io = {};
4487                 if (!view->lines) {
4488                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4489                         report("Tree is empty");
4490                         return TRUE;
4491                 }
4493                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4494                         report("Failed to load tree data");
4495                         return TRUE;
4496                 }
4498                 io_done(view->pipe);
4499                 view->io = io;
4500                 *read_date = TRUE;
4501                 return FALSE;
4503         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4504                 parse_author_line(text + STRING_SIZE("author "),
4505                                   &author_name, &author_time);
4507         } else if (*text == ':') {
4508                 char *pos;
4509                 size_t annotated = 1;
4510                 size_t i;
4512                 pos = strchr(text, '\t');
4513                 if (!pos)
4514                         return TRUE;
4515                 text = pos + 1;
4516                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4517                         text += strlen(opt_path);
4518                 pos = strchr(text, '/');
4519                 if (pos)
4520                         *pos = 0;
4522                 for (i = 1; i < view->lines; i++) {
4523                         struct line *line = &view->line[i];
4524                         struct tree_entry *entry = line->data;
4526                         annotated += !!entry->author;
4527                         if (entry->author || strcmp(entry->name, text))
4528                                 continue;
4530                         entry->author = author_name;
4531                         entry->time = author_time;
4532                         line->dirty = 1;
4533                         break;
4534                 }
4536                 if (annotated == view->lines)
4537                         io_kill(view->pipe);
4538         }
4539         return TRUE;
4542 static bool
4543 tree_read(struct view *view, char *text)
4545         static bool read_date = FALSE;
4546         struct tree_entry *data;
4547         struct line *entry, *line;
4548         enum line_type type;
4549         size_t textlen = text ? strlen(text) : 0;
4550         char *path = text + SIZEOF_TREE_ATTR;
4552         if (read_date || !text)
4553                 return tree_read_date(view, text, &read_date);
4555         if (textlen <= SIZEOF_TREE_ATTR)
4556                 return FALSE;
4557         if (view->lines == 0 &&
4558             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4559                 return FALSE;
4561         /* Strip the path part ... */
4562         if (*opt_path) {
4563                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4564                 size_t striplen = strlen(opt_path);
4566                 if (pathlen > striplen)
4567                         memmove(path, path + striplen,
4568                                 pathlen - striplen + 1);
4570                 /* Insert "link" to parent directory. */
4571                 if (view->lines == 1 &&
4572                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4573                         return FALSE;
4574         }
4576         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4577         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4578         if (!entry)
4579                 return FALSE;
4580         data = entry->data;
4582         /* Skip "Directory ..." and ".." line. */
4583         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4584                 if (tree_compare_entry(line, entry) <= 0)
4585                         continue;
4587                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4589                 line->data = data;
4590                 line->type = type;
4591                 for (; line <= entry; line++)
4592                         line->dirty = line->cleareol = 1;
4593                 return TRUE;
4594         }
4596         if (tree_lineno > view->lineno) {
4597                 view->lineno = tree_lineno;
4598                 tree_lineno = 0;
4599         }
4601         return TRUE;
4604 static bool
4605 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4607         struct tree_entry *entry = line->data;
4609         if (line->type == LINE_TREE_HEAD) {
4610                 if (draw_text(view, line->type, "Directory path /", TRUE))
4611                         return TRUE;
4612         } else {
4613                 if (draw_mode(view, entry->mode))
4614                         return TRUE;
4616                 if (opt_author && draw_author(view, entry->author))
4617                         return TRUE;
4619                 if (opt_date && draw_date(view, &entry->time))
4620                         return TRUE;
4621         }
4622         if (draw_text(view, line->type, entry->name, TRUE))
4623                 return TRUE;
4624         return TRUE;
4627 static void
4628 open_blob_editor()
4630         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4631         int fd = mkstemp(file);
4633         if (fd == -1)
4634                 report("Failed to create temporary file");
4635         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4636                 report("Failed to save blob data to file");
4637         else
4638                 open_editor(file);
4639         if (fd != -1)
4640                 unlink(file);
4643 static enum request
4644 tree_request(struct view *view, enum request request, struct line *line)
4646         enum open_flags flags;
4648         switch (request) {
4649         case REQ_VIEW_BLAME:
4650                 if (line->type != LINE_TREE_FILE) {
4651                         report("Blame only supported for files");
4652                         return REQ_NONE;
4653                 }
4655                 string_copy(opt_ref, view->vid);
4656                 return request;
4658         case REQ_EDIT:
4659                 if (line->type != LINE_TREE_FILE) {
4660                         report("Edit only supported for files");
4661                 } else if (!is_head_commit(view->vid)) {
4662                         open_blob_editor();
4663                 } else {
4664                         open_editor(opt_file);
4665                 }
4666                 return REQ_NONE;
4668         case REQ_TOGGLE_SORT_FIELD:
4669         case REQ_TOGGLE_SORT_ORDER:
4670                 sort_view(view, request, &tree_sort_state, tree_compare);
4671                 return REQ_NONE;
4673         case REQ_PARENT:
4674                 if (!*opt_path) {
4675                         /* quit view if at top of tree */
4676                         return REQ_VIEW_CLOSE;
4677                 }
4678                 /* fake 'cd  ..' */
4679                 line = &view->line[1];
4680                 break;
4682         case REQ_ENTER:
4683                 break;
4685         default:
4686                 return request;
4687         }
4689         /* Cleanup the stack if the tree view is at a different tree. */
4690         while (!*opt_path && tree_stack)
4691                 pop_tree_stack_entry();
4693         switch (line->type) {
4694         case LINE_TREE_DIR:
4695                 /* Depending on whether it is a subdirectory or parent link
4696                  * mangle the path buffer. */
4697                 if (line == &view->line[1] && *opt_path) {
4698                         pop_tree_stack_entry();
4700                 } else {
4701                         const char *basename = tree_path(line);
4703                         push_tree_stack_entry(basename, view->lineno);
4704                 }
4706                 /* Trees and subtrees share the same ID, so they are not not
4707                  * unique like blobs. */
4708                 flags = OPEN_RELOAD;
4709                 request = REQ_VIEW_TREE;
4710                 break;
4712         case LINE_TREE_FILE:
4713                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4714                 request = REQ_VIEW_BLOB;
4715                 break;
4717         default:
4718                 return REQ_NONE;
4719         }
4721         open_view(view, request, flags);
4722         if (request == REQ_VIEW_TREE)
4723                 view->lineno = tree_lineno;
4725         return REQ_NONE;
4728 static bool
4729 tree_grep(struct view *view, struct line *line)
4731         struct tree_entry *entry = line->data;
4732         const char *text[] = {
4733                 entry->name,
4734                 opt_author ? entry->author : "",
4735                 mkdate(&entry->time, opt_date),
4736                 NULL
4737         };
4739         return grep_text(view, text);
4742 static void
4743 tree_select(struct view *view, struct line *line)
4745         struct tree_entry *entry = line->data;
4747         if (line->type == LINE_TREE_FILE) {
4748                 string_copy_rev(ref_blob, entry->id);
4749                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4751         } else if (line->type != LINE_TREE_DIR) {
4752                 return;
4753         }
4755         string_copy_rev(view->ref, entry->id);
4758 static bool
4759 tree_prepare(struct view *view)
4761         if (view->lines == 0 && opt_prefix[0]) {
4762                 char *pos = opt_prefix;
4764                 while (pos && *pos) {
4765                         char *end = strchr(pos, '/');
4767                         if (end)
4768                                 *end = 0;
4769                         push_tree_stack_entry(pos, 0);
4770                         pos = end;
4771                         if (end) {
4772                                 *end = '/';
4773                                 pos++;
4774                         }
4775                 }
4777         } else if (strcmp(view->vid, view->id)) {
4778                 opt_path[0] = 0;
4779         }
4781         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4784 static const char *tree_argv[SIZEOF_ARG] = {
4785         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4786 };
4788 static struct view_ops tree_ops = {
4789         "file",
4790         tree_argv,
4791         NULL,
4792         tree_read,
4793         tree_draw,
4794         tree_request,
4795         tree_grep,
4796         tree_select,
4797         tree_prepare,
4798 };
4800 static bool
4801 blob_read(struct view *view, char *line)
4803         if (!line)
4804                 return TRUE;
4805         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4808 static enum request
4809 blob_request(struct view *view, enum request request, struct line *line)
4811         switch (request) {
4812         case REQ_EDIT:
4813                 open_blob_editor();
4814                 return REQ_NONE;
4815         default:
4816                 return pager_request(view, request, line);
4817         }
4820 static const char *blob_argv[SIZEOF_ARG] = {
4821         "git", "cat-file", "blob", "%(blob)", NULL
4822 };
4824 static struct view_ops blob_ops = {
4825         "line",
4826         blob_argv,
4827         NULL,
4828         blob_read,
4829         pager_draw,
4830         blob_request,
4831         pager_grep,
4832         pager_select,
4833 };
4835 /*
4836  * Blame backend
4837  *
4838  * Loading the blame view is a two phase job:
4839  *
4840  *  1. File content is read either using opt_file from the
4841  *     filesystem or using git-cat-file.
4842  *  2. Then blame information is incrementally added by
4843  *     reading output from git-blame.
4844  */
4846 static const char *blame_head_argv[] = {
4847         "git", "blame", "--incremental", "--", "%(file)", NULL
4848 };
4850 static const char *blame_ref_argv[] = {
4851         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4852 };
4854 static const char *blame_cat_file_argv[] = {
4855         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4856 };
4858 struct blame_commit {
4859         char id[SIZEOF_REV];            /* SHA1 ID. */
4860         char title[128];                /* First line of the commit message. */
4861         const char *author;             /* Author of the commit. */
4862         struct time time;               /* Date from the author ident. */
4863         char filename[128];             /* Name of file. */
4864         bool has_previous;              /* Was a "previous" line detected. */
4865 };
4867 struct blame {
4868         struct blame_commit *commit;
4869         unsigned long lineno;
4870         char text[1];
4871 };
4873 static bool
4874 blame_open(struct view *view)
4876         char path[SIZEOF_STR];
4878         if (!view->parent && *opt_prefix) {
4879                 string_copy(path, opt_file);
4880                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4881                         return FALSE;
4882         }
4884         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4885                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4886                         return FALSE;
4887         }
4889         setup_update(view, opt_file);
4890         string_format(view->ref, "%s ...", opt_file);
4892         return TRUE;
4895 static struct blame_commit *
4896 get_blame_commit(struct view *view, const char *id)
4898         size_t i;
4900         for (i = 0; i < view->lines; i++) {
4901                 struct blame *blame = view->line[i].data;
4903                 if (!blame->commit)
4904                         continue;
4906                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4907                         return blame->commit;
4908         }
4910         {
4911                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4913                 if (commit)
4914                         string_ncopy(commit->id, id, SIZEOF_REV);
4915                 return commit;
4916         }
4919 static bool
4920 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4922         const char *pos = *posref;
4924         *posref = NULL;
4925         pos = strchr(pos + 1, ' ');
4926         if (!pos || !isdigit(pos[1]))
4927                 return FALSE;
4928         *number = atoi(pos + 1);
4929         if (*number < min || *number > max)
4930                 return FALSE;
4932         *posref = pos;
4933         return TRUE;
4936 static struct blame_commit *
4937 parse_blame_commit(struct view *view, const char *text, int *blamed)
4939         struct blame_commit *commit;
4940         struct blame *blame;
4941         const char *pos = text + SIZEOF_REV - 2;
4942         size_t orig_lineno = 0;
4943         size_t lineno;
4944         size_t group;
4946         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4947                 return NULL;
4949         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4950             !parse_number(&pos, &lineno, 1, view->lines) ||
4951             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4952                 return NULL;
4954         commit = get_blame_commit(view, text);
4955         if (!commit)
4956                 return NULL;
4958         *blamed += group;
4959         while (group--) {
4960                 struct line *line = &view->line[lineno + group - 1];
4962                 blame = line->data;
4963                 blame->commit = commit;
4964                 blame->lineno = orig_lineno + group - 1;
4965                 line->dirty = 1;
4966         }
4968         return commit;
4971 static bool
4972 blame_read_file(struct view *view, const char *line, bool *read_file)
4974         if (!line) {
4975                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4976                 struct io io = {};
4978                 if (view->lines == 0 && !view->parent)
4979                         die("No blame exist for %s", view->vid);
4981                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4982                         report("Failed to load blame data");
4983                         return TRUE;
4984                 }
4986                 io_done(view->pipe);
4987                 view->io = io;
4988                 *read_file = FALSE;
4989                 return FALSE;
4991         } else {
4992                 size_t linelen = strlen(line);
4993                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4995                 if (!blame)
4996                         return FALSE;
4998                 blame->commit = NULL;
4999                 strncpy(blame->text, line, linelen);
5000                 blame->text[linelen] = 0;
5001                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5002         }
5005 static bool
5006 match_blame_header(const char *name, char **line)
5008         size_t namelen = strlen(name);
5009         bool matched = !strncmp(name, *line, namelen);
5011         if (matched)
5012                 *line += namelen;
5014         return matched;
5017 static bool
5018 blame_read(struct view *view, char *line)
5020         static struct blame_commit *commit = NULL;
5021         static int blamed = 0;
5022         static bool read_file = TRUE;
5024         if (read_file)
5025                 return blame_read_file(view, line, &read_file);
5027         if (!line) {
5028                 /* Reset all! */
5029                 commit = NULL;
5030                 blamed = 0;
5031                 read_file = TRUE;
5032                 string_format(view->ref, "%s", view->vid);
5033                 if (view_is_displayed(view)) {
5034                         update_view_title(view);
5035                         redraw_view_from(view, 0);
5036                 }
5037                 return TRUE;
5038         }
5040         if (!commit) {
5041                 commit = parse_blame_commit(view, line, &blamed);
5042                 string_format(view->ref, "%s %2d%%", view->vid,
5043                               view->lines ? blamed * 100 / view->lines : 0);
5045         } else if (match_blame_header("author ", &line)) {
5046                 commit->author = get_author(line);
5048         } else if (match_blame_header("author-time ", &line)) {
5049                 parse_timesec(&commit->time, line);
5051         } else if (match_blame_header("author-tz ", &line)) {
5052                 parse_timezone(&commit->time, line);
5054         } else if (match_blame_header("summary ", &line)) {
5055                 string_ncopy(commit->title, line, strlen(line));
5057         } else if (match_blame_header("previous ", &line)) {
5058                 commit->has_previous = TRUE;
5060         } else if (match_blame_header("filename ", &line)) {
5061                 string_ncopy(commit->filename, line, strlen(line));
5062                 commit = NULL;
5063         }
5065         return TRUE;
5068 static bool
5069 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5071         struct blame *blame = line->data;
5072         struct time *time = NULL;
5073         const char *id = NULL, *author = NULL;
5074         char text[SIZEOF_STR];
5076         if (blame->commit && *blame->commit->filename) {
5077                 id = blame->commit->id;
5078                 author = blame->commit->author;
5079                 time = &blame->commit->time;
5080         }
5082         if (opt_date && draw_date(view, time))
5083                 return TRUE;
5085         if (opt_author && draw_author(view, author))
5086                 return TRUE;
5088         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5089                 return TRUE;
5091         if (draw_lineno(view, lineno))
5092                 return TRUE;
5094         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5095         draw_text(view, LINE_DEFAULT, text, TRUE);
5096         return TRUE;
5099 static bool
5100 check_blame_commit(struct blame *blame, bool check_null_id)
5102         if (!blame->commit)
5103                 report("Commit data not loaded yet");
5104         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5105                 report("No commit exist for the selected line");
5106         else
5107                 return TRUE;
5108         return FALSE;
5111 static void
5112 setup_blame_parent_line(struct view *view, struct blame *blame)
5114         const char *diff_tree_argv[] = {
5115                 "git", "diff-tree", "-U0", blame->commit->id,
5116                         "--", blame->commit->filename, NULL
5117         };
5118         struct io io = {};
5119         int parent_lineno = -1;
5120         int blamed_lineno = -1;
5121         char *line;
5123         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5124                 return;
5126         while ((line = io_get(&io, '\n', TRUE))) {
5127                 if (*line == '@') {
5128                         char *pos = strchr(line, '+');
5130                         parent_lineno = atoi(line + 4);
5131                         if (pos)
5132                                 blamed_lineno = atoi(pos + 1);
5134                 } else if (*line == '+' && parent_lineno != -1) {
5135                         if (blame->lineno == blamed_lineno - 1 &&
5136                             !strcmp(blame->text, line + 1)) {
5137                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5138                                 break;
5139                         }
5140                         blamed_lineno++;
5141                 }
5142         }
5144         io_done(&io);
5147 static enum request
5148 blame_request(struct view *view, enum request request, struct line *line)
5150         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5151         struct blame *blame = line->data;
5153         switch (request) {
5154         case REQ_VIEW_BLAME:
5155                 if (check_blame_commit(blame, TRUE)) {
5156                         string_copy(opt_ref, blame->commit->id);
5157                         string_copy(opt_file, blame->commit->filename);
5158                         if (blame->lineno)
5159                                 view->lineno = blame->lineno;
5160                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5161                 }
5162                 break;
5164         case REQ_PARENT:
5165                 if (check_blame_commit(blame, TRUE) &&
5166                     select_commit_parent(blame->commit->id, opt_ref,
5167                                          blame->commit->filename)) {
5168                         string_copy(opt_file, blame->commit->filename);
5169                         setup_blame_parent_line(view, blame);
5170                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5171                 }
5172                 break;
5174         case REQ_ENTER:
5175                 if (!check_blame_commit(blame, FALSE))
5176                         break;
5178                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5179                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5180                         break;
5182                 if (!strcmp(blame->commit->id, NULL_ID)) {
5183                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5184                         const char *diff_index_argv[] = {
5185                                 "git", "diff-index", "--root", "--patch-with-stat",
5186                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5187                         };
5189                         if (!blame->commit->has_previous) {
5190                                 diff_index_argv[1] = "diff";
5191                                 diff_index_argv[2] = "--no-color";
5192                                 diff_index_argv[6] = "--";
5193                                 diff_index_argv[7] = "/dev/null";
5194                         }
5196                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5197                                 report("Failed to allocate diff command");
5198                                 break;
5199                         }
5200                         flags |= OPEN_PREPARED;
5201                 }
5203                 open_view(view, REQ_VIEW_DIFF, flags);
5204                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5205                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5206                 break;
5208         default:
5209                 return request;
5210         }
5212         return REQ_NONE;
5215 static bool
5216 blame_grep(struct view *view, struct line *line)
5218         struct blame *blame = line->data;
5219         struct blame_commit *commit = blame->commit;
5220         const char *text[] = {
5221                 blame->text,
5222                 commit ? commit->title : "",
5223                 commit ? commit->id : "",
5224                 commit && opt_author ? commit->author : "",
5225                 commit ? mkdate(&commit->time, opt_date) : "",
5226                 NULL
5227         };
5229         return grep_text(view, text);
5232 static void
5233 blame_select(struct view *view, struct line *line)
5235         struct blame *blame = line->data;
5236         struct blame_commit *commit = blame->commit;
5238         if (!commit)
5239                 return;
5241         if (!strcmp(commit->id, NULL_ID))
5242                 string_ncopy(ref_commit, "HEAD", 4);
5243         else
5244                 string_copy_rev(ref_commit, commit->id);
5247 static struct view_ops blame_ops = {
5248         "line",
5249         NULL,
5250         blame_open,
5251         blame_read,
5252         blame_draw,
5253         blame_request,
5254         blame_grep,
5255         blame_select,
5256 };
5258 /*
5259  * Branch backend
5260  */
5262 struct branch {
5263         const char *author;             /* Author of the last commit. */
5264         struct time time;               /* Date of the last activity. */
5265         const struct ref *ref;          /* Name and commit ID information. */
5266 };
5268 static const struct ref branch_all;
5270 static const enum sort_field branch_sort_fields[] = {
5271         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5272 };
5273 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5275 static int
5276 branch_compare(const void *l1, const void *l2)
5278         const struct branch *branch1 = ((const struct line *) l1)->data;
5279         const struct branch *branch2 = ((const struct line *) l2)->data;
5281         switch (get_sort_field(branch_sort_state)) {
5282         case ORDERBY_DATE:
5283                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5285         case ORDERBY_AUTHOR:
5286                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5288         case ORDERBY_NAME:
5289         default:
5290                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5291         }
5294 static bool
5295 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5297         struct branch *branch = line->data;
5298         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5300         if (opt_date && draw_date(view, &branch->time))
5301                 return TRUE;
5303         if (opt_author && draw_author(view, branch->author))
5304                 return TRUE;
5306         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5307         return TRUE;
5310 static enum request
5311 branch_request(struct view *view, enum request request, struct line *line)
5313         struct branch *branch = line->data;
5315         switch (request) {
5316         case REQ_REFRESH:
5317                 load_refs();
5318                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5319                 return REQ_NONE;
5321         case REQ_TOGGLE_SORT_FIELD:
5322         case REQ_TOGGLE_SORT_ORDER:
5323                 sort_view(view, request, &branch_sort_state, branch_compare);
5324                 return REQ_NONE;
5326         case REQ_ENTER:
5327                 if (branch->ref == &branch_all) {
5328                         const char *all_branches_argv[] = {
5329                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5330                                       "--topo-order", "--all", NULL
5331                         };
5332                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5334                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5335                                 report("Failed to load view of all branches");
5336                                 return REQ_NONE;
5337                         }
5338                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5339                 } else {
5340                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5341                 }
5342                 return REQ_NONE;
5344         default:
5345                 return request;
5346         }
5349 static bool
5350 branch_read(struct view *view, char *line)
5352         static char id[SIZEOF_REV];
5353         struct branch *reference;
5354         size_t i;
5356         if (!line)
5357                 return TRUE;
5359         switch (get_line_type(line)) {
5360         case LINE_COMMIT:
5361                 string_copy_rev(id, line + STRING_SIZE("commit "));
5362                 return TRUE;
5364         case LINE_AUTHOR:
5365                 for (i = 0, reference = NULL; i < view->lines; i++) {
5366                         struct branch *branch = view->line[i].data;
5368                         if (strcmp(branch->ref->id, id))
5369                                 continue;
5371                         view->line[i].dirty = TRUE;
5372                         if (reference) {
5373                                 branch->author = reference->author;
5374                                 branch->time = reference->time;
5375                                 continue;
5376                         }
5378                         parse_author_line(line + STRING_SIZE("author "),
5379                                           &branch->author, &branch->time);
5380                         reference = branch;
5381                 }
5382                 return TRUE;
5384         default:
5385                 return TRUE;
5386         }
5390 static bool
5391 branch_open_visitor(void *data, const struct ref *ref)
5393         struct view *view = data;
5394         struct branch *branch;
5396         if (ref->tag || ref->ltag || ref->remote)
5397                 return TRUE;
5399         branch = calloc(1, sizeof(*branch));
5400         if (!branch)
5401                 return FALSE;
5403         branch->ref = ref;
5404         return !!add_line_data(view, branch, LINE_DEFAULT);
5407 static bool
5408 branch_open(struct view *view)
5410         const char *branch_log[] = {
5411                 "git", "log", "--no-color", "--pretty=raw",
5412                         "--simplify-by-decoration", "--all", NULL
5413         };
5415         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5416                 report("Failed to load branch data");
5417                 return TRUE;
5418         }
5420         setup_update(view, view->id);
5421         branch_open_visitor(view, &branch_all);
5422         foreach_ref(branch_open_visitor, view);
5423         view->p_restore = TRUE;
5425         return TRUE;
5428 static bool
5429 branch_grep(struct view *view, struct line *line)
5431         struct branch *branch = line->data;
5432         const char *text[] = {
5433                 branch->ref->name,
5434                 branch->author,
5435                 NULL
5436         };
5438         return grep_text(view, text);
5441 static void
5442 branch_select(struct view *view, struct line *line)
5444         struct branch *branch = line->data;
5446         string_copy_rev(view->ref, branch->ref->id);
5447         string_copy_rev(ref_commit, branch->ref->id);
5448         string_copy_rev(ref_head, branch->ref->id);
5449         string_copy_rev(ref_branch, branch->ref->name);
5452 static struct view_ops branch_ops = {
5453         "branch",
5454         NULL,
5455         branch_open,
5456         branch_read,
5457         branch_draw,
5458         branch_request,
5459         branch_grep,
5460         branch_select,
5461 };
5463 /*
5464  * Status backend
5465  */
5467 struct status {
5468         char status;
5469         struct {
5470                 mode_t mode;
5471                 char rev[SIZEOF_REV];
5472                 char name[SIZEOF_STR];
5473         } old;
5474         struct {
5475                 mode_t mode;
5476                 char rev[SIZEOF_REV];
5477                 char name[SIZEOF_STR];
5478         } new;
5479 };
5481 static char status_onbranch[SIZEOF_STR];
5482 static struct status stage_status;
5483 static enum line_type stage_line_type;
5484 static size_t stage_chunks;
5485 static int *stage_chunk;
5487 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5489 /* This should work even for the "On branch" line. */
5490 static inline bool
5491 status_has_none(struct view *view, struct line *line)
5493         return line < view->line + view->lines && !line[1].data;
5496 /* Get fields from the diff line:
5497  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5498  */
5499 static inline bool
5500 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5502         const char *old_mode = buf +  1;
5503         const char *new_mode = buf +  8;
5504         const char *old_rev  = buf + 15;
5505         const char *new_rev  = buf + 56;
5506         const char *status   = buf + 97;
5508         if (bufsize < 98 ||
5509             old_mode[-1] != ':' ||
5510             new_mode[-1] != ' ' ||
5511             old_rev[-1]  != ' ' ||
5512             new_rev[-1]  != ' ' ||
5513             status[-1]   != ' ')
5514                 return FALSE;
5516         file->status = *status;
5518         string_copy_rev(file->old.rev, old_rev);
5519         string_copy_rev(file->new.rev, new_rev);
5521         file->old.mode = strtoul(old_mode, NULL, 8);
5522         file->new.mode = strtoul(new_mode, NULL, 8);
5524         file->old.name[0] = file->new.name[0] = 0;
5526         return TRUE;
5529 static bool
5530 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5532         struct status *unmerged = NULL;
5533         char *buf;
5534         struct io io = {};
5536         if (!io_run(&io, argv, opt_cdup, IO_RD))
5537                 return FALSE;
5539         add_line_data(view, NULL, type);
5541         while ((buf = io_get(&io, 0, TRUE))) {
5542                 struct status *file = unmerged;
5544                 if (!file) {
5545                         file = calloc(1, sizeof(*file));
5546                         if (!file || !add_line_data(view, file, type))
5547                                 goto error_out;
5548                 }
5550                 /* Parse diff info part. */
5551                 if (status) {
5552                         file->status = status;
5553                         if (status == 'A')
5554                                 string_copy(file->old.rev, NULL_ID);
5556                 } else if (!file->status || file == unmerged) {
5557                         if (!status_get_diff(file, buf, strlen(buf)))
5558                                 goto error_out;
5560                         buf = io_get(&io, 0, TRUE);
5561                         if (!buf)
5562                                 break;
5564                         /* Collapse all modified entries that follow an
5565                          * associated unmerged entry. */
5566                         if (unmerged == file) {
5567                                 unmerged->status = 'U';
5568                                 unmerged = NULL;
5569                         } else if (file->status == 'U') {
5570                                 unmerged = file;
5571                         }
5572                 }
5574                 /* Grab the old name for rename/copy. */
5575                 if (!*file->old.name &&
5576                     (file->status == 'R' || file->status == 'C')) {
5577                         string_ncopy(file->old.name, buf, strlen(buf));
5579                         buf = io_get(&io, 0, TRUE);
5580                         if (!buf)
5581                                 break;
5582                 }
5584                 /* git-ls-files just delivers a NUL separated list of
5585                  * file names similar to the second half of the
5586                  * git-diff-* output. */
5587                 string_ncopy(file->new.name, buf, strlen(buf));
5588                 if (!*file->old.name)
5589                         string_copy(file->old.name, file->new.name);
5590                 file = NULL;
5591         }
5593         if (io_error(&io)) {
5594 error_out:
5595                 io_done(&io);
5596                 return FALSE;
5597         }
5599         if (!view->line[view->lines - 1].data)
5600                 add_line_data(view, NULL, LINE_STAT_NONE);
5602         io_done(&io);
5603         return TRUE;
5606 /* Don't show unmerged entries in the staged section. */
5607 static const char *status_diff_index_argv[] = {
5608         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5609                              "--cached", "-M", "HEAD", NULL
5610 };
5612 static const char *status_diff_files_argv[] = {
5613         "git", "diff-files", "-z", NULL
5614 };
5616 static const char *status_list_other_argv[] = {
5617         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5618 };
5620 static const char *status_list_no_head_argv[] = {
5621         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5622 };
5624 static const char *update_index_argv[] = {
5625         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5626 };
5628 /* Restore the previous line number to stay in the context or select a
5629  * line with something that can be updated. */
5630 static void
5631 status_restore(struct view *view)
5633         if (view->p_lineno >= view->lines)
5634                 view->p_lineno = view->lines - 1;
5635         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5636                 view->p_lineno++;
5637         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5638                 view->p_lineno--;
5640         /* If the above fails, always skip the "On branch" line. */
5641         if (view->p_lineno < view->lines)
5642                 view->lineno = view->p_lineno;
5643         else
5644                 view->lineno = 1;
5646         if (view->lineno < view->offset)
5647                 view->offset = view->lineno;
5648         else if (view->offset + view->height <= view->lineno)
5649                 view->offset = view->lineno - view->height + 1;
5651         view->p_restore = FALSE;
5654 static void
5655 status_update_onbranch(void)
5657         static const char *paths[][2] = {
5658                 { "rebase-apply/rebasing",      "Rebasing" },
5659                 { "rebase-apply/applying",      "Applying mailbox" },
5660                 { "rebase-apply/",              "Rebasing mailbox" },
5661                 { "rebase-merge/interactive",   "Interactive rebase" },
5662                 { "rebase-merge/",              "Rebase merge" },
5663                 { "MERGE_HEAD",                 "Merging" },
5664                 { "BISECT_LOG",                 "Bisecting" },
5665                 { "HEAD",                       "On branch" },
5666         };
5667         char buf[SIZEOF_STR];
5668         struct stat stat;
5669         int i;
5671         if (is_initial_commit()) {
5672                 string_copy(status_onbranch, "Initial commit");
5673                 return;
5674         }
5676         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5677                 char *head = opt_head;
5679                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5680                     lstat(buf, &stat) < 0)
5681                         continue;
5683                 if (!*opt_head) {
5684                         struct io io = {};
5686                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5687                             io_read_buf(&io, buf, sizeof(buf))) {
5688                                 head = buf;
5689                                 if (!prefixcmp(head, "refs/heads/"))
5690                                         head += STRING_SIZE("refs/heads/");
5691                         }
5692                 }
5694                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5695                         string_copy(status_onbranch, opt_head);
5696                 return;
5697         }
5699         string_copy(status_onbranch, "Not currently on any branch");
5702 /* First parse staged info using git-diff-index(1), then parse unstaged
5703  * info using git-diff-files(1), and finally untracked files using
5704  * git-ls-files(1). */
5705 static bool
5706 status_open(struct view *view)
5708         reset_view(view);
5710         add_line_data(view, NULL, LINE_STAT_HEAD);
5711         status_update_onbranch();
5713         io_run_bg(update_index_argv);
5715         if (is_initial_commit()) {
5716                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5717                         return FALSE;
5718         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5719                 return FALSE;
5720         }
5722         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5723             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5724                 return FALSE;
5726         /* Restore the exact position or use the specialized restore
5727          * mode? */
5728         if (!view->p_restore)
5729                 status_restore(view);
5730         return TRUE;
5733 static bool
5734 status_draw(struct view *view, struct line *line, unsigned int lineno)
5736         struct status *status = line->data;
5737         enum line_type type;
5738         const char *text;
5740         if (!status) {
5741                 switch (line->type) {
5742                 case LINE_STAT_STAGED:
5743                         type = LINE_STAT_SECTION;
5744                         text = "Changes to be committed:";
5745                         break;
5747                 case LINE_STAT_UNSTAGED:
5748                         type = LINE_STAT_SECTION;
5749                         text = "Changed but not updated:";
5750                         break;
5752                 case LINE_STAT_UNTRACKED:
5753                         type = LINE_STAT_SECTION;
5754                         text = "Untracked files:";
5755                         break;
5757                 case LINE_STAT_NONE:
5758                         type = LINE_DEFAULT;
5759                         text = "  (no files)";
5760                         break;
5762                 case LINE_STAT_HEAD:
5763                         type = LINE_STAT_HEAD;
5764                         text = status_onbranch;
5765                         break;
5767                 default:
5768                         return FALSE;
5769                 }
5770         } else {
5771                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5773                 buf[0] = status->status;
5774                 if (draw_text(view, line->type, buf, TRUE))
5775                         return TRUE;
5776                 type = LINE_DEFAULT;
5777                 text = status->new.name;
5778         }
5780         draw_text(view, type, text, TRUE);
5781         return TRUE;
5784 static enum request
5785 status_load_error(struct view *view, struct view *stage, const char *path)
5787         if (displayed_views() == 2 || display[current_view] != view)
5788                 maximize_view(view);
5789         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5790         return REQ_NONE;
5793 static enum request
5794 status_enter(struct view *view, struct line *line)
5796         struct status *status = line->data;
5797         const char *oldpath = status ? status->old.name : NULL;
5798         /* Diffs for unmerged entries are empty when passing the new
5799          * path, so leave it empty. */
5800         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5801         const char *info;
5802         enum open_flags split;
5803         struct view *stage = VIEW(REQ_VIEW_STAGE);
5805         if (line->type == LINE_STAT_NONE ||
5806             (!status && line[1].type == LINE_STAT_NONE)) {
5807                 report("No file to diff");
5808                 return REQ_NONE;
5809         }
5811         switch (line->type) {
5812         case LINE_STAT_STAGED:
5813                 if (is_initial_commit()) {
5814                         const char *no_head_diff_argv[] = {
5815                                 "git", "diff", "--no-color", "--patch-with-stat",
5816                                         "--", "/dev/null", newpath, NULL
5817                         };
5819                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5820                                 return status_load_error(view, stage, newpath);
5821                 } else {
5822                         const char *index_show_argv[] = {
5823                                 "git", "diff-index", "--root", "--patch-with-stat",
5824                                         "-C", "-M", "--cached", "HEAD", "--",
5825                                         oldpath, newpath, NULL
5826                         };
5828                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5829                                 return status_load_error(view, stage, newpath);
5830                 }
5832                 if (status)
5833                         info = "Staged changes to %s";
5834                 else
5835                         info = "Staged changes";
5836                 break;
5838         case LINE_STAT_UNSTAGED:
5839         {
5840                 const char *files_show_argv[] = {
5841                         "git", "diff-files", "--root", "--patch-with-stat",
5842                                 "-C", "-M", "--", oldpath, newpath, NULL
5843                 };
5845                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5846                         return status_load_error(view, stage, newpath);
5847                 if (status)
5848                         info = "Unstaged changes to %s";
5849                 else
5850                         info = "Unstaged changes";
5851                 break;
5852         }
5853         case LINE_STAT_UNTRACKED:
5854                 if (!newpath) {
5855                         report("No file to show");
5856                         return REQ_NONE;
5857                 }
5859                 if (!suffixcmp(status->new.name, -1, "/")) {
5860                         report("Cannot display a directory");
5861                         return REQ_NONE;
5862                 }
5864                 if (!prepare_update_file(stage, newpath))
5865                         return status_load_error(view, stage, newpath);
5866                 info = "Untracked file %s";
5867                 break;
5869         case LINE_STAT_HEAD:
5870                 return REQ_NONE;
5872         default:
5873                 die("line type %d not handled in switch", line->type);
5874         }
5876         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5877         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5878         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5879                 if (status) {
5880                         stage_status = *status;
5881                 } else {
5882                         memset(&stage_status, 0, sizeof(stage_status));
5883                 }
5885                 stage_line_type = line->type;
5886                 stage_chunks = 0;
5887                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5888         }
5890         return REQ_NONE;
5893 static bool
5894 status_exists(struct status *status, enum line_type type)
5896         struct view *view = VIEW(REQ_VIEW_STATUS);
5897         unsigned long lineno;
5899         for (lineno = 0; lineno < view->lines; lineno++) {
5900                 struct line *line = &view->line[lineno];
5901                 struct status *pos = line->data;
5903                 if (line->type != type)
5904                         continue;
5905                 if (!pos && (!status || !status->status) && line[1].data) {
5906                         select_view_line(view, lineno);
5907                         return TRUE;
5908                 }
5909                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5910                         select_view_line(view, lineno);
5911                         return TRUE;
5912                 }
5913         }
5915         return FALSE;
5919 static bool
5920 status_update_prepare(struct io *io, enum line_type type)
5922         const char *staged_argv[] = {
5923                 "git", "update-index", "-z", "--index-info", NULL
5924         };
5925         const char *others_argv[] = {
5926                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5927         };
5929         switch (type) {
5930         case LINE_STAT_STAGED:
5931                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5933         case LINE_STAT_UNSTAGED:
5934         case LINE_STAT_UNTRACKED:
5935                 return io_run(io, others_argv, opt_cdup, IO_WR);
5937         default:
5938                 die("line type %d not handled in switch", type);
5939                 return FALSE;
5940         }
5943 static bool
5944 status_update_write(struct io *io, struct status *status, enum line_type type)
5946         char buf[SIZEOF_STR];
5947         size_t bufsize = 0;
5949         switch (type) {
5950         case LINE_STAT_STAGED:
5951                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5952                                         status->old.mode,
5953                                         status->old.rev,
5954                                         status->old.name, 0))
5955                         return FALSE;
5956                 break;
5958         case LINE_STAT_UNSTAGED:
5959         case LINE_STAT_UNTRACKED:
5960                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5961                         return FALSE;
5962                 break;
5964         default:
5965                 die("line type %d not handled in switch", type);
5966         }
5968         return io_write(io, buf, bufsize);
5971 static bool
5972 status_update_file(struct status *status, enum line_type type)
5974         struct io io = {};
5975         bool result;
5977         if (!status_update_prepare(&io, type))
5978                 return FALSE;
5980         result = status_update_write(&io, status, type);
5981         return io_done(&io) && result;
5984 static bool
5985 status_update_files(struct view *view, struct line *line)
5987         char buf[sizeof(view->ref)];
5988         struct io io = {};
5989         bool result = TRUE;
5990         struct line *pos = view->line + view->lines;
5991         int files = 0;
5992         int file, done;
5993         int cursor_y = -1, cursor_x = -1;
5995         if (!status_update_prepare(&io, line->type))
5996                 return FALSE;
5998         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5999                 files++;
6001         string_copy(buf, view->ref);
6002         getsyx(cursor_y, cursor_x);
6003         for (file = 0, done = 5; result && file < files; line++, file++) {
6004                 int almost_done = file * 100 / files;
6006                 if (almost_done > done) {
6007                         done = almost_done;
6008                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6009                                       file, files, done);
6010                         update_view_title(view);
6011                         setsyx(cursor_y, cursor_x);
6012                         doupdate();
6013                 }
6014                 result = status_update_write(&io, line->data, line->type);
6015         }
6016         string_copy(view->ref, buf);
6018         return io_done(&io) && result;
6021 static bool
6022 status_update(struct view *view)
6024         struct line *line = &view->line[view->lineno];
6026         assert(view->lines);
6028         if (!line->data) {
6029                 /* This should work even for the "On branch" line. */
6030                 if (line < view->line + view->lines && !line[1].data) {
6031                         report("Nothing to update");
6032                         return FALSE;
6033                 }
6035                 if (!status_update_files(view, line + 1)) {
6036                         report("Failed to update file status");
6037                         return FALSE;
6038                 }
6040         } else if (!status_update_file(line->data, line->type)) {
6041                 report("Failed to update file status");
6042                 return FALSE;
6043         }
6045         return TRUE;
6048 static bool
6049 status_revert(struct status *status, enum line_type type, bool has_none)
6051         if (!status || type != LINE_STAT_UNSTAGED) {
6052                 if (type == LINE_STAT_STAGED) {
6053                         report("Cannot revert changes to staged files");
6054                 } else if (type == LINE_STAT_UNTRACKED) {
6055                         report("Cannot revert changes to untracked files");
6056                 } else if (has_none) {
6057                         report("Nothing to revert");
6058                 } else {
6059                         report("Cannot revert changes to multiple files");
6060                 }
6062         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6063                 char mode[10] = "100644";
6064                 const char *reset_argv[] = {
6065                         "git", "update-index", "--cacheinfo", mode,
6066                                 status->old.rev, status->old.name, NULL
6067                 };
6068                 const char *checkout_argv[] = {
6069                         "git", "checkout", "--", status->old.name, NULL
6070                 };
6072                 if (status->status == 'U') {
6073                         string_format(mode, "%5o", status->old.mode);
6075                         if (status->old.mode == 0 && status->new.mode == 0) {
6076                                 reset_argv[2] = "--force-remove";
6077                                 reset_argv[3] = status->old.name;
6078                                 reset_argv[4] = NULL;
6079                         }
6081                         if (!io_run_fg(reset_argv, opt_cdup))
6082                                 return FALSE;
6083                         if (status->old.mode == 0 && status->new.mode == 0)
6084                                 return TRUE;
6085                 }
6087                 return io_run_fg(checkout_argv, opt_cdup);
6088         }
6090         return FALSE;
6093 static enum request
6094 status_request(struct view *view, enum request request, struct line *line)
6096         struct status *status = line->data;
6098         switch (request) {
6099         case REQ_STATUS_UPDATE:
6100                 if (!status_update(view))
6101                         return REQ_NONE;
6102                 break;
6104         case REQ_STATUS_REVERT:
6105                 if (!status_revert(status, line->type, status_has_none(view, line)))
6106                         return REQ_NONE;
6107                 break;
6109         case REQ_STATUS_MERGE:
6110                 if (!status || status->status != 'U') {
6111                         report("Merging only possible for files with unmerged status ('U').");
6112                         return REQ_NONE;
6113                 }
6114                 open_mergetool(status->new.name);
6115                 break;
6117         case REQ_EDIT:
6118                 if (!status)
6119                         return request;
6120                 if (status->status == 'D') {
6121                         report("File has been deleted.");
6122                         return REQ_NONE;
6123                 }
6125                 open_editor(status->new.name);
6126                 break;
6128         case REQ_VIEW_BLAME:
6129                 if (status)
6130                         opt_ref[0] = 0;
6131                 return request;
6133         case REQ_ENTER:
6134                 /* After returning the status view has been split to
6135                  * show the stage view. No further reloading is
6136                  * necessary. */
6137                 return status_enter(view, line);
6139         case REQ_REFRESH:
6140                 /* Simply reload the view. */
6141                 break;
6143         default:
6144                 return request;
6145         }
6147         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6149         return REQ_NONE;
6152 static void
6153 status_select(struct view *view, struct line *line)
6155         struct status *status = line->data;
6156         char file[SIZEOF_STR] = "all files";
6157         const char *text;
6158         const char *key;
6160         if (status && !string_format(file, "'%s'", status->new.name))
6161                 return;
6163         if (!status && line[1].type == LINE_STAT_NONE)
6164                 line++;
6166         switch (line->type) {
6167         case LINE_STAT_STAGED:
6168                 text = "Press %s to unstage %s for commit";
6169                 break;
6171         case LINE_STAT_UNSTAGED:
6172                 text = "Press %s to stage %s for commit";
6173                 break;
6175         case LINE_STAT_UNTRACKED:
6176                 text = "Press %s to stage %s for addition";
6177                 break;
6179         case LINE_STAT_HEAD:
6180         case LINE_STAT_NONE:
6181                 text = "Nothing to update";
6182                 break;
6184         default:
6185                 die("line type %d not handled in switch", line->type);
6186         }
6188         if (status && status->status == 'U') {
6189                 text = "Press %s to resolve conflict in %s";
6190                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6192         } else {
6193                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6194         }
6196         string_format(view->ref, text, key, file);
6197         if (status)
6198                 string_copy(opt_file, status->new.name);
6201 static bool
6202 status_grep(struct view *view, struct line *line)
6204         struct status *status = line->data;
6206         if (status) {
6207                 const char buf[2] = { status->status, 0 };
6208                 const char *text[] = { status->new.name, buf, NULL };
6210                 return grep_text(view, text);
6211         }
6213         return FALSE;
6216 static struct view_ops status_ops = {
6217         "file",
6218         NULL,
6219         status_open,
6220         NULL,
6221         status_draw,
6222         status_request,
6223         status_grep,
6224         status_select,
6225 };
6228 static bool
6229 stage_diff_write(struct io *io, struct line *line, struct line *end)
6231         while (line < end) {
6232                 if (!io_write(io, line->data, strlen(line->data)) ||
6233                     !io_write(io, "\n", 1))
6234                         return FALSE;
6235                 line++;
6236                 if (line->type == LINE_DIFF_CHUNK ||
6237                     line->type == LINE_DIFF_HEADER)
6238                         break;
6239         }
6241         return TRUE;
6244 static struct line *
6245 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6247         for (; view->line < line; line--)
6248                 if (line->type == type)
6249                         return line;
6251         return NULL;
6254 static bool
6255 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6257         const char *apply_argv[SIZEOF_ARG] = {
6258                 "git", "apply", "--whitespace=nowarn", NULL
6259         };
6260         struct line *diff_hdr;
6261         struct io io = {};
6262         int argc = 3;
6264         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6265         if (!diff_hdr)
6266                 return FALSE;
6268         if (!revert)
6269                 apply_argv[argc++] = "--cached";
6270         if (revert || stage_line_type == LINE_STAT_STAGED)
6271                 apply_argv[argc++] = "-R";
6272         apply_argv[argc++] = "-";
6273         apply_argv[argc++] = NULL;
6274         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6275                 return FALSE;
6277         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6278             !stage_diff_write(&io, chunk, view->line + view->lines))
6279                 chunk = NULL;
6281         io_done(&io);
6282         io_run_bg(update_index_argv);
6284         return chunk ? TRUE : FALSE;
6287 static bool
6288 stage_update(struct view *view, struct line *line)
6290         struct line *chunk = NULL;
6292         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6293                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6295         if (chunk) {
6296                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6297                         report("Failed to apply chunk");
6298                         return FALSE;
6299                 }
6301         } else if (!stage_status.status) {
6302                 view = VIEW(REQ_VIEW_STATUS);
6304                 for (line = view->line; line < view->line + view->lines; line++)
6305                         if (line->type == stage_line_type)
6306                                 break;
6308                 if (!status_update_files(view, line + 1)) {
6309                         report("Failed to update files");
6310                         return FALSE;
6311                 }
6313         } else if (!status_update_file(&stage_status, stage_line_type)) {
6314                 report("Failed to update file");
6315                 return FALSE;
6316         }
6318         return TRUE;
6321 static bool
6322 stage_revert(struct view *view, struct line *line)
6324         struct line *chunk = NULL;
6326         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6327                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6329         if (chunk) {
6330                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6331                         return FALSE;
6333                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6334                         report("Failed to revert chunk");
6335                         return FALSE;
6336                 }
6337                 return TRUE;
6339         } else {
6340                 return status_revert(stage_status.status ? &stage_status : NULL,
6341                                      stage_line_type, FALSE);
6342         }
6346 static void
6347 stage_next(struct view *view, struct line *line)
6349         int i;
6351         if (!stage_chunks) {
6352                 for (line = view->line; line < view->line + view->lines; line++) {
6353                         if (line->type != LINE_DIFF_CHUNK)
6354                                 continue;
6356                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6357                                 report("Allocation failure");
6358                                 return;
6359                         }
6361                         stage_chunk[stage_chunks++] = line - view->line;
6362                 }
6363         }
6365         for (i = 0; i < stage_chunks; i++) {
6366                 if (stage_chunk[i] > view->lineno) {
6367                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6368                         report("Chunk %d of %d", i + 1, stage_chunks);
6369                         return;
6370                 }
6371         }
6373         report("No next chunk found");
6376 static enum request
6377 stage_request(struct view *view, enum request request, struct line *line)
6379         switch (request) {
6380         case REQ_STATUS_UPDATE:
6381                 if (!stage_update(view, line))
6382                         return REQ_NONE;
6383                 break;
6385         case REQ_STATUS_REVERT:
6386                 if (!stage_revert(view, line))
6387                         return REQ_NONE;
6388                 break;
6390         case REQ_STAGE_NEXT:
6391                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6392                         report("File is untracked; press %s to add",
6393                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6394                         return REQ_NONE;
6395                 }
6396                 stage_next(view, line);
6397                 return REQ_NONE;
6399         case REQ_EDIT:
6400                 if (!stage_status.new.name[0])
6401                         return request;
6402                 if (stage_status.status == 'D') {
6403                         report("File has been deleted.");
6404                         return REQ_NONE;
6405                 }
6407                 open_editor(stage_status.new.name);
6408                 break;
6410         case REQ_REFRESH:
6411                 /* Reload everything ... */
6412                 break;
6414         case REQ_VIEW_BLAME:
6415                 if (stage_status.new.name[0]) {
6416                         string_copy(opt_file, stage_status.new.name);
6417                         opt_ref[0] = 0;
6418                 }
6419                 return request;
6421         case REQ_ENTER:
6422                 return pager_request(view, request, line);
6424         default:
6425                 return request;
6426         }
6428         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6429         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6431         /* Check whether the staged entry still exists, and close the
6432          * stage view if it doesn't. */
6433         if (!status_exists(&stage_status, stage_line_type)) {
6434                 status_restore(VIEW(REQ_VIEW_STATUS));
6435                 return REQ_VIEW_CLOSE;
6436         }
6438         if (stage_line_type == LINE_STAT_UNTRACKED) {
6439                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6440                         report("Cannot display a directory");
6441                         return REQ_NONE;
6442                 }
6444                 if (!prepare_update_file(view, stage_status.new.name)) {
6445                         report("Failed to open file: %s", strerror(errno));
6446                         return REQ_NONE;
6447                 }
6448         }
6449         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6451         return REQ_NONE;
6454 static struct view_ops stage_ops = {
6455         "line",
6456         NULL,
6457         NULL,
6458         pager_read,
6459         pager_draw,
6460         stage_request,
6461         pager_grep,
6462         pager_select,
6463 };
6466 /*
6467  * Revision graph
6468  */
6470 struct commit {
6471         char id[SIZEOF_REV];            /* SHA1 ID. */
6472         char title[128];                /* First line of the commit message. */
6473         const char *author;             /* Author of the commit. */
6474         struct time time;               /* Date from the author ident. */
6475         struct ref_list *refs;          /* Repository references. */
6476         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6477         size_t graph_size;              /* The width of the graph array. */
6478         bool has_parents;               /* Rewritten --parents seen. */
6479 };
6481 /* Size of rev graph with no  "padding" columns */
6482 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6484 struct rev_graph {
6485         struct rev_graph *prev, *next, *parents;
6486         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6487         size_t size;
6488         struct commit *commit;
6489         size_t pos;
6490         unsigned int boundary:1;
6491 };
6493 /* Parents of the commit being visualized. */
6494 static struct rev_graph graph_parents[4];
6496 /* The current stack of revisions on the graph. */
6497 static struct rev_graph graph_stacks[4] = {
6498         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6499         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6500         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6501         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6502 };
6504 static inline bool
6505 graph_parent_is_merge(struct rev_graph *graph)
6507         return graph->parents->size > 1;
6510 static inline void
6511 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6513         struct commit *commit = graph->commit;
6515         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6516                 commit->graph[commit->graph_size++] = symbol;
6519 static void
6520 clear_rev_graph(struct rev_graph *graph)
6522         graph->boundary = 0;
6523         graph->size = graph->pos = 0;
6524         graph->commit = NULL;
6525         memset(graph->parents, 0, sizeof(*graph->parents));
6528 static void
6529 done_rev_graph(struct rev_graph *graph)
6531         if (graph_parent_is_merge(graph) &&
6532             graph->pos < graph->size - 1 &&
6533             graph->next->size == graph->size + graph->parents->size - 1) {
6534                 size_t i = graph->pos + graph->parents->size - 1;
6536                 graph->commit->graph_size = i * 2;
6537                 while (i < graph->next->size - 1) {
6538                         append_to_rev_graph(graph, ' ');
6539                         append_to_rev_graph(graph, '\\');
6540                         i++;
6541                 }
6542         }
6544         clear_rev_graph(graph);
6547 static void
6548 push_rev_graph(struct rev_graph *graph, const char *parent)
6550         int i;
6552         /* "Collapse" duplicate parents lines.
6553          *
6554          * FIXME: This needs to also update update the drawn graph but
6555          * for now it just serves as a method for pruning graph lines. */
6556         for (i = 0; i < graph->size; i++)
6557                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6558                         return;
6560         if (graph->size < SIZEOF_REVITEMS) {
6561                 string_copy_rev(graph->rev[graph->size++], parent);
6562         }
6565 static chtype
6566 get_rev_graph_symbol(struct rev_graph *graph)
6568         chtype symbol;
6570         if (graph->boundary)
6571                 symbol = REVGRAPH_BOUND;
6572         else if (graph->parents->size == 0)
6573                 symbol = REVGRAPH_INIT;
6574         else if (graph_parent_is_merge(graph))
6575                 symbol = REVGRAPH_MERGE;
6576         else if (graph->pos >= graph->size)
6577                 symbol = REVGRAPH_BRANCH;
6578         else
6579                 symbol = REVGRAPH_COMMIT;
6581         return symbol;
6584 static void
6585 draw_rev_graph(struct rev_graph *graph)
6587         struct rev_filler {
6588                 chtype separator, line;
6589         };
6590         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6591         static struct rev_filler fillers[] = {
6592                 { ' ',  '|' },
6593                 { '`',  '.' },
6594                 { '\'', ' ' },
6595                 { '/',  ' ' },
6596         };
6597         chtype symbol = get_rev_graph_symbol(graph);
6598         struct rev_filler *filler;
6599         size_t i;
6601         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6602         filler = &fillers[DEFAULT];
6604         for (i = 0; i < graph->pos; i++) {
6605                 append_to_rev_graph(graph, filler->line);
6606                 if (graph_parent_is_merge(graph->prev) &&
6607                     graph->prev->pos == i)
6608                         filler = &fillers[RSHARP];
6610                 append_to_rev_graph(graph, filler->separator);
6611         }
6613         /* Place the symbol for this revision. */
6614         append_to_rev_graph(graph, symbol);
6616         if (graph->prev->size > graph->size)
6617                 filler = &fillers[RDIAG];
6618         else
6619                 filler = &fillers[DEFAULT];
6621         i++;
6623         for (; i < graph->size; i++) {
6624                 append_to_rev_graph(graph, filler->separator);
6625                 append_to_rev_graph(graph, filler->line);
6626                 if (graph_parent_is_merge(graph->prev) &&
6627                     i < graph->prev->pos + graph->parents->size)
6628                         filler = &fillers[RSHARP];
6629                 if (graph->prev->size > graph->size)
6630                         filler = &fillers[LDIAG];
6631         }
6633         if (graph->prev->size > graph->size) {
6634                 append_to_rev_graph(graph, filler->separator);
6635                 if (filler->line != ' ')
6636                         append_to_rev_graph(graph, filler->line);
6637         }
6640 /* Prepare the next rev graph */
6641 static void
6642 prepare_rev_graph(struct rev_graph *graph)
6644         size_t i;
6646         /* First, traverse all lines of revisions up to the active one. */
6647         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6648                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6649                         break;
6651                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6652         }
6654         /* Interleave the new revision parent(s). */
6655         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6656                 push_rev_graph(graph->next, graph->parents->rev[i]);
6658         /* Lastly, put any remaining revisions. */
6659         for (i = graph->pos + 1; i < graph->size; i++)
6660                 push_rev_graph(graph->next, graph->rev[i]);
6663 static void
6664 update_rev_graph(struct view *view, struct rev_graph *graph)
6666         /* If this is the finalizing update ... */
6667         if (graph->commit)
6668                 prepare_rev_graph(graph);
6670         /* Graph visualization needs a one rev look-ahead,
6671          * so the first update doesn't visualize anything. */
6672         if (!graph->prev->commit)
6673                 return;
6675         if (view->lines > 2)
6676                 view->line[view->lines - 3].dirty = 1;
6677         if (view->lines > 1)
6678                 view->line[view->lines - 2].dirty = 1;
6679         draw_rev_graph(graph->prev);
6680         done_rev_graph(graph->prev->prev);
6684 /*
6685  * Main view backend
6686  */
6688 static const char *main_argv[SIZEOF_ARG] = {
6689         "git", "log", "--no-color", "--pretty=raw", "--parents",
6690                       "--topo-order", "%(head)", NULL
6691 };
6693 static bool
6694 main_draw(struct view *view, struct line *line, unsigned int lineno)
6696         struct commit *commit = line->data;
6698         if (!commit->author)
6699                 return FALSE;
6701         if (opt_date && draw_date(view, &commit->time))
6702                 return TRUE;
6704         if (opt_author && draw_author(view, commit->author))
6705                 return TRUE;
6707         if (opt_rev_graph && commit->graph_size &&
6708             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6709                 return TRUE;
6711         if (opt_show_refs && commit->refs) {
6712                 size_t i;
6714                 for (i = 0; i < commit->refs->size; i++) {
6715                         struct ref *ref = commit->refs->refs[i];
6716                         enum line_type type;
6718                         if (ref->head)
6719                                 type = LINE_MAIN_HEAD;
6720                         else if (ref->ltag)
6721                                 type = LINE_MAIN_LOCAL_TAG;
6722                         else if (ref->tag)
6723                                 type = LINE_MAIN_TAG;
6724                         else if (ref->tracked)
6725                                 type = LINE_MAIN_TRACKED;
6726                         else if (ref->remote)
6727                                 type = LINE_MAIN_REMOTE;
6728                         else
6729                                 type = LINE_MAIN_REF;
6731                         if (draw_text(view, type, "[", TRUE) ||
6732                             draw_text(view, type, ref->name, TRUE) ||
6733                             draw_text(view, type, "]", TRUE))
6734                                 return TRUE;
6736                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6737                                 return TRUE;
6738                 }
6739         }
6741         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6742         return TRUE;
6745 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6746 static bool
6747 main_read(struct view *view, char *line)
6749         static struct rev_graph *graph = graph_stacks;
6750         enum line_type type;
6751         struct commit *commit;
6753         if (!line) {
6754                 int i;
6756                 if (!view->lines && !view->parent)
6757                         die("No revisions match the given arguments.");
6758                 if (view->lines > 0) {
6759                         commit = view->line[view->lines - 1].data;
6760                         view->line[view->lines - 1].dirty = 1;
6761                         if (!commit->author) {
6762                                 view->lines--;
6763                                 free(commit);
6764                                 graph->commit = NULL;
6765                         }
6766                 }
6767                 update_rev_graph(view, graph);
6769                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6770                         clear_rev_graph(&graph_stacks[i]);
6771                 return TRUE;
6772         }
6774         type = get_line_type(line);
6775         if (type == LINE_COMMIT) {
6776                 commit = calloc(1, sizeof(struct commit));
6777                 if (!commit)
6778                         return FALSE;
6780                 line += STRING_SIZE("commit ");
6781                 if (*line == '-') {
6782                         graph->boundary = 1;
6783                         line++;
6784                 }
6786                 string_copy_rev(commit->id, line);
6787                 commit->refs = get_ref_list(commit->id);
6788                 graph->commit = commit;
6789                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6791                 while ((line = strchr(line, ' '))) {
6792                         line++;
6793                         push_rev_graph(graph->parents, line);
6794                         commit->has_parents = TRUE;
6795                 }
6796                 return TRUE;
6797         }
6799         if (!view->lines)
6800                 return TRUE;
6801         commit = view->line[view->lines - 1].data;
6803         switch (type) {
6804         case LINE_PARENT:
6805                 if (commit->has_parents)
6806                         break;
6807                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6808                 break;
6810         case LINE_AUTHOR:
6811                 parse_author_line(line + STRING_SIZE("author "),
6812                                   &commit->author, &commit->time);
6813                 update_rev_graph(view, graph);
6814                 graph = graph->next;
6815                 break;
6817         default:
6818                 /* Fill in the commit title if it has not already been set. */
6819                 if (commit->title[0])
6820                         break;
6822                 /* Require titles to start with a non-space character at the
6823                  * offset used by git log. */
6824                 if (strncmp(line, "    ", 4))
6825                         break;
6826                 line += 4;
6827                 /* Well, if the title starts with a whitespace character,
6828                  * try to be forgiving.  Otherwise we end up with no title. */
6829                 while (isspace(*line))
6830                         line++;
6831                 if (*line == '\0')
6832                         break;
6833                 /* FIXME: More graceful handling of titles; append "..." to
6834                  * shortened titles, etc. */
6836                 string_expand(commit->title, sizeof(commit->title), line, 1);
6837                 view->line[view->lines - 1].dirty = 1;
6838         }
6840         return TRUE;
6843 static enum request
6844 main_request(struct view *view, enum request request, struct line *line)
6846         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6848         switch (request) {
6849         case REQ_ENTER:
6850                 open_view(view, REQ_VIEW_DIFF, flags);
6851                 break;
6852         case REQ_REFRESH:
6853                 load_refs();
6854                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6855                 break;
6856         default:
6857                 return request;
6858         }
6860         return REQ_NONE;
6863 static bool
6864 grep_refs(struct ref_list *list, regex_t *regex)
6866         regmatch_t pmatch;
6867         size_t i;
6869         if (!opt_show_refs || !list)
6870                 return FALSE;
6872         for (i = 0; i < list->size; i++) {
6873                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6874                         return TRUE;
6875         }
6877         return FALSE;
6880 static bool
6881 main_grep(struct view *view, struct line *line)
6883         struct commit *commit = line->data;
6884         const char *text[] = {
6885                 commit->title,
6886                 opt_author ? commit->author : "",
6887                 mkdate(&commit->time, opt_date),
6888                 NULL
6889         };
6891         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6894 static void
6895 main_select(struct view *view, struct line *line)
6897         struct commit *commit = line->data;
6899         string_copy_rev(view->ref, commit->id);
6900         string_copy_rev(ref_commit, view->ref);
6903 static struct view_ops main_ops = {
6904         "commit",
6905         main_argv,
6906         NULL,
6907         main_read,
6908         main_draw,
6909         main_request,
6910         main_grep,
6911         main_select,
6912 };
6915 /*
6916  * Status management
6917  */
6919 /* Whether or not the curses interface has been initialized. */
6920 static bool cursed = FALSE;
6922 /* Terminal hacks and workarounds. */
6923 static bool use_scroll_redrawwin;
6924 static bool use_scroll_status_wclear;
6926 /* The status window is used for polling keystrokes. */
6927 static WINDOW *status_win;
6929 /* Reading from the prompt? */
6930 static bool input_mode = FALSE;
6932 static bool status_empty = FALSE;
6934 /* Update status and title window. */
6935 static void
6936 report(const char *msg, ...)
6938         struct view *view = display[current_view];
6940         if (input_mode)
6941                 return;
6943         if (!view) {
6944                 char buf[SIZEOF_STR];
6945                 va_list args;
6947                 va_start(args, msg);
6948                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6949                         buf[sizeof(buf) - 1] = 0;
6950                         buf[sizeof(buf) - 2] = '.';
6951                         buf[sizeof(buf) - 3] = '.';
6952                         buf[sizeof(buf) - 4] = '.';
6953                 }
6954                 va_end(args);
6955                 die("%s", buf);
6956         }
6958         if (!status_empty || *msg) {
6959                 va_list args;
6961                 va_start(args, msg);
6963                 wmove(status_win, 0, 0);
6964                 if (view->has_scrolled && use_scroll_status_wclear)
6965                         wclear(status_win);
6966                 if (*msg) {
6967                         vwprintw(status_win, msg, args);
6968                         status_empty = FALSE;
6969                 } else {
6970                         status_empty = TRUE;
6971                 }
6972                 wclrtoeol(status_win);
6973                 wnoutrefresh(status_win);
6975                 va_end(args);
6976         }
6978         update_view_title(view);
6981 static void
6982 init_display(void)
6984         const char *term;
6985         int x, y;
6987         /* Initialize the curses library */
6988         if (isatty(STDIN_FILENO)) {
6989                 cursed = !!initscr();
6990                 opt_tty = stdin;
6991         } else {
6992                 /* Leave stdin and stdout alone when acting as a pager. */
6993                 opt_tty = fopen("/dev/tty", "r+");
6994                 if (!opt_tty)
6995                         die("Failed to open /dev/tty");
6996                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6997         }
6999         if (!cursed)
7000                 die("Failed to initialize curses");
7002         nonl();         /* Disable conversion and detect newlines from input. */
7003         cbreak();       /* Take input chars one at a time, no wait for \n */
7004         noecho();       /* Don't echo input */
7005         leaveok(stdscr, FALSE);
7007         if (has_colors())
7008                 init_colors();
7010         getmaxyx(stdscr, y, x);
7011         status_win = newwin(1, 0, y - 1, 0);
7012         if (!status_win)
7013                 die("Failed to create status window");
7015         /* Enable keyboard mapping */
7016         keypad(status_win, TRUE);
7017         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7019         TABSIZE = opt_tab_size;
7021         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7022         if (term && !strcmp(term, "gnome-terminal")) {
7023                 /* In the gnome-terminal-emulator, the message from
7024                  * scrolling up one line when impossible followed by
7025                  * scrolling down one line causes corruption of the
7026                  * status line. This is fixed by calling wclear. */
7027                 use_scroll_status_wclear = TRUE;
7028                 use_scroll_redrawwin = FALSE;
7030         } else if (term && !strcmp(term, "xrvt-xpm")) {
7031                 /* No problems with full optimizations in xrvt-(unicode)
7032                  * and aterm. */
7033                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7035         } else {
7036                 /* When scrolling in (u)xterm the last line in the
7037                  * scrolling direction will update slowly. */
7038                 use_scroll_redrawwin = TRUE;
7039                 use_scroll_status_wclear = FALSE;
7040         }
7043 static int
7044 get_input(int prompt_position)
7046         struct view *view;
7047         int i, key, cursor_y, cursor_x;
7048         bool loading = FALSE;
7050         if (prompt_position)
7051                 input_mode = TRUE;
7053         while (TRUE) {
7054                 foreach_view (view, i) {
7055                         update_view(view);
7056                         if (view_is_displayed(view) && view->has_scrolled &&
7057                             use_scroll_redrawwin)
7058                                 redrawwin(view->win);
7059                         view->has_scrolled = FALSE;
7060                         if (view->pipe)
7061                                 loading = TRUE;
7062                 }
7064                 /* Update the cursor position. */
7065                 if (prompt_position) {
7066                         getbegyx(status_win, cursor_y, cursor_x);
7067                         cursor_x = prompt_position;
7068                 } else {
7069                         view = display[current_view];
7070                         getbegyx(view->win, cursor_y, cursor_x);
7071                         cursor_x = view->width - 1;
7072                         cursor_y += view->lineno - view->offset;
7073                 }
7074                 setsyx(cursor_y, cursor_x);
7076                 /* Refresh, accept single keystroke of input */
7077                 doupdate();
7078                 nodelay(status_win, loading);
7079                 key = wgetch(status_win);
7081                 /* wgetch() with nodelay() enabled returns ERR when
7082                  * there's no input. */
7083                 if (key == ERR) {
7085                 } else if (key == KEY_RESIZE) {
7086                         int height, width;
7088                         getmaxyx(stdscr, height, width);
7090                         wresize(status_win, 1, width);
7091                         mvwin(status_win, height - 1, 0);
7092                         wnoutrefresh(status_win);
7093                         resize_display();
7094                         redraw_display(TRUE);
7096                 } else {
7097                         input_mode = FALSE;
7098                         return key;
7099                 }
7100         }
7103 static char *
7104 prompt_input(const char *prompt, input_handler handler, void *data)
7106         enum input_status status = INPUT_OK;
7107         static char buf[SIZEOF_STR];
7108         size_t pos = 0;
7110         buf[pos] = 0;
7112         while (status == INPUT_OK || status == INPUT_SKIP) {
7113                 int key;
7115                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7116                 wclrtoeol(status_win);
7118                 key = get_input(pos + 1);
7119                 switch (key) {
7120                 case KEY_RETURN:
7121                 case KEY_ENTER:
7122                 case '\n':
7123                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7124                         break;
7126                 case KEY_BACKSPACE:
7127                         if (pos > 0)
7128                                 buf[--pos] = 0;
7129                         else
7130                                 status = INPUT_CANCEL;
7131                         break;
7133                 case KEY_ESC:
7134                         status = INPUT_CANCEL;
7135                         break;
7137                 default:
7138                         if (pos >= sizeof(buf)) {
7139                                 report("Input string too long");
7140                                 return NULL;
7141                         }
7143                         status = handler(data, buf, key);
7144                         if (status == INPUT_OK)
7145                                 buf[pos++] = (char) key;
7146                 }
7147         }
7149         /* Clear the status window */
7150         status_empty = FALSE;
7151         report("");
7153         if (status == INPUT_CANCEL)
7154                 return NULL;
7156         buf[pos++] = 0;
7158         return buf;
7161 static enum input_status
7162 prompt_yesno_handler(void *data, char *buf, int c)
7164         if (c == 'y' || c == 'Y')
7165                 return INPUT_STOP;
7166         if (c == 'n' || c == 'N')
7167                 return INPUT_CANCEL;
7168         return INPUT_SKIP;
7171 static bool
7172 prompt_yesno(const char *prompt)
7174         char prompt2[SIZEOF_STR];
7176         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7177                 return FALSE;
7179         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7182 static enum input_status
7183 read_prompt_handler(void *data, char *buf, int c)
7185         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7188 static char *
7189 read_prompt(const char *prompt)
7191         return prompt_input(prompt, read_prompt_handler, NULL);
7194 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7196         enum input_status status = INPUT_OK;
7197         int size = 0;
7199         while (items[size].text)
7200                 size++;
7202         while (status == INPUT_OK) {
7203                 const struct menu_item *item = &items[*selected];
7204                 int key;
7205                 int i;
7207                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7208                           prompt, *selected + 1, size);
7209                 if (item->hotkey)
7210                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7211                 wprintw(status_win, "%s", item->text);
7212                 wclrtoeol(status_win);
7214                 key = get_input(COLS - 1);
7215                 switch (key) {
7216                 case KEY_RETURN:
7217                 case KEY_ENTER:
7218                 case '\n':
7219                         status = INPUT_STOP;
7220                         break;
7222                 case KEY_LEFT:
7223                 case KEY_UP:
7224                         *selected = *selected - 1;
7225                         if (*selected < 0)
7226                                 *selected = size - 1;
7227                         break;
7229                 case KEY_RIGHT:
7230                 case KEY_DOWN:
7231                         *selected = (*selected + 1) % size;
7232                         break;
7234                 case KEY_ESC:
7235                         status = INPUT_CANCEL;
7236                         break;
7238                 default:
7239                         for (i = 0; items[i].text; i++)
7240                                 if (items[i].hotkey == key) {
7241                                         *selected = i;
7242                                         status = INPUT_STOP;
7243                                         break;
7244                                 }
7245                 }
7246         }
7248         /* Clear the status window */
7249         status_empty = FALSE;
7250         report("");
7252         return status != INPUT_CANCEL;
7255 /*
7256  * Repository properties
7257  */
7259 static struct ref **refs = NULL;
7260 static size_t refs_size = 0;
7261 static struct ref *refs_head = NULL;
7263 static struct ref_list **ref_lists = NULL;
7264 static size_t ref_lists_size = 0;
7266 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7267 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7268 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7270 static int
7271 compare_refs(const void *ref1_, const void *ref2_)
7273         const struct ref *ref1 = *(const struct ref **)ref1_;
7274         const struct ref *ref2 = *(const struct ref **)ref2_;
7276         if (ref1->tag != ref2->tag)
7277                 return ref2->tag - ref1->tag;
7278         if (ref1->ltag != ref2->ltag)
7279                 return ref2->ltag - ref2->ltag;
7280         if (ref1->head != ref2->head)
7281                 return ref2->head - ref1->head;
7282         if (ref1->tracked != ref2->tracked)
7283                 return ref2->tracked - ref1->tracked;
7284         if (ref1->remote != ref2->remote)
7285                 return ref2->remote - ref1->remote;
7286         return strcmp(ref1->name, ref2->name);
7289 static void
7290 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7292         size_t i;
7294         for (i = 0; i < refs_size; i++)
7295                 if (!visitor(data, refs[i]))
7296                         break;
7299 static struct ref *
7300 get_ref_head()
7302         return refs_head;
7305 static struct ref_list *
7306 get_ref_list(const char *id)
7308         struct ref_list *list;
7309         size_t i;
7311         for (i = 0; i < ref_lists_size; i++)
7312                 if (!strcmp(id, ref_lists[i]->id))
7313                         return ref_lists[i];
7315         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7316                 return NULL;
7317         list = calloc(1, sizeof(*list));
7318         if (!list)
7319                 return NULL;
7321         for (i = 0; i < refs_size; i++) {
7322                 if (!strcmp(id, refs[i]->id) &&
7323                     realloc_refs_list(&list->refs, list->size, 1))
7324                         list->refs[list->size++] = refs[i];
7325         }
7327         if (!list->refs) {
7328                 free(list);
7329                 return NULL;
7330         }
7332         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7333         ref_lists[ref_lists_size++] = list;
7334         return list;
7337 static int
7338 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7340         struct ref *ref = NULL;
7341         bool tag = FALSE;
7342         bool ltag = FALSE;
7343         bool remote = FALSE;
7344         bool tracked = FALSE;
7345         bool head = FALSE;
7346         int from = 0, to = refs_size - 1;
7348         if (!prefixcmp(name, "refs/tags/")) {
7349                 if (!suffixcmp(name, namelen, "^{}")) {
7350                         namelen -= 3;
7351                         name[namelen] = 0;
7352                 } else {
7353                         ltag = TRUE;
7354                 }
7356                 tag = TRUE;
7357                 namelen -= STRING_SIZE("refs/tags/");
7358                 name    += STRING_SIZE("refs/tags/");
7360         } else if (!prefixcmp(name, "refs/remotes/")) {
7361                 remote = TRUE;
7362                 namelen -= STRING_SIZE("refs/remotes/");
7363                 name    += STRING_SIZE("refs/remotes/");
7364                 tracked  = !strcmp(opt_remote, name);
7366         } else if (!prefixcmp(name, "refs/heads/")) {
7367                 namelen -= STRING_SIZE("refs/heads/");
7368                 name    += STRING_SIZE("refs/heads/");
7369                 if (!strncmp(opt_head, name, namelen))
7370                         return OK;
7372         } else if (!strcmp(name, "HEAD")) {
7373                 head     = TRUE;
7374                 if (*opt_head) {
7375                         namelen  = strlen(opt_head);
7376                         name     = opt_head;
7377                 }
7378         }
7380         /* If we are reloading or it's an annotated tag, replace the
7381          * previous SHA1 with the resolved commit id; relies on the fact
7382          * git-ls-remote lists the commit id of an annotated tag right
7383          * before the commit id it points to. */
7384         while (from <= to) {
7385                 size_t pos = (to + from) / 2;
7386                 int cmp = strcmp(name, refs[pos]->name);
7388                 if (!cmp) {
7389                         ref = refs[pos];
7390                         break;
7391                 }
7393                 if (cmp < 0)
7394                         to = pos - 1;
7395                 else
7396                         from = pos + 1;
7397         }
7399         if (!ref) {
7400                 if (!realloc_refs(&refs, refs_size, 1))
7401                         return ERR;
7402                 ref = calloc(1, sizeof(*ref) + namelen);
7403                 if (!ref)
7404                         return ERR;
7405                 memmove(refs + from + 1, refs + from,
7406                         (refs_size - from) * sizeof(*refs));
7407                 refs[from] = ref;
7408                 strncpy(ref->name, name, namelen);
7409                 refs_size++;
7410         }
7412         ref->head = head;
7413         ref->tag = tag;
7414         ref->ltag = ltag;
7415         ref->remote = remote;
7416         ref->tracked = tracked;
7417         string_copy_rev(ref->id, id);
7419         if (head)
7420                 refs_head = ref;
7421         return OK;
7424 static int
7425 load_refs(void)
7427         const char *head_argv[] = {
7428                 "git", "symbolic-ref", "HEAD", NULL
7429         };
7430         static const char *ls_remote_argv[SIZEOF_ARG] = {
7431                 "git", "ls-remote", opt_git_dir, NULL
7432         };
7433         static bool init = FALSE;
7434         size_t i;
7436         if (!init) {
7437                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7438                         die("TIG_LS_REMOTE contains too many arguments");
7439                 init = TRUE;
7440         }
7442         if (!*opt_git_dir)
7443                 return OK;
7445         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7446             !prefixcmp(opt_head, "refs/heads/")) {
7447                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7449                 memmove(opt_head, offset, strlen(offset) + 1);
7450         }
7452         refs_head = NULL;
7453         for (i = 0; i < refs_size; i++)
7454                 refs[i]->id[0] = 0;
7456         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7457                 return ERR;
7459         /* Update the ref lists to reflect changes. */
7460         for (i = 0; i < ref_lists_size; i++) {
7461                 struct ref_list *list = ref_lists[i];
7462                 size_t old, new;
7464                 for (old = new = 0; old < list->size; old++)
7465                         if (!strcmp(list->id, list->refs[old]->id))
7466                                 list->refs[new++] = list->refs[old];
7467                 list->size = new;
7468         }
7470         return OK;
7473 static void
7474 set_remote_branch(const char *name, const char *value, size_t valuelen)
7476         if (!strcmp(name, ".remote")) {
7477                 string_ncopy(opt_remote, value, valuelen);
7479         } else if (*opt_remote && !strcmp(name, ".merge")) {
7480                 size_t from = strlen(opt_remote);
7482                 if (!prefixcmp(value, "refs/heads/"))
7483                         value += STRING_SIZE("refs/heads/");
7485                 if (!string_format_from(opt_remote, &from, "/%s", value))
7486                         opt_remote[0] = 0;
7487         }
7490 static void
7491 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7493         const char *argv[SIZEOF_ARG] = { name, "=" };
7494         int argc = 1 + (cmd == option_set_command);
7495         int error = ERR;
7497         if (!argv_from_string(argv, &argc, value))
7498                 config_msg = "Too many option arguments";
7499         else
7500                 error = cmd(argc, argv);
7502         if (error == ERR)
7503                 warn("Option 'tig.%s': %s", name, config_msg);
7506 static bool
7507 set_environment_variable(const char *name, const char *value)
7509         size_t len = strlen(name) + 1 + strlen(value) + 1;
7510         char *env = malloc(len);
7512         if (env &&
7513             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7514             putenv(env) == 0)
7515                 return TRUE;
7516         free(env);
7517         return FALSE;
7520 static void
7521 set_work_tree(const char *value)
7523         char cwd[SIZEOF_STR];
7525         if (!getcwd(cwd, sizeof(cwd)))
7526                 die("Failed to get cwd path: %s", strerror(errno));
7527         if (chdir(opt_git_dir) < 0)
7528                 die("Failed to chdir(%s): %s", strerror(errno));
7529         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7530                 die("Failed to get git path: %s", strerror(errno));
7531         if (chdir(cwd) < 0)
7532                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7533         if (chdir(value) < 0)
7534                 die("Failed to chdir(%s): %s", value, strerror(errno));
7535         if (!getcwd(cwd, sizeof(cwd)))
7536                 die("Failed to get cwd path: %s", strerror(errno));
7537         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7538                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7539         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7540                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7541         opt_is_inside_work_tree = TRUE;
7544 static int
7545 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7547         if (!strcmp(name, "i18n.commitencoding"))
7548                 string_ncopy(opt_encoding, value, valuelen);
7550         else if (!strcmp(name, "core.editor"))
7551                 string_ncopy(opt_editor, value, valuelen);
7553         else if (!strcmp(name, "core.worktree"))
7554                 set_work_tree(value);
7556         else if (!prefixcmp(name, "tig.color."))
7557                 set_repo_config_option(name + 10, value, option_color_command);
7559         else if (!prefixcmp(name, "tig.bind."))
7560                 set_repo_config_option(name + 9, value, option_bind_command);
7562         else if (!prefixcmp(name, "tig."))
7563                 set_repo_config_option(name + 4, value, option_set_command);
7565         else if (*opt_head && !prefixcmp(name, "branch.") &&
7566                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7567                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7569         return OK;
7572 static int
7573 load_git_config(void)
7575         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7577         return io_run_load(config_list_argv, "=", read_repo_config_option);
7580 static int
7581 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7583         if (!opt_git_dir[0]) {
7584                 string_ncopy(opt_git_dir, name, namelen);
7586         } else if (opt_is_inside_work_tree == -1) {
7587                 /* This can be 3 different values depending on the
7588                  * version of git being used. If git-rev-parse does not
7589                  * understand --is-inside-work-tree it will simply echo
7590                  * the option else either "true" or "false" is printed.
7591                  * Default to true for the unknown case. */
7592                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7594         } else if (*name == '.') {
7595                 string_ncopy(opt_cdup, name, namelen);
7597         } else {
7598                 string_ncopy(opt_prefix, name, namelen);
7599         }
7601         return OK;
7604 static int
7605 load_repo_info(void)
7607         const char *rev_parse_argv[] = {
7608                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7609                         "--show-cdup", "--show-prefix", NULL
7610         };
7612         return io_run_load(rev_parse_argv, "=", read_repo_info);
7616 /*
7617  * Main
7618  */
7620 static const char usage[] =
7621 "tig " TIG_VERSION " (" __DATE__ ")\n"
7622 "\n"
7623 "Usage: tig        [options] [revs] [--] [paths]\n"
7624 "   or: tig show   [options] [revs] [--] [paths]\n"
7625 "   or: tig blame  [rev] path\n"
7626 "   or: tig status\n"
7627 "   or: tig <      [git command output]\n"
7628 "\n"
7629 "Options:\n"
7630 "  -v, --version   Show version and exit\n"
7631 "  -h, --help      Show help message and exit";
7633 static void __NORETURN
7634 quit(int sig)
7636         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7637         if (cursed)
7638                 endwin();
7639         exit(0);
7642 static void __NORETURN
7643 die(const char *err, ...)
7645         va_list args;
7647         endwin();
7649         va_start(args, err);
7650         fputs("tig: ", stderr);
7651         vfprintf(stderr, err, args);
7652         fputs("\n", stderr);
7653         va_end(args);
7655         exit(1);
7658 static void
7659 warn(const char *msg, ...)
7661         va_list args;
7663         va_start(args, msg);
7664         fputs("tig warning: ", stderr);
7665         vfprintf(stderr, msg, args);
7666         fputs("\n", stderr);
7667         va_end(args);
7670 static enum request
7671 parse_options(int argc, const char *argv[])
7673         enum request request = REQ_VIEW_MAIN;
7674         const char *subcommand;
7675         bool seen_dashdash = FALSE;
7676         /* XXX: This is vulnerable to the user overriding options
7677          * required for the main view parser. */
7678         const char *custom_argv[SIZEOF_ARG] = {
7679                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7680                         "--topo-order", NULL
7681         };
7682         int i, j = 6;
7684         if (!isatty(STDIN_FILENO)) {
7685                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7686                 return REQ_VIEW_PAGER;
7687         }
7689         if (argc <= 1)
7690                 return REQ_NONE;
7692         subcommand = argv[1];
7693         if (!strcmp(subcommand, "status")) {
7694                 if (argc > 2)
7695                         warn("ignoring arguments after `%s'", subcommand);
7696                 return REQ_VIEW_STATUS;
7698         } else if (!strcmp(subcommand, "blame")) {
7699                 if (argc <= 2 || argc > 4)
7700                         die("invalid number of options to blame\n\n%s", usage);
7702                 i = 2;
7703                 if (argc == 4) {
7704                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7705                         i++;
7706                 }
7708                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7709                 return REQ_VIEW_BLAME;
7711         } else if (!strcmp(subcommand, "show")) {
7712                 request = REQ_VIEW_DIFF;
7714         } else {
7715                 subcommand = NULL;
7716         }
7718         if (subcommand) {
7719                 custom_argv[1] = subcommand;
7720                 j = 2;
7721         }
7723         for (i = 1 + !!subcommand; i < argc; i++) {
7724                 const char *opt = argv[i];
7726                 if (seen_dashdash || !strcmp(opt, "--")) {
7727                         seen_dashdash = TRUE;
7729                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7730                         printf("tig version %s\n", TIG_VERSION);
7731                         quit(0);
7733                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7734                         printf("%s\n", usage);
7735                         quit(0);
7736                 }
7738                 custom_argv[j++] = opt;
7739                 if (j >= ARRAY_SIZE(custom_argv))
7740                         die("command too long");
7741         }
7743         if (!prepare_update(VIEW(request), custom_argv, NULL))
7744                 die("Failed to format arguments");
7746         return request;
7749 int
7750 main(int argc, const char *argv[])
7752         const char *codeset = "UTF-8";
7753         enum request request = parse_options(argc, argv);
7754         struct view *view;
7755         size_t i;
7757         signal(SIGINT, quit);
7758         signal(SIGPIPE, SIG_IGN);
7760         if (setlocale(LC_ALL, "")) {
7761                 codeset = nl_langinfo(CODESET);
7762         }
7764         if (load_repo_info() == ERR)
7765                 die("Failed to load repo info.");
7767         if (load_options() == ERR)
7768                 die("Failed to load user config.");
7770         if (load_git_config() == ERR)
7771                 die("Failed to load repo config.");
7773         /* Require a git repository unless when running in pager mode. */
7774         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7775                 die("Not a git repository");
7777         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7778                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7779                 if (opt_iconv_in == ICONV_NONE)
7780                         die("Failed to initialize character set conversion");
7781         }
7783         if (codeset && strcmp(codeset, "UTF-8")) {
7784                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7785                 if (opt_iconv_out == ICONV_NONE)
7786                         die("Failed to initialize character set conversion");
7787         }
7789         if (load_refs() == ERR)
7790                 die("Failed to load refs.");
7792         foreach_view (view, i)
7793                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7794                         die("Too many arguments in the `%s` environment variable",
7795                             view->cmd_env);
7797         init_display();
7799         if (request != REQ_NONE)
7800                 open_view(NULL, request, OPEN_PREPARED);
7801         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7803         while (view_driver(display[current_view], request)) {
7804                 int key = get_input(0);
7806                 view = display[current_view];
7807                 request = get_keybinding(view->keymap, key);
7809                 /* Some low-level request handling. This keeps access to
7810                  * status_win restricted. */
7811                 switch (request) {
7812                 case REQ_PROMPT:
7813                 {
7814                         char *cmd = read_prompt(":");
7816                         if (cmd && isdigit(*cmd)) {
7817                                 int lineno = view->lineno + 1;
7819                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7820                                         select_view_line(view, lineno - 1);
7821                                         report("");
7822                                 } else {
7823                                         report("Unable to parse '%s' as a line number", cmd);
7824                                 }
7826                         } else if (cmd) {
7827                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7828                                 const char *argv[SIZEOF_ARG] = { "git" };
7829                                 int argc = 1;
7831                                 /* When running random commands, initially show the
7832                                  * command in the title. However, it maybe later be
7833                                  * overwritten if a commit line is selected. */
7834                                 string_ncopy(next->ref, cmd, strlen(cmd));
7836                                 if (!argv_from_string(argv, &argc, cmd)) {
7837                                         report("Too many arguments");
7838                                 } else if (!prepare_update(next, argv, NULL)) {
7839                                         report("Failed to format command");
7840                                 } else {
7841                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7842                                 }
7843                         }
7845                         request = REQ_NONE;
7846                         break;
7847                 }
7848                 case REQ_SEARCH:
7849                 case REQ_SEARCH_BACK:
7850                 {
7851                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7852                         char *search = read_prompt(prompt);
7854                         if (search)
7855                                 string_ncopy(opt_search, search, strlen(search));
7856                         else if (*opt_search)
7857                                 request = request == REQ_SEARCH ?
7858                                         REQ_FIND_NEXT :
7859                                         REQ_FIND_PREV;
7860                         else
7861                                 request = REQ_NONE;
7862                         break;
7863                 }
7864                 default:
7865                         break;
7866                 }
7867         }
7869         quit(0);
7871         return 0;