Code

Restructure option management code to separate section
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int head:1;    /* Is it the current HEAD? */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int ltag:1;    /* If so, is the tag local? */
127         unsigned int remote:1;  /* Is it a remote ref? */
128         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129         char name[1];           /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         size_t size;            /* Number of refs. */
135         struct ref **refs;      /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151         INPUT_OK,
152         INPUT_SKIP,
153         INPUT_STOP,
154         INPUT_CANCEL
155 };
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163         int hotkey;
164         const char *text;
165         void *data;
166 };
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
170 /*
171  * Allocation helpers ... Entering macro hell to never be seen again.
172  */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
175 static type *                                                                   \
176 name(type **mem, size_t size, size_t increase)                                  \
177 {                                                                               \
178         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
179         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180         type *tmp = *mem;                                                       \
181                                                                                 \
182         if (mem == NULL || num_chunks != num_chunks_new) {                      \
183                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184                 if (tmp)                                                        \
185                         *mem = tmp;                                             \
186         }                                                                       \
187                                                                                 \
188         return tmp;                                                             \
191 /*
192  * String helpers
193  */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198         if (srclen > dstlen - 1)
199                 srclen = dstlen - 1;
201         strncpy(dst, src, srclen);
202         dst[srclen] = 0;
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211         string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
222         size_t size, pos;
224         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225                 if (src[pos] == '\t') {
226                         size_t expanded = tabsize - (size % tabsize);
228                         if (expanded + size >= dstlen - 1)
229                                 expanded = dstlen - size - 1;
230                         memcpy(dst + size, "        ", expanded);
231                         size += expanded;
232                 } else {
233                         dst[size++] = src[pos];
234                 }
235         }
237         dst[size] = 0;
240 static char *
241 chomp_string(char *name)
243         int namelen;
245         while (isspace(*name))
246                 name++;
248         namelen = strlen(name) - 1;
249         while (namelen > 0 && isspace(name[namelen]))
250                 name[namelen--] = 0;
252         return name;
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
258         va_list args;
259         size_t pos = bufpos ? *bufpos : 0;
261         va_start(args, fmt);
262         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263         va_end(args);
265         if (bufpos)
266                 *bufpos = pos;
268         return pos >= bufsize ? FALSE : TRUE;
271 #define string_format(buf, fmt, args...) \
272         string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275         string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
280         size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284         /* Diff-Header == DIFF_HEADER */
285         for (i = 0; i < len; i++) {
286                 if (toupper(str1[i]) == toupper(str2[i]))
287                         continue;
289                 if (string_enum_sep(str1[i]) &&
290                     string_enum_sep(str2[i]))
291                         continue;
293                 return str1[i] - str2[i];
294         }
296         return 0;
299 #define enum_equals(entry, str, len) \
300         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303         const char *name;
304         int namelen;
305         int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
313         static char buf[SIZEOF_STR];
314         int bufpos;
316         for (bufpos = 0; bufpos <= namelen; bufpos++) {
317                 buf[bufpos] = tolower(name[bufpos]);
318                 if (buf[bufpos] == '_')
319                         buf[bufpos] = '-';
320         }
322         buf[bufpos] = 0;
323         return buf;
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
331         size_t namelen = strlen(name);
332         int i;
334         for (i = 0; i < map_size; i++)
335                 if (enum_equals(map[i], name, namelen)) {
336                         *value = map[i].value;
337                         return TRUE;
338                 }
340         return FALSE;
343 #define map_enum(attr, map, name) \
344         map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347         strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
352         size_t len = slen >= 0 ? slen : strlen(str);
353         size_t suffixlen = strlen(suffix);
355         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
359 /*
360  * Unicode / UTF-8 handling
361  *
362  * NOTE: Much of the following code for dealing with Unicode is derived from
363  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
365  */
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
370         if (c >= 0x1100 &&
371            (c <= 0x115f                         /* Hangul Jamo */
372             || c == 0x2329
373             || c == 0x232a
374             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
375                                                 /* CJK ... Yi */
376             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
377             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
378             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
379             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
380             || (c >= 0xffe0  && c <= 0xffe6)
381             || (c >= 0x20000 && c <= 0x2fffd)
382             || (c >= 0x30000 && c <= 0x3fffd)))
383                 return 2;
385         if (c == '\t')
386                 return tab_size;
388         return 1;
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392  * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
396         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
397         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
398         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
399         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
400         2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
401         3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
402 };
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
407         int c = *(unsigned char *) string;
409         return utf8_bytes[c];
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
416         unsigned long unicode;
418         switch (length) {
419         case 1:
420                 unicode  =   string[0];
421                 break;
422         case 2:
423                 unicode  =  (string[0] & 0x1f) << 6;
424                 unicode +=  (string[1] & 0x3f);
425                 break;
426         case 3:
427                 unicode  =  (string[0] & 0x0f) << 12;
428                 unicode += ((string[1] & 0x3f) << 6);
429                 unicode +=  (string[2] & 0x3f);
430                 break;
431         case 4:
432                 unicode  =  (string[0] & 0x0f) << 18;
433                 unicode += ((string[1] & 0x3f) << 12);
434                 unicode += ((string[2] & 0x3f) << 6);
435                 unicode +=  (string[3] & 0x3f);
436                 break;
437         case 5:
438                 unicode  =  (string[0] & 0x0f) << 24;
439                 unicode += ((string[1] & 0x3f) << 18);
440                 unicode += ((string[2] & 0x3f) << 12);
441                 unicode += ((string[3] & 0x3f) << 6);
442                 unicode +=  (string[4] & 0x3f);
443                 break;
444         case 6:
445                 unicode  =  (string[0] & 0x01) << 30;
446                 unicode += ((string[1] & 0x3f) << 24);
447                 unicode += ((string[2] & 0x3f) << 18);
448                 unicode += ((string[3] & 0x3f) << 12);
449                 unicode += ((string[4] & 0x3f) << 6);
450                 unicode +=  (string[5] & 0x3f);
451                 break;
452         default:
453                 return 0;
454         }
456         /* Invalid characters could return the special 0xfffd value but NUL
457          * should be just as good. */
458         return unicode > 0xffff ? 0 : unicode;
461 /* Calculates how much of string can be shown within the given maximum width
462  * and sets trimmed parameter to non-zero value if all of string could not be
463  * shown. If the reserve flag is TRUE, it will reserve at least one
464  * trailing character, which can be useful when drawing a delimiter.
465  *
466  * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
470         const char *string = *start;
471         const char *end = strchr(string, '\0');
472         unsigned char last_bytes = 0;
473         size_t last_ucwidth = 0;
475         *width = 0;
476         *trimmed = 0;
478         while (string < end) {
479                 unsigned char bytes = utf8_char_length(string, end);
480                 size_t ucwidth;
481                 unsigned long unicode;
483                 if (string + bytes > end)
484                         break;
486                 /* Change representation to figure out whether
487                  * it is a single- or double-width character. */
489                 unicode = utf8_to_unicode(string, bytes);
490                 /* FIXME: Graceful handling of invalid Unicode character. */
491                 if (!unicode)
492                         break;
494                 ucwidth = unicode_width(unicode, tab_size);
495                 if (skip > 0) {
496                         skip -= ucwidth <= skip ? ucwidth : skip;
497                         *start += bytes;
498                 }
499                 *width  += ucwidth;
500                 if (*width > max_width) {
501                         *trimmed = 1;
502                         *width -= ucwidth;
503                         if (reserve && *width == max_width) {
504                                 string -= last_bytes;
505                                 *width -= last_ucwidth;
506                         }
507                         break;
508                 }
510                 string  += bytes;
511                 last_bytes = ucwidth ? bytes : 0;
512                 last_ucwidth = ucwidth;
513         }
515         return string - *start;
519 #define DATE_INFO \
520         DATE_(NO), \
521         DATE_(DEFAULT), \
522         DATE_(LOCAL), \
523         DATE_(RELATIVE), \
524         DATE_(SHORT)
526 enum date {
527 #define DATE_(name) DATE_##name
528         DATE_INFO
529 #undef  DATE_
530 };
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534         DATE_INFO
535 #undef  DATE_
536 };
538 struct time {
539         time_t sec;
540         int tz;
541 };
543 static inline int timecmp(const struct time *t1, const struct time *t2)
545         return t1->sec - t2->sec;
548 static const char *
549 mkdate(const struct time *time, enum date date)
551         static char buf[DATE_COLS + 1];
552         static const struct enum_map reldate[] = {
553                 { "second", 1,                  60 * 2 },
554                 { "minute", 60,                 60 * 60 * 2 },
555                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
556                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
557                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
558                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
559         };
560         struct tm tm;
562         if (!date || !time || !time->sec)
563                 return "";
565         if (date == DATE_RELATIVE) {
566                 struct timeval now;
567                 time_t date = time->sec + time->tz;
568                 time_t seconds;
569                 int i;
571                 gettimeofday(&now, NULL);
572                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
573                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
574                         if (seconds >= reldate[i].value)
575                                 continue;
577                         seconds /= reldate[i].namelen;
578                         if (!string_format(buf, "%ld %s%s %s",
579                                            seconds, reldate[i].name,
580                                            seconds > 1 ? "s" : "",
581                                            now.tv_sec >= date ? "ago" : "ahead"))
582                                 break;
583                         return buf;
584                 }
585         }
587         if (date == DATE_LOCAL) {
588                 time_t date = time->sec + time->tz;
589                 localtime_r(&date, &tm);
590         }
591         else {
592                 gmtime_r(&time->sec, &tm);
593         }
594         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
598 #define AUTHOR_VALUES \
599         AUTHOR_(NO), \
600         AUTHOR_(FULL), \
601         AUTHOR_(ABBREVIATED)
603 enum author {
604 #define AUTHOR_(name) AUTHOR_##name
605         AUTHOR_VALUES,
606 #undef  AUTHOR_
607         AUTHOR_DEFAULT = AUTHOR_FULL
608 };
610 static const struct enum_map author_map[] = {
611 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
612         AUTHOR_VALUES
613 #undef  AUTHOR_
614 };
616 static const char *
617 get_author_initials(const char *author)
619         static char initials[AUTHOR_COLS * 6 + 1];
620         size_t pos = 0;
621         const char *end = strchr(author, '\0');
623 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
625         memset(initials, 0, sizeof(initials));
626         while (author < end) {
627                 unsigned char bytes;
628                 size_t i;
630                 while (is_initial_sep(*author))
631                         author++;
633                 bytes = utf8_char_length(author, end);
634                 if (bytes < sizeof(initials) - 1 - pos) {
635                         while (bytes--) {
636                                 initials[pos++] = *author++;
637                         }
638                 }
640                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
641                         if (i < sizeof(initials) - 1)
642                                 initials[i++] = *author;
643                 }
645                 initials[i++] = 0;
646         }
648         return initials;
652 static bool
653 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
655         int valuelen;
657         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
658                 bool advance = cmd[valuelen] != 0;
660                 cmd[valuelen] = 0;
661                 argv[(*argc)++] = chomp_string(cmd);
662                 cmd = chomp_string(cmd + valuelen + advance);
663         }
665         if (*argc < SIZEOF_ARG)
666                 argv[*argc] = NULL;
667         return *argc < SIZEOF_ARG;
670 static bool
671 argv_from_env(const char **argv, const char *name)
673         char *env = argv ? getenv(name) : NULL;
674         int argc = 0;
676         if (env && *env)
677                 env = strdup(env);
678         return !env || argv_from_string(argv, &argc, env);
682 /*
683  * Executing external commands.
684  */
686 enum io_type {
687         IO_FD,                  /* File descriptor based IO. */
688         IO_BG,                  /* Execute command in the background. */
689         IO_FG,                  /* Execute command with same std{in,out,err}. */
690         IO_RD,                  /* Read only fork+exec IO. */
691         IO_WR,                  /* Write only fork+exec IO. */
692         IO_AP,                  /* Append fork+exec output to file. */
693 };
695 struct io {
696         enum io_type type;      /* The requested type of pipe. */
697         const char *dir;        /* Directory from which to execute. */
698         pid_t pid;              /* PID of spawned process. */
699         int pipe;               /* Pipe end for reading or writing. */
700         int error;              /* Error status. */
701         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
702         char *buf;              /* Read buffer. */
703         size_t bufalloc;        /* Allocated buffer size. */
704         size_t bufsize;         /* Buffer content size. */
705         char *bufpos;           /* Current buffer position. */
706         unsigned int eof:1;     /* Has end of file been reached. */
707 };
709 static void
710 io_reset(struct io *io)
712         io->pipe = -1;
713         io->pid = 0;
714         io->buf = io->bufpos = NULL;
715         io->bufalloc = io->bufsize = 0;
716         io->error = 0;
717         io->eof = 0;
720 static void
721 io_init(struct io *io, const char *dir, enum io_type type)
723         io_reset(io);
724         io->type = type;
725         io->dir = dir;
728 static bool
729 io_format(struct io *io, const char *dir, enum io_type type,
730           const char *argv[], enum format_flags flags)
732         io_init(io, dir, type);
733         return format_argv(io->argv, argv, flags);
736 static bool
737 io_open(struct io *io, const char *fmt, ...)
739         char name[SIZEOF_STR] = "";
740         bool fits;
741         va_list args;
743         io_init(io, NULL, IO_FD);
745         va_start(args, fmt);
746         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
747         va_end(args);
749         if (!fits) {
750                 io->error = ENAMETOOLONG;
751                 return FALSE;
752         }
753         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
754         if (io->pipe == -1)
755                 io->error = errno;
756         return io->pipe != -1;
759 static bool
760 io_kill(struct io *io)
762         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
765 static bool
766 io_done(struct io *io)
768         pid_t pid = io->pid;
770         if (io->pipe != -1)
771                 close(io->pipe);
772         free(io->buf);
773         io_reset(io);
775         while (pid > 0) {
776                 int status;
777                 pid_t waiting = waitpid(pid, &status, 0);
779                 if (waiting < 0) {
780                         if (errno == EINTR)
781                                 continue;
782                         io->error = errno;
783                         return FALSE;
784                 }
786                 return waiting == pid &&
787                        !WIFSIGNALED(status) &&
788                        WIFEXITED(status) &&
789                        !WEXITSTATUS(status);
790         }
792         return TRUE;
795 static bool
796 io_start(struct io *io)
798         int pipefds[2] = { -1, -1 };
800         if (io->type == IO_FD)
801                 return TRUE;
803         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
804                 io->error = errno;
805                 return FALSE;
806         } else if (io->type == IO_AP) {
807                 pipefds[1] = io->pipe;
808         }
810         if ((io->pid = fork())) {
811                 if (io->pid == -1)
812                         io->error = errno;
813                 if (pipefds[!(io->type == IO_WR)] != -1)
814                         close(pipefds[!(io->type == IO_WR)]);
815                 if (io->pid != -1) {
816                         io->pipe = pipefds[!!(io->type == IO_WR)];
817                         return TRUE;
818                 }
820         } else {
821                 if (io->type != IO_FG) {
822                         int devnull = open("/dev/null", O_RDWR);
823                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
824                         int writefd = (io->type == IO_RD || io->type == IO_AP)
825                                                         ? pipefds[1] : devnull;
827                         dup2(readfd,  STDIN_FILENO);
828                         dup2(writefd, STDOUT_FILENO);
829                         dup2(devnull, STDERR_FILENO);
831                         close(devnull);
832                         if (pipefds[0] != -1)
833                                 close(pipefds[0]);
834                         if (pipefds[1] != -1)
835                                 close(pipefds[1]);
836                 }
838                 if (io->dir && *io->dir && chdir(io->dir) == -1)
839                         exit(errno);
841                 execvp(io->argv[0], (char *const*) io->argv);
842                 exit(errno);
843         }
845         if (pipefds[!!(io->type == IO_WR)] != -1)
846                 close(pipefds[!!(io->type == IO_WR)]);
847         return FALSE;
850 static bool
851 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
853         io_init(io, dir, type);
854         if (!format_argv(io->argv, argv, FORMAT_NONE))
855                 return FALSE;
856         return io_start(io);
859 static int
860 io_complete(struct io *io)
862         return io_start(io) && io_done(io);
865 static int
866 io_run_bg(const char **argv)
868         struct io io = {};
870         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
871                 return FALSE;
872         return io_complete(&io);
875 static bool
876 io_run_fg(const char **argv, const char *dir)
878         struct io io = {};
880         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
881                 return FALSE;
882         return io_complete(&io);
885 static bool
886 io_run_append(const char **argv, enum format_flags flags, int fd)
888         struct io io = {};
890         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
891                 close(fd);
892                 return FALSE;
893         }
895         io.pipe = fd;
896         return io_complete(&io);
899 static bool
900 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
902         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
905 static bool
906 io_eof(struct io *io)
908         return io->eof;
911 static int
912 io_error(struct io *io)
914         return io->error;
917 static char *
918 io_strerror(struct io *io)
920         return strerror(io->error);
923 static bool
924 io_can_read(struct io *io)
926         struct timeval tv = { 0, 500 };
927         fd_set fds;
929         FD_ZERO(&fds);
930         FD_SET(io->pipe, &fds);
932         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
935 static ssize_t
936 io_read(struct io *io, void *buf, size_t bufsize)
938         do {
939                 ssize_t readsize = read(io->pipe, buf, bufsize);
941                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
942                         continue;
943                 else if (readsize == -1)
944                         io->error = errno;
945                 else if (readsize == 0)
946                         io->eof = 1;
947                 return readsize;
948         } while (1);
951 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
953 static char *
954 io_get(struct io *io, int c, bool can_read)
956         char *eol;
957         ssize_t readsize;
959         while (TRUE) {
960                 if (io->bufsize > 0) {
961                         eol = memchr(io->bufpos, c, io->bufsize);
962                         if (eol) {
963                                 char *line = io->bufpos;
965                                 *eol = 0;
966                                 io->bufpos = eol + 1;
967                                 io->bufsize -= io->bufpos - line;
968                                 return line;
969                         }
970                 }
972                 if (io_eof(io)) {
973                         if (io->bufsize) {
974                                 io->bufpos[io->bufsize] = 0;
975                                 io->bufsize = 0;
976                                 return io->bufpos;
977                         }
978                         return NULL;
979                 }
981                 if (!can_read)
982                         return NULL;
984                 if (io->bufsize > 0 && io->bufpos > io->buf)
985                         memmove(io->buf, io->bufpos, io->bufsize);
987                 if (io->bufalloc == io->bufsize) {
988                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
989                                 return NULL;
990                         io->bufalloc += BUFSIZ;
991                 }
993                 io->bufpos = io->buf;
994                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
995                 if (io_error(io))
996                         return NULL;
997                 io->bufsize += readsize;
998         }
1001 static bool
1002 io_write(struct io *io, const void *buf, size_t bufsize)
1004         size_t written = 0;
1006         while (!io_error(io) && written < bufsize) {
1007                 ssize_t size;
1009                 size = write(io->pipe, buf + written, bufsize - written);
1010                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1011                         continue;
1012                 else if (size == -1)
1013                         io->error = errno;
1014                 else
1015                         written += size;
1016         }
1018         return written == bufsize;
1021 static bool
1022 io_read_buf(struct io *io, char buf[], size_t bufsize)
1024         char *result = io_get(io, '\n', TRUE);
1026         if (result) {
1027                 result = chomp_string(result);
1028                 string_ncopy_do(buf, bufsize, result, strlen(result));
1029         }
1031         return io_done(io) && result;
1034 static bool
1035 io_run_buf(const char **argv, char buf[], size_t bufsize)
1037         struct io io = {};
1039         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1040             && io_read_buf(&io, buf, bufsize);
1043 static int
1044 io_load(struct io *io, const char *separators,
1045         int (*read_property)(char *, size_t, char *, size_t))
1047         char *name;
1048         int state = OK;
1050         if (!io_start(io))
1051                 return ERR;
1053         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054                 char *value;
1055                 size_t namelen;
1056                 size_t valuelen;
1058                 name = chomp_string(name);
1059                 namelen = strcspn(name, separators);
1061                 if (name[namelen]) {
1062                         name[namelen] = 0;
1063                         value = chomp_string(name + namelen + 1);
1064                         valuelen = strlen(value);
1066                 } else {
1067                         value = "";
1068                         valuelen = 0;
1069                 }
1071                 state = read_property(name, namelen, value, valuelen);
1072         }
1074         if (state != ERR && io_error(io))
1075                 state = ERR;
1076         io_done(io);
1078         return state;
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083             int (*read_property)(char *, size_t, char *, size_t))
1085         struct io io = {};
1087         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1088                 ? io_load(&io, separators, read_property) : ERR;
1092 /*
1093  * User requests
1094  */
1096 #define REQ_INFO \
1097         /* XXX: Keep the view request first and in sync with views[]. */ \
1098         REQ_GROUP("View switching") \
1099         REQ_(VIEW_MAIN,         "Show main view"), \
1100         REQ_(VIEW_DIFF,         "Show diff view"), \
1101         REQ_(VIEW_LOG,          "Show log view"), \
1102         REQ_(VIEW_TREE,         "Show tree view"), \
1103         REQ_(VIEW_BLOB,         "Show blob view"), \
1104         REQ_(VIEW_BLAME,        "Show blame view"), \
1105         REQ_(VIEW_BRANCH,       "Show branch view"), \
1106         REQ_(VIEW_HELP,         "Show help page"), \
1107         REQ_(VIEW_PAGER,        "Show pager view"), \
1108         REQ_(VIEW_STATUS,       "Show status view"), \
1109         REQ_(VIEW_STAGE,        "Show stage view"), \
1110         \
1111         REQ_GROUP("View manipulation") \
1112         REQ_(ENTER,             "Enter current line and scroll"), \
1113         REQ_(NEXT,              "Move to next"), \
1114         REQ_(PREVIOUS,          "Move to previous"), \
1115         REQ_(PARENT,            "Move to parent"), \
1116         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1117         REQ_(REFRESH,           "Reload and refresh"), \
1118         REQ_(MAXIMIZE,          "Maximize the current view"), \
1119         REQ_(VIEW_CLOSE,        "Close the current view"), \
1120         REQ_(QUIT,              "Close all views and quit"), \
1121         \
1122         REQ_GROUP("View specific requests") \
1123         REQ_(STATUS_UPDATE,     "Update file status"), \
1124         REQ_(STATUS_REVERT,     "Revert file changes"), \
1125         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1126         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1127         \
1128         REQ_GROUP("Cursor navigation") \
1129         REQ_(MOVE_UP,           "Move cursor one line up"), \
1130         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1131         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1132         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1133         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1134         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1135         \
1136         REQ_GROUP("Scrolling") \
1137         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1138         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1139         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1140         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1141         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1142         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1143         \
1144         REQ_GROUP("Searching") \
1145         REQ_(SEARCH,            "Search the view"), \
1146         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1147         REQ_(FIND_NEXT,         "Find next search match"), \
1148         REQ_(FIND_PREV,         "Find previous search match"), \
1149         \
1150         REQ_GROUP("Option manipulation") \
1151         REQ_(OPTIONS,           "Open option menu"), \
1152         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1153         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1154         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1155         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1156         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1157         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1158         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1159         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1160         \
1161         REQ_GROUP("Misc") \
1162         REQ_(PROMPT,            "Bring up the prompt"), \
1163         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1164         REQ_(SHOW_VERSION,      "Show version information"), \
1165         REQ_(STOP_LOADING,      "Stop all loading views"), \
1166         REQ_(EDIT,              "Open in editor"), \
1167         REQ_(NONE,              "Do nothing")
1170 /* User action requests. */
1171 enum request {
1172 #define REQ_GROUP(help)
1173 #define REQ_(req, help) REQ_##req
1175         /* Offset all requests to avoid conflicts with ncurses getch values. */
1176         REQ_UNKNOWN = KEY_MAX + 1,
1177         REQ_OFFSET,
1178         REQ_INFO
1180 #undef  REQ_GROUP
1181 #undef  REQ_
1182 };
1184 struct request_info {
1185         enum request request;
1186         const char *name;
1187         int namelen;
1188         const char *help;
1189 };
1191 static const struct request_info req_info[] = {
1192 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1193 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1194         REQ_INFO
1195 #undef  REQ_GROUP
1196 #undef  REQ_
1197 };
1199 static enum request
1200 get_request(const char *name)
1202         int namelen = strlen(name);
1203         int i;
1205         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1206                 if (enum_equals(req_info[i], name, namelen))
1207                         return req_info[i].request;
1209         return REQ_UNKNOWN;
1213 /*
1214  * Options
1215  */
1217 /* Option and state variables. */
1218 static enum date opt_date               = DATE_DEFAULT;
1219 static enum author opt_author           = AUTHOR_DEFAULT;
1220 static bool opt_line_number             = FALSE;
1221 static bool opt_line_graphics           = TRUE;
1222 static bool opt_rev_graph               = FALSE;
1223 static bool opt_show_refs               = TRUE;
1224 static int opt_num_interval             = 5;
1225 static double opt_hscroll               = 0.50;
1226 static double opt_scale_split_view      = 2.0 / 3.0;
1227 static int opt_tab_size                 = 8;
1228 static int opt_author_cols              = AUTHOR_COLS;
1229 static char opt_path[SIZEOF_STR]        = "";
1230 static char opt_file[SIZEOF_STR]        = "";
1231 static char opt_ref[SIZEOF_REF]         = "";
1232 static char opt_head[SIZEOF_REF]        = "";
1233 static char opt_remote[SIZEOF_REF]      = "";
1234 static char opt_encoding[20]            = "UTF-8";
1235 static iconv_t opt_iconv_in             = ICONV_NONE;
1236 static iconv_t opt_iconv_out            = ICONV_NONE;
1237 static char opt_search[SIZEOF_STR]      = "";
1238 static char opt_cdup[SIZEOF_STR]        = "";
1239 static char opt_prefix[SIZEOF_STR]      = "";
1240 static char opt_git_dir[SIZEOF_STR]     = "";
1241 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1242 static char opt_editor[SIZEOF_STR]      = "";
1243 static FILE *opt_tty                    = NULL;
1245 #define is_initial_commit()     (!get_ref_head())
1246 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1249 /*
1250  * Line-oriented content detection.
1251  */
1253 #define LINE_INFO \
1254 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1255 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1256 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1257 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1258 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1259 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1261 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1262 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1263 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1264 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1265 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1266 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1267 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1268 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1269 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1270 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1271 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1272 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1273 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1275 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1276 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1277 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1278 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1279 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1280 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1281 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1282 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1283 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1284 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1285 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1286 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1287 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1288 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1289 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1290 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1291 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1292 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1293 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1294 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1295 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1296 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1297 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1298 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1299 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1300 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1301 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1302 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1303 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1304 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1305 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1306 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1307 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1308 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1309 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1310 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1311 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1312 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1314 enum line_type {
1315 #define LINE(type, line, fg, bg, attr) \
1316         LINE_##type
1317         LINE_INFO,
1318         LINE_NONE
1319 #undef  LINE
1320 };
1322 struct line_info {
1323         const char *name;       /* Option name. */
1324         int namelen;            /* Size of option name. */
1325         const char *line;       /* The start of line to match. */
1326         int linelen;            /* Size of string to match. */
1327         int fg, bg, attr;       /* Color and text attributes for the lines. */
1328 };
1330 static struct line_info line_info[] = {
1331 #define LINE(type, line, fg, bg, attr) \
1332         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1333         LINE_INFO
1334 #undef  LINE
1335 };
1337 static enum line_type
1338 get_line_type(const char *line)
1340         int linelen = strlen(line);
1341         enum line_type type;
1343         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1344                 /* Case insensitive search matches Signed-off-by lines better. */
1345                 if (linelen >= line_info[type].linelen &&
1346                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1347                         return type;
1349         return LINE_DEFAULT;
1352 static inline int
1353 get_line_attr(enum line_type type)
1355         assert(type < ARRAY_SIZE(line_info));
1356         return COLOR_PAIR(type) | line_info[type].attr;
1359 static struct line_info *
1360 get_line_info(const char *name)
1362         size_t namelen = strlen(name);
1363         enum line_type type;
1365         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1366                 if (enum_equals(line_info[type], name, namelen))
1367                         return &line_info[type];
1369         return NULL;
1372 static void
1373 init_colors(void)
1375         int default_bg = line_info[LINE_DEFAULT].bg;
1376         int default_fg = line_info[LINE_DEFAULT].fg;
1377         enum line_type type;
1379         start_color();
1381         if (assume_default_colors(default_fg, default_bg) == ERR) {
1382                 default_bg = COLOR_BLACK;
1383                 default_fg = COLOR_WHITE;
1384         }
1386         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1387                 struct line_info *info = &line_info[type];
1388                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1389                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1391                 init_pair(type, fg, bg);
1392         }
1395 struct line {
1396         enum line_type type;
1398         /* State flags */
1399         unsigned int selected:1;
1400         unsigned int dirty:1;
1401         unsigned int cleareol:1;
1402         unsigned int other:16;
1404         void *data;             /* User data */
1405 };
1408 /*
1409  * Keys
1410  */
1412 struct keybinding {
1413         int alias;
1414         enum request request;
1415 };
1417 static struct keybinding default_keybindings[] = {
1418         /* View switching */
1419         { 'm',          REQ_VIEW_MAIN },
1420         { 'd',          REQ_VIEW_DIFF },
1421         { 'l',          REQ_VIEW_LOG },
1422         { 't',          REQ_VIEW_TREE },
1423         { 'f',          REQ_VIEW_BLOB },
1424         { 'B',          REQ_VIEW_BLAME },
1425         { 'H',          REQ_VIEW_BRANCH },
1426         { 'p',          REQ_VIEW_PAGER },
1427         { 'h',          REQ_VIEW_HELP },
1428         { 'S',          REQ_VIEW_STATUS },
1429         { 'c',          REQ_VIEW_STAGE },
1431         /* View manipulation */
1432         { 'q',          REQ_VIEW_CLOSE },
1433         { KEY_TAB,      REQ_VIEW_NEXT },
1434         { KEY_RETURN,   REQ_ENTER },
1435         { KEY_UP,       REQ_PREVIOUS },
1436         { KEY_DOWN,     REQ_NEXT },
1437         { 'R',          REQ_REFRESH },
1438         { KEY_F(5),     REQ_REFRESH },
1439         { 'O',          REQ_MAXIMIZE },
1441         /* Cursor navigation */
1442         { 'k',          REQ_MOVE_UP },
1443         { 'j',          REQ_MOVE_DOWN },
1444         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1445         { KEY_END,      REQ_MOVE_LAST_LINE },
1446         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1447         { ' ',          REQ_MOVE_PAGE_DOWN },
1448         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1449         { 'b',          REQ_MOVE_PAGE_UP },
1450         { '-',          REQ_MOVE_PAGE_UP },
1452         /* Scrolling */
1453         { KEY_LEFT,     REQ_SCROLL_LEFT },
1454         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1455         { KEY_IC,       REQ_SCROLL_LINE_UP },
1456         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1457         { 'w',          REQ_SCROLL_PAGE_UP },
1458         { 's',          REQ_SCROLL_PAGE_DOWN },
1460         /* Searching */
1461         { '/',          REQ_SEARCH },
1462         { '?',          REQ_SEARCH_BACK },
1463         { 'n',          REQ_FIND_NEXT },
1464         { 'N',          REQ_FIND_PREV },
1466         /* Misc */
1467         { 'Q',          REQ_QUIT },
1468         { 'z',          REQ_STOP_LOADING },
1469         { 'v',          REQ_SHOW_VERSION },
1470         { 'r',          REQ_SCREEN_REDRAW },
1471         { 'o',          REQ_OPTIONS },
1472         { '.',          REQ_TOGGLE_LINENO },
1473         { 'D',          REQ_TOGGLE_DATE },
1474         { 'A',          REQ_TOGGLE_AUTHOR },
1475         { 'g',          REQ_TOGGLE_REV_GRAPH },
1476         { 'F',          REQ_TOGGLE_REFS },
1477         { 'I',          REQ_TOGGLE_SORT_ORDER },
1478         { 'i',          REQ_TOGGLE_SORT_FIELD },
1479         { ':',          REQ_PROMPT },
1480         { 'u',          REQ_STATUS_UPDATE },
1481         { '!',          REQ_STATUS_REVERT },
1482         { 'M',          REQ_STATUS_MERGE },
1483         { '@',          REQ_STAGE_NEXT },
1484         { ',',          REQ_PARENT },
1485         { 'e',          REQ_EDIT },
1486 };
1488 #define KEYMAP_INFO \
1489         KEYMAP_(GENERIC), \
1490         KEYMAP_(MAIN), \
1491         KEYMAP_(DIFF), \
1492         KEYMAP_(LOG), \
1493         KEYMAP_(TREE), \
1494         KEYMAP_(BLOB), \
1495         KEYMAP_(BLAME), \
1496         KEYMAP_(BRANCH), \
1497         KEYMAP_(PAGER), \
1498         KEYMAP_(HELP), \
1499         KEYMAP_(STATUS), \
1500         KEYMAP_(STAGE)
1502 enum keymap {
1503 #define KEYMAP_(name) KEYMAP_##name
1504         KEYMAP_INFO
1505 #undef  KEYMAP_
1506 };
1508 static const struct enum_map keymap_table[] = {
1509 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1510         KEYMAP_INFO
1511 #undef  KEYMAP_
1512 };
1514 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1516 struct keybinding_table {
1517         struct keybinding *data;
1518         size_t size;
1519 };
1521 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1523 static void
1524 add_keybinding(enum keymap keymap, enum request request, int key)
1526         struct keybinding_table *table = &keybindings[keymap];
1527         size_t i;
1529         for (i = 0; i < keybindings[keymap].size; i++) {
1530                 if (keybindings[keymap].data[i].alias == key) {
1531                         keybindings[keymap].data[i].request = request;
1532                         return;
1533                 }
1534         }
1536         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1537         if (!table->data)
1538                 die("Failed to allocate keybinding");
1539         table->data[table->size].alias = key;
1540         table->data[table->size++].request = request;
1542         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1543                 int i;
1545                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1546                         if (default_keybindings[i].alias == key)
1547                                 default_keybindings[i].request = REQ_NONE;
1548         }
1551 /* Looks for a key binding first in the given map, then in the generic map, and
1552  * lastly in the default keybindings. */
1553 static enum request
1554 get_keybinding(enum keymap keymap, int key)
1556         size_t i;
1558         for (i = 0; i < keybindings[keymap].size; i++)
1559                 if (keybindings[keymap].data[i].alias == key)
1560                         return keybindings[keymap].data[i].request;
1562         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1563                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1564                         return keybindings[KEYMAP_GENERIC].data[i].request;
1566         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1567                 if (default_keybindings[i].alias == key)
1568                         return default_keybindings[i].request;
1570         return (enum request) key;
1574 struct key {
1575         const char *name;
1576         int value;
1577 };
1579 static const struct key key_table[] = {
1580         { "Enter",      KEY_RETURN },
1581         { "Space",      ' ' },
1582         { "Backspace",  KEY_BACKSPACE },
1583         { "Tab",        KEY_TAB },
1584         { "Escape",     KEY_ESC },
1585         { "Left",       KEY_LEFT },
1586         { "Right",      KEY_RIGHT },
1587         { "Up",         KEY_UP },
1588         { "Down",       KEY_DOWN },
1589         { "Insert",     KEY_IC },
1590         { "Delete",     KEY_DC },
1591         { "Hash",       '#' },
1592         { "Home",       KEY_HOME },
1593         { "End",        KEY_END },
1594         { "PageUp",     KEY_PPAGE },
1595         { "PageDown",   KEY_NPAGE },
1596         { "F1",         KEY_F(1) },
1597         { "F2",         KEY_F(2) },
1598         { "F3",         KEY_F(3) },
1599         { "F4",         KEY_F(4) },
1600         { "F5",         KEY_F(5) },
1601         { "F6",         KEY_F(6) },
1602         { "F7",         KEY_F(7) },
1603         { "F8",         KEY_F(8) },
1604         { "F9",         KEY_F(9) },
1605         { "F10",        KEY_F(10) },
1606         { "F11",        KEY_F(11) },
1607         { "F12",        KEY_F(12) },
1608 };
1610 static int
1611 get_key_value(const char *name)
1613         int i;
1615         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1616                 if (!strcasecmp(key_table[i].name, name))
1617                         return key_table[i].value;
1619         if (strlen(name) == 1 && isprint(*name))
1620                 return (int) *name;
1622         return ERR;
1625 static const char *
1626 get_key_name(int key_value)
1628         static char key_char[] = "'X'";
1629         const char *seq = NULL;
1630         int key;
1632         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1633                 if (key_table[key].value == key_value)
1634                         seq = key_table[key].name;
1636         if (seq == NULL &&
1637             key_value < 127 &&
1638             isprint(key_value)) {
1639                 key_char[1] = (char) key_value;
1640                 seq = key_char;
1641         }
1643         return seq ? seq : "(no key)";
1646 static bool
1647 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1649         const char *sep = *pos > 0 ? ", " : "";
1650         const char *keyname = get_key_name(keybinding->alias);
1652         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1655 static bool
1656 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1657                            enum keymap keymap, bool all)
1659         int i;
1661         for (i = 0; i < keybindings[keymap].size; i++) {
1662                 if (keybindings[keymap].data[i].request == request) {
1663                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1664                                 return FALSE;
1665                         if (!all)
1666                                 break;
1667                 }
1668         }
1670         return TRUE;
1673 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1675 static const char *
1676 get_keys(enum keymap keymap, enum request request, bool all)
1678         static char buf[BUFSIZ];
1679         size_t pos = 0;
1680         int i;
1682         buf[pos] = 0;
1684         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1685                 return "Too many keybindings!";
1686         if (pos > 0 && !all)
1687                 return buf;
1689         if (keymap != KEYMAP_GENERIC) {
1690                 /* Only the generic keymap includes the default keybindings when
1691                  * listing all keys. */
1692                 if (all)
1693                         return buf;
1695                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1696                         return "Too many keybindings!";
1697                 if (pos)
1698                         return buf;
1699         }
1701         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1702                 if (default_keybindings[i].request == request) {
1703                         if (!append_key(buf, &pos, &default_keybindings[i]))
1704                                 return "Too many keybindings!";
1705                         if (!all)
1706                                 return buf;
1707                 }
1708         }
1710         return buf;
1713 struct run_request {
1714         enum keymap keymap;
1715         int key;
1716         const char *argv[SIZEOF_ARG];
1717 };
1719 static struct run_request *run_request;
1720 static size_t run_requests;
1722 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1724 static enum request
1725 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1727         struct run_request *req;
1729         if (argc >= ARRAY_SIZE(req->argv) - 1)
1730                 return REQ_NONE;
1732         if (!realloc_run_requests(&run_request, run_requests, 1))
1733                 return REQ_NONE;
1735         req = &run_request[run_requests];
1736         req->keymap = keymap;
1737         req->key = key;
1738         req->argv[0] = NULL;
1740         if (!format_argv(req->argv, argv, FORMAT_NONE))
1741                 return REQ_NONE;
1743         return REQ_NONE + ++run_requests;
1746 static struct run_request *
1747 get_run_request(enum request request)
1749         if (request <= REQ_NONE)
1750                 return NULL;
1751         return &run_request[request - REQ_NONE - 1];
1754 static void
1755 add_builtin_run_requests(void)
1757         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1758         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1759         const char *commit[] = { "git", "commit", NULL };
1760         const char *gc[] = { "git", "gc", NULL };
1761         struct {
1762                 enum keymap keymap;
1763                 int key;
1764                 int argc;
1765                 const char **argv;
1766         } reqs[] = {
1767                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1768                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1769                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1770                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1771         };
1772         int i;
1774         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1775                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1777                 if (req != reqs[i].key)
1778                         continue;
1779                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1780                 if (req != REQ_NONE)
1781                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1782         }
1785 /*
1786  * User config file handling.
1787  */
1789 static int   config_lineno;
1790 static bool  config_errors;
1791 static const char *config_msg;
1793 static const struct enum_map color_map[] = {
1794 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1795         COLOR_MAP(DEFAULT),
1796         COLOR_MAP(BLACK),
1797         COLOR_MAP(BLUE),
1798         COLOR_MAP(CYAN),
1799         COLOR_MAP(GREEN),
1800         COLOR_MAP(MAGENTA),
1801         COLOR_MAP(RED),
1802         COLOR_MAP(WHITE),
1803         COLOR_MAP(YELLOW),
1804 };
1806 static const struct enum_map attr_map[] = {
1807 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1808         ATTR_MAP(NORMAL),
1809         ATTR_MAP(BLINK),
1810         ATTR_MAP(BOLD),
1811         ATTR_MAP(DIM),
1812         ATTR_MAP(REVERSE),
1813         ATTR_MAP(STANDOUT),
1814         ATTR_MAP(UNDERLINE),
1815 };
1817 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1819 static int parse_step(double *opt, const char *arg)
1821         *opt = atoi(arg);
1822         if (!strchr(arg, '%'))
1823                 return OK;
1825         /* "Shift down" so 100% and 1 does not conflict. */
1826         *opt = (*opt - 1) / 100;
1827         if (*opt >= 1.0) {
1828                 *opt = 0.99;
1829                 config_msg = "Step value larger than 100%";
1830                 return ERR;
1831         }
1832         if (*opt < 0.0) {
1833                 *opt = 1;
1834                 config_msg = "Invalid step value";
1835                 return ERR;
1836         }
1837         return OK;
1840 static int
1841 parse_int(int *opt, const char *arg, int min, int max)
1843         int value = atoi(arg);
1845         if (min <= value && value <= max) {
1846                 *opt = value;
1847                 return OK;
1848         }
1850         config_msg = "Integer value out of bound";
1851         return ERR;
1854 static bool
1855 set_color(int *color, const char *name)
1857         if (map_enum(color, color_map, name))
1858                 return TRUE;
1859         if (!prefixcmp(name, "color"))
1860                 return parse_int(color, name + 5, 0, 255) == OK;
1861         return FALSE;
1864 /* Wants: object fgcolor bgcolor [attribute] */
1865 static int
1866 option_color_command(int argc, const char *argv[])
1868         struct line_info *info;
1870         if (argc < 3) {
1871                 config_msg = "Wrong number of arguments given to color command";
1872                 return ERR;
1873         }
1875         info = get_line_info(argv[0]);
1876         if (!info) {
1877                 static const struct enum_map obsolete[] = {
1878                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1879                         ENUM_MAP("main-date",   LINE_DATE),
1880                         ENUM_MAP("main-author", LINE_AUTHOR),
1881                 };
1882                 int index;
1884                 if (!map_enum(&index, obsolete, argv[0])) {
1885                         config_msg = "Unknown color name";
1886                         return ERR;
1887                 }
1888                 info = &line_info[index];
1889         }
1891         if (!set_color(&info->fg, argv[1]) ||
1892             !set_color(&info->bg, argv[2])) {
1893                 config_msg = "Unknown color";
1894                 return ERR;
1895         }
1897         info->attr = 0;
1898         while (argc-- > 3) {
1899                 int attr;
1901                 if (!set_attribute(&attr, argv[argc])) {
1902                         config_msg = "Unknown attribute";
1903                         return ERR;
1904                 }
1905                 info->attr |= attr;
1906         }
1908         return OK;
1911 static int parse_bool(bool *opt, const char *arg)
1913         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1914                 ? TRUE : FALSE;
1915         return OK;
1918 static int parse_enum_do(unsigned int *opt, const char *arg,
1919                          const struct enum_map *map, size_t map_size)
1921         bool is_true;
1923         assert(map_size > 1);
1925         if (map_enum_do(map, map_size, (int *) opt, arg))
1926                 return OK;
1928         if (parse_bool(&is_true, arg) != OK)
1929                 return ERR;
1931         *opt = is_true ? map[1].value : map[0].value;
1932         return OK;
1935 #define parse_enum(opt, arg, map) \
1936         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1938 static int
1939 parse_string(char *opt, const char *arg, size_t optsize)
1941         int arglen = strlen(arg);
1943         switch (arg[0]) {
1944         case '\"':
1945         case '\'':
1946                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1947                         config_msg = "Unmatched quotation";
1948                         return ERR;
1949                 }
1950                 arg += 1; arglen -= 2;
1951         default:
1952                 string_ncopy_do(opt, optsize, arg, arglen);
1953                 return OK;
1954         }
1957 /* Wants: name = value */
1958 static int
1959 option_set_command(int argc, const char *argv[])
1961         if (argc != 3) {
1962                 config_msg = "Wrong number of arguments given to set command";
1963                 return ERR;
1964         }
1966         if (strcmp(argv[1], "=")) {
1967                 config_msg = "No value assigned";
1968                 return ERR;
1969         }
1971         if (!strcmp(argv[0], "show-author"))
1972                 return parse_enum(&opt_author, argv[2], author_map);
1974         if (!strcmp(argv[0], "show-date"))
1975                 return parse_enum(&opt_date, argv[2], date_map);
1977         if (!strcmp(argv[0], "show-rev-graph"))
1978                 return parse_bool(&opt_rev_graph, argv[2]);
1980         if (!strcmp(argv[0], "show-refs"))
1981                 return parse_bool(&opt_show_refs, argv[2]);
1983         if (!strcmp(argv[0], "show-line-numbers"))
1984                 return parse_bool(&opt_line_number, argv[2]);
1986         if (!strcmp(argv[0], "line-graphics"))
1987                 return parse_bool(&opt_line_graphics, argv[2]);
1989         if (!strcmp(argv[0], "line-number-interval"))
1990                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1992         if (!strcmp(argv[0], "author-width"))
1993                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1995         if (!strcmp(argv[0], "horizontal-scroll"))
1996                 return parse_step(&opt_hscroll, argv[2]);
1998         if (!strcmp(argv[0], "split-view-height"))
1999                 return parse_step(&opt_scale_split_view, argv[2]);
2001         if (!strcmp(argv[0], "tab-size"))
2002                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2004         if (!strcmp(argv[0], "commit-encoding"))
2005                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2007         config_msg = "Unknown variable name";
2008         return ERR;
2011 /* Wants: mode request key */
2012 static int
2013 option_bind_command(int argc, const char *argv[])
2015         enum request request;
2016         int keymap = -1;
2017         int key;
2019         if (argc < 3) {
2020                 config_msg = "Wrong number of arguments given to bind command";
2021                 return ERR;
2022         }
2024         if (!set_keymap(&keymap, argv[0])) {
2025                 config_msg = "Unknown key map";
2026                 return ERR;
2027         }
2029         key = get_key_value(argv[1]);
2030         if (key == ERR) {
2031                 config_msg = "Unknown key";
2032                 return ERR;
2033         }
2035         request = get_request(argv[2]);
2036         if (request == REQ_UNKNOWN) {
2037                 static const struct enum_map obsolete[] = {
2038                         ENUM_MAP("cherry-pick",         REQ_NONE),
2039                         ENUM_MAP("screen-resize",       REQ_NONE),
2040                         ENUM_MAP("tree-parent",         REQ_PARENT),
2041                 };
2042                 int alias;
2044                 if (map_enum(&alias, obsolete, argv[2])) {
2045                         if (alias != REQ_NONE)
2046                                 add_keybinding(keymap, alias, key);
2047                         config_msg = "Obsolete request name";
2048                         return ERR;
2049                 }
2050         }
2051         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2052                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2053         if (request == REQ_UNKNOWN) {
2054                 config_msg = "Unknown request name";
2055                 return ERR;
2056         }
2058         add_keybinding(keymap, request, key);
2060         return OK;
2063 static int
2064 set_option(const char *opt, char *value)
2066         const char *argv[SIZEOF_ARG];
2067         int argc = 0;
2069         if (!argv_from_string(argv, &argc, value)) {
2070                 config_msg = "Too many option arguments";
2071                 return ERR;
2072         }
2074         if (!strcmp(opt, "color"))
2075                 return option_color_command(argc, argv);
2077         if (!strcmp(opt, "set"))
2078                 return option_set_command(argc, argv);
2080         if (!strcmp(opt, "bind"))
2081                 return option_bind_command(argc, argv);
2083         config_msg = "Unknown option command";
2084         return ERR;
2087 static int
2088 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2090         int status = OK;
2092         config_lineno++;
2093         config_msg = "Internal error";
2095         /* Check for comment markers, since read_properties() will
2096          * only ensure opt and value are split at first " \t". */
2097         optlen = strcspn(opt, "#");
2098         if (optlen == 0)
2099                 return OK;
2101         if (opt[optlen] != 0) {
2102                 config_msg = "No option value";
2103                 status = ERR;
2105         }  else {
2106                 /* Look for comment endings in the value. */
2107                 size_t len = strcspn(value, "#");
2109                 if (len < valuelen) {
2110                         valuelen = len;
2111                         value[valuelen] = 0;
2112                 }
2114                 status = set_option(opt, value);
2115         }
2117         if (status == ERR) {
2118                 warn("Error on line %d, near '%.*s': %s",
2119                      config_lineno, (int) optlen, opt, config_msg);
2120                 config_errors = TRUE;
2121         }
2123         /* Always keep going if errors are encountered. */
2124         return OK;
2127 static void
2128 load_option_file(const char *path)
2130         struct io io = {};
2132         /* It's OK that the file doesn't exist. */
2133         if (!io_open(&io, "%s", path))
2134                 return;
2136         config_lineno = 0;
2137         config_errors = FALSE;
2139         if (io_load(&io, " \t", read_option) == ERR ||
2140             config_errors == TRUE)
2141                 warn("Errors while loading %s.", path);
2144 static int
2145 load_options(void)
2147         const char *home = getenv("HOME");
2148         const char *tigrc_user = getenv("TIGRC_USER");
2149         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2150         char buf[SIZEOF_STR];
2152         if (!tigrc_system)
2153                 tigrc_system = SYSCONFDIR "/tigrc";
2154         load_option_file(tigrc_system);
2156         if (!tigrc_user) {
2157                 if (!home || !string_format(buf, "%s/.tigrc", home))
2158                         return ERR;
2159                 tigrc_user = buf;
2160         }
2161         load_option_file(tigrc_user);
2163         /* Add _after_ loading config files to avoid adding run requests
2164          * that conflict with keybindings. */
2165         add_builtin_run_requests();
2167         return OK;
2171 /*
2172  * The viewer
2173  */
2175 struct view;
2176 struct view_ops;
2178 /* The display array of active views and the index of the current view. */
2179 static struct view *display[2];
2180 static unsigned int current_view;
2182 #define foreach_displayed_view(view, i) \
2183         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2185 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2187 /* Current head and commit ID */
2188 static char ref_blob[SIZEOF_REF]        = "";
2189 static char ref_commit[SIZEOF_REF]      = "HEAD";
2190 static char ref_head[SIZEOF_REF]        = "HEAD";
2191 static char ref_branch[SIZEOF_REF]      = "";
2193 enum view_type {
2194         VIEW_MAIN,
2195         VIEW_DIFF,
2196         VIEW_LOG,
2197         VIEW_TREE,
2198         VIEW_BLOB,
2199         VIEW_BLAME,
2200         VIEW_BRANCH,
2201         VIEW_HELP,
2202         VIEW_PAGER,
2203         VIEW_STATUS,
2204         VIEW_STAGE,
2205 };
2207 struct view {
2208         enum view_type type;    /* View type */
2209         const char *name;       /* View name */
2210         const char *cmd_env;    /* Command line set via environment */
2211         const char *id;         /* Points to either of ref_{head,commit,blob} */
2213         struct view_ops *ops;   /* View operations */
2215         enum keymap keymap;     /* What keymap does this view have */
2216         bool git_dir;           /* Whether the view requires a git directory. */
2217         bool refresh;           /* Whether the view supports refreshing. */
2219         char ref[SIZEOF_REF];   /* Hovered commit reference */
2220         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2222         int height, width;      /* The width and height of the main window */
2223         WINDOW *win;            /* The main window */
2224         WINDOW *title;          /* The title window living below the main window */
2226         /* Navigation */
2227         unsigned long offset;   /* Offset of the window top */
2228         unsigned long yoffset;  /* Offset from the window side. */
2229         unsigned long lineno;   /* Current line number */
2230         unsigned long p_offset; /* Previous offset of the window top */
2231         unsigned long p_yoffset;/* Previous offset from the window side */
2232         unsigned long p_lineno; /* Previous current line number */
2233         bool p_restore;         /* Should the previous position be restored. */
2235         /* Searching */
2236         char grep[SIZEOF_STR];  /* Search string */
2237         regex_t *regex;         /* Pre-compiled regexp */
2239         /* If non-NULL, points to the view that opened this view. If this view
2240          * is closed tig will switch back to the parent view. */
2241         struct view *parent;
2242         struct view *prev;
2244         /* Buffering */
2245         size_t lines;           /* Total number of lines */
2246         struct line *line;      /* Line index */
2247         unsigned int digits;    /* Number of digits in the lines member. */
2249         /* Drawing */
2250         struct line *curline;   /* Line currently being drawn. */
2251         enum line_type curtype; /* Attribute currently used for drawing. */
2252         unsigned long col;      /* Column when drawing. */
2253         bool has_scrolled;      /* View was scrolled. */
2255         /* Loading */
2256         struct io io;
2257         struct io *pipe;
2258         time_t start_time;
2259         time_t update_secs;
2260 };
2262 struct view_ops {
2263         /* What type of content being displayed. Used in the title bar. */
2264         const char *type;
2265         /* Default command arguments. */
2266         const char **argv;
2267         /* Open and reads in all view content. */
2268         bool (*open)(struct view *view);
2269         /* Read one line; updates view->line. */
2270         bool (*read)(struct view *view, char *data);
2271         /* Draw one line; @lineno must be < view->height. */
2272         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2273         /* Depending on view handle a special requests. */
2274         enum request (*request)(struct view *view, enum request request, struct line *line);
2275         /* Search for regexp in a line. */
2276         bool (*grep)(struct view *view, struct line *line);
2277         /* Select line */
2278         void (*select)(struct view *view, struct line *line);
2279         /* Prepare view for loading */
2280         bool (*prepare)(struct view *view);
2281 };
2283 static struct view_ops blame_ops;
2284 static struct view_ops blob_ops;
2285 static struct view_ops diff_ops;
2286 static struct view_ops help_ops;
2287 static struct view_ops log_ops;
2288 static struct view_ops main_ops;
2289 static struct view_ops pager_ops;
2290 static struct view_ops stage_ops;
2291 static struct view_ops status_ops;
2292 static struct view_ops tree_ops;
2293 static struct view_ops branch_ops;
2295 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2296         { type, name, #env, ref, ops, map, git, refresh }
2298 #define VIEW_(id, name, ops, git, refresh, ref) \
2299         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2301 static struct view views[] = {
2302         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  TRUE,  ref_head),
2303         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  FALSE, ref_commit),
2304         VIEW_(LOG,    "log",    &log_ops,    TRUE,  TRUE,  ref_head),
2305         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  FALSE, ref_commit),
2306         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  FALSE, ref_blob),
2307         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  FALSE, ref_commit),
2308         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  TRUE,  ref_head),
2309         VIEW_(HELP,   "help",   &help_ops,   FALSE, FALSE, ""),
2310         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, FALSE, "stdin"),
2311         VIEW_(STATUS, "status", &status_ops, TRUE,  TRUE,  ""),
2312         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  TRUE,  ""),
2313 };
2315 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2317 #define foreach_view(view, i) \
2318         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2320 #define view_is_displayed(view) \
2321         (view == display[0] || view == display[1])
2324 /*
2325  * View drawing.
2326  */
2328 static inline void
2329 set_view_attr(struct view *view, enum line_type type)
2331         if (!view->curline->selected && view->curtype != type) {
2332                 (void) wattrset(view->win, get_line_attr(type));
2333                 wchgat(view->win, -1, 0, type, NULL);
2334                 view->curtype = type;
2335         }
2338 static int
2339 draw_chars(struct view *view, enum line_type type, const char *string,
2340            int max_len, bool use_tilde)
2342         static char out_buffer[BUFSIZ * 2];
2343         int len = 0;
2344         int col = 0;
2345         int trimmed = FALSE;
2346         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2348         if (max_len <= 0)
2349                 return 0;
2351         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2353         set_view_attr(view, type);
2354         if (len > 0) {
2355                 if (opt_iconv_out != ICONV_NONE) {
2356                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2357                         size_t inlen = len + 1;
2359                         char *outbuf = out_buffer;
2360                         size_t outlen = sizeof(out_buffer);
2362                         size_t ret;
2364                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2365                         if (ret != (size_t) -1) {
2366                                 string = out_buffer;
2367                                 len = sizeof(out_buffer) - outlen;
2368                         }
2369                 }
2371                 waddnstr(view->win, string, len);
2372         }
2373         if (trimmed && use_tilde) {
2374                 set_view_attr(view, LINE_DELIMITER);
2375                 waddch(view->win, '~');
2376                 col++;
2377         }
2379         return col;
2382 static int
2383 draw_space(struct view *view, enum line_type type, int max, int spaces)
2385         static char space[] = "                    ";
2386         int col = 0;
2388         spaces = MIN(max, spaces);
2390         while (spaces > 0) {
2391                 int len = MIN(spaces, sizeof(space) - 1);
2393                 col += draw_chars(view, type, space, len, FALSE);
2394                 spaces -= len;
2395         }
2397         return col;
2400 static bool
2401 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2403         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2404         return view->width + view->yoffset <= view->col;
2407 static bool
2408 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2410         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2411         int max = view->width + view->yoffset - view->col;
2412         int i;
2414         if (max < size)
2415                 size = max;
2417         set_view_attr(view, type);
2418         /* Using waddch() instead of waddnstr() ensures that
2419          * they'll be rendered correctly for the cursor line. */
2420         for (i = skip; i < size; i++)
2421                 waddch(view->win, graphic[i]);
2423         view->col += size;
2424         if (size < max && skip <= size)
2425                 waddch(view->win, ' ');
2426         view->col++;
2428         return view->width + view->yoffset <= view->col;
2431 static bool
2432 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2434         int max = MIN(view->width + view->yoffset - view->col, len);
2435         int col;
2437         if (text)
2438                 col = draw_chars(view, type, text, max - 1, trim);
2439         else
2440                 col = draw_space(view, type, max - 1, max - 1);
2442         view->col += col;
2443         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2444         return view->width + view->yoffset <= view->col;
2447 static bool
2448 draw_date(struct view *view, struct time *time)
2450         const char *date = mkdate(time, opt_date);
2451         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2453         return draw_field(view, LINE_DATE, date, cols, FALSE);
2456 static bool
2457 draw_author(struct view *view, const char *author)
2459         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2460         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2462         if (abbreviate && author)
2463                 author = get_author_initials(author);
2465         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2468 static bool
2469 draw_mode(struct view *view, mode_t mode)
2471         const char *str;
2473         if (S_ISDIR(mode))
2474                 str = "drwxr-xr-x";
2475         else if (S_ISLNK(mode))
2476                 str = "lrwxrwxrwx";
2477         else if (S_ISGITLINK(mode))
2478                 str = "m---------";
2479         else if (S_ISREG(mode) && mode & S_IXUSR)
2480                 str = "-rwxr-xr-x";
2481         else if (S_ISREG(mode))
2482                 str = "-rw-r--r--";
2483         else
2484                 str = "----------";
2486         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2489 static bool
2490 draw_lineno(struct view *view, unsigned int lineno)
2492         char number[10];
2493         int digits3 = view->digits < 3 ? 3 : view->digits;
2494         int max = MIN(view->width + view->yoffset - view->col, digits3);
2495         char *text = NULL;
2496         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2498         lineno += view->offset + 1;
2499         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2500                 static char fmt[] = "%1ld";
2502                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2503                 if (string_format(number, fmt, lineno))
2504                         text = number;
2505         }
2506         if (text)
2507                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2508         else
2509                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2510         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2513 static bool
2514 draw_view_line(struct view *view, unsigned int lineno)
2516         struct line *line;
2517         bool selected = (view->offset + lineno == view->lineno);
2519         assert(view_is_displayed(view));
2521         if (view->offset + lineno >= view->lines)
2522                 return FALSE;
2524         line = &view->line[view->offset + lineno];
2526         wmove(view->win, lineno, 0);
2527         if (line->cleareol)
2528                 wclrtoeol(view->win);
2529         view->col = 0;
2530         view->curline = line;
2531         view->curtype = LINE_NONE;
2532         line->selected = FALSE;
2533         line->dirty = line->cleareol = 0;
2535         if (selected) {
2536                 set_view_attr(view, LINE_CURSOR);
2537                 line->selected = TRUE;
2538                 view->ops->select(view, line);
2539         }
2541         return view->ops->draw(view, line, lineno);
2544 static void
2545 redraw_view_dirty(struct view *view)
2547         bool dirty = FALSE;
2548         int lineno;
2550         for (lineno = 0; lineno < view->height; lineno++) {
2551                 if (view->offset + lineno >= view->lines)
2552                         break;
2553                 if (!view->line[view->offset + lineno].dirty)
2554                         continue;
2555                 dirty = TRUE;
2556                 if (!draw_view_line(view, lineno))
2557                         break;
2558         }
2560         if (!dirty)
2561                 return;
2562         wnoutrefresh(view->win);
2565 static void
2566 redraw_view_from(struct view *view, int lineno)
2568         assert(0 <= lineno && lineno < view->height);
2570         for (; lineno < view->height; lineno++) {
2571                 if (!draw_view_line(view, lineno))
2572                         break;
2573         }
2575         wnoutrefresh(view->win);
2578 static void
2579 redraw_view(struct view *view)
2581         werase(view->win);
2582         redraw_view_from(view, 0);
2586 static void
2587 update_view_title(struct view *view)
2589         char buf[SIZEOF_STR];
2590         char state[SIZEOF_STR];
2591         size_t bufpos = 0, statelen = 0;
2593         assert(view_is_displayed(view));
2595         if (view->type != VIEW_STATUS && view->lines) {
2596                 unsigned int view_lines = view->offset + view->height;
2597                 unsigned int lines = view->lines
2598                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2599                                    : 0;
2601                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2602                                    view->ops->type,
2603                                    view->lineno + 1,
2604                                    view->lines,
2605                                    lines);
2607         }
2609         if (view->pipe) {
2610                 time_t secs = time(NULL) - view->start_time;
2612                 /* Three git seconds are a long time ... */
2613                 if (secs > 2)
2614                         string_format_from(state, &statelen, " loading %lds", secs);
2615         }
2617         string_format_from(buf, &bufpos, "[%s]", view->name);
2618         if (*view->ref && bufpos < view->width) {
2619                 size_t refsize = strlen(view->ref);
2620                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2622                 if (minsize < view->width)
2623                         refsize = view->width - minsize + 7;
2624                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2625         }
2627         if (statelen && bufpos < view->width) {
2628                 string_format_from(buf, &bufpos, "%s", state);
2629         }
2631         if (view == display[current_view])
2632                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2633         else
2634                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2636         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2637         wclrtoeol(view->title);
2638         wnoutrefresh(view->title);
2641 static int
2642 apply_step(double step, int value)
2644         if (step >= 1)
2645                 return (int) step;
2646         value *= step + 0.01;
2647         return value ? value : 1;
2650 static void
2651 resize_display(void)
2653         int offset, i;
2654         struct view *base = display[0];
2655         struct view *view = display[1] ? display[1] : display[0];
2657         /* Setup window dimensions */
2659         getmaxyx(stdscr, base->height, base->width);
2661         /* Make room for the status window. */
2662         base->height -= 1;
2664         if (view != base) {
2665                 /* Horizontal split. */
2666                 view->width   = base->width;
2667                 view->height  = apply_step(opt_scale_split_view, base->height);
2668                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2669                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2670                 base->height -= view->height;
2672                 /* Make room for the title bar. */
2673                 view->height -= 1;
2674         }
2676         /* Make room for the title bar. */
2677         base->height -= 1;
2679         offset = 0;
2681         foreach_displayed_view (view, i) {
2682                 if (!view->win) {
2683                         view->win = newwin(view->height, 0, offset, 0);
2684                         if (!view->win)
2685                                 die("Failed to create %s view", view->name);
2687                         scrollok(view->win, FALSE);
2689                         view->title = newwin(1, 0, offset + view->height, 0);
2690                         if (!view->title)
2691                                 die("Failed to create title window");
2693                 } else {
2694                         wresize(view->win, view->height, view->width);
2695                         mvwin(view->win,   offset, 0);
2696                         mvwin(view->title, offset + view->height, 0);
2697                 }
2699                 offset += view->height + 1;
2700         }
2703 static void
2704 redraw_display(bool clear)
2706         struct view *view;
2707         int i;
2709         foreach_displayed_view (view, i) {
2710                 if (clear)
2711                         wclear(view->win);
2712                 redraw_view(view);
2713                 update_view_title(view);
2714         }
2718 /*
2719  * Option management
2720  */
2722 static void
2723 toggle_enum_option_do(unsigned int *opt, const char *help,
2724                       const struct enum_map *map, size_t size)
2726         *opt = (*opt + 1) % size;
2727         redraw_display(FALSE);
2728         report("Displaying %s %s", enum_name(map[*opt]), help);
2731 #define toggle_enum_option(opt, help, map) \
2732         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2734 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2735 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2737 static void
2738 toggle_view_option(bool *option, const char *help)
2740         *option = !*option;
2741         redraw_display(FALSE);
2742         report("%sabling %s", *option ? "En" : "Dis", help);
2745 static void
2746 open_option_menu(void)
2748         const struct menu_item menu[] = {
2749                 { '.', "line numbers", &opt_line_number },
2750                 { 'D', "date display", &opt_date },
2751                 { 'A', "author display", &opt_author },
2752                 { 'g', "revision graph display", &opt_rev_graph },
2753                 { 'F', "reference display", &opt_show_refs },
2754                 { 0 }
2755         };
2756         int selected = 0;
2758         if (prompt_menu("Toggle option", menu, &selected)) {
2759                 if (menu[selected].data == &opt_date)
2760                         toggle_date();
2761                 else if (menu[selected].data == &opt_author)
2762                         toggle_author();
2763                 else
2764                         toggle_view_option(menu[selected].data, menu[selected].text);
2765         }
2768 static void
2769 maximize_view(struct view *view)
2771         memset(display, 0, sizeof(display));
2772         current_view = 0;
2773         display[current_view] = view;
2774         resize_display();
2775         redraw_display(FALSE);
2776         report("");
2780 /*
2781  * Navigation
2782  */
2784 static bool
2785 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2787         if (lineno >= view->lines)
2788                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2790         if (offset > lineno || offset + view->height <= lineno) {
2791                 unsigned long half = view->height / 2;
2793                 if (lineno > half)
2794                         offset = lineno - half;
2795                 else
2796                         offset = 0;
2797         }
2799         if (offset != view->offset || lineno != view->lineno) {
2800                 view->offset = offset;
2801                 view->lineno = lineno;
2802                 return TRUE;
2803         }
2805         return FALSE;
2808 /* Scrolling backend */
2809 static void
2810 do_scroll_view(struct view *view, int lines)
2812         bool redraw_current_line = FALSE;
2814         /* The rendering expects the new offset. */
2815         view->offset += lines;
2817         assert(0 <= view->offset && view->offset < view->lines);
2818         assert(lines);
2820         /* Move current line into the view. */
2821         if (view->lineno < view->offset) {
2822                 view->lineno = view->offset;
2823                 redraw_current_line = TRUE;
2824         } else if (view->lineno >= view->offset + view->height) {
2825                 view->lineno = view->offset + view->height - 1;
2826                 redraw_current_line = TRUE;
2827         }
2829         assert(view->offset <= view->lineno && view->lineno < view->lines);
2831         /* Redraw the whole screen if scrolling is pointless. */
2832         if (view->height < ABS(lines)) {
2833                 redraw_view(view);
2835         } else {
2836                 int line = lines > 0 ? view->height - lines : 0;
2837                 int end = line + ABS(lines);
2839                 scrollok(view->win, TRUE);
2840                 wscrl(view->win, lines);
2841                 scrollok(view->win, FALSE);
2843                 while (line < end && draw_view_line(view, line))
2844                         line++;
2846                 if (redraw_current_line)
2847                         draw_view_line(view, view->lineno - view->offset);
2848                 wnoutrefresh(view->win);
2849         }
2851         view->has_scrolled = TRUE;
2852         report("");
2855 /* Scroll frontend */
2856 static void
2857 scroll_view(struct view *view, enum request request)
2859         int lines = 1;
2861         assert(view_is_displayed(view));
2863         switch (request) {
2864         case REQ_SCROLL_LEFT:
2865                 if (view->yoffset == 0) {
2866                         report("Cannot scroll beyond the first column");
2867                         return;
2868                 }
2869                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2870                         view->yoffset = 0;
2871                 else
2872                         view->yoffset -= apply_step(opt_hscroll, view->width);
2873                 redraw_view_from(view, 0);
2874                 report("");
2875                 return;
2876         case REQ_SCROLL_RIGHT:
2877                 view->yoffset += apply_step(opt_hscroll, view->width);
2878                 redraw_view(view);
2879                 report("");
2880                 return;
2881         case REQ_SCROLL_PAGE_DOWN:
2882                 lines = view->height;
2883         case REQ_SCROLL_LINE_DOWN:
2884                 if (view->offset + lines > view->lines)
2885                         lines = view->lines - view->offset;
2887                 if (lines == 0 || view->offset + view->height >= view->lines) {
2888                         report("Cannot scroll beyond the last line");
2889                         return;
2890                 }
2891                 break;
2893         case REQ_SCROLL_PAGE_UP:
2894                 lines = view->height;
2895         case REQ_SCROLL_LINE_UP:
2896                 if (lines > view->offset)
2897                         lines = view->offset;
2899                 if (lines == 0) {
2900                         report("Cannot scroll beyond the first line");
2901                         return;
2902                 }
2904                 lines = -lines;
2905                 break;
2907         default:
2908                 die("request %d not handled in switch", request);
2909         }
2911         do_scroll_view(view, lines);
2914 /* Cursor moving */
2915 static void
2916 move_view(struct view *view, enum request request)
2918         int scroll_steps = 0;
2919         int steps;
2921         switch (request) {
2922         case REQ_MOVE_FIRST_LINE:
2923                 steps = -view->lineno;
2924                 break;
2926         case REQ_MOVE_LAST_LINE:
2927                 steps = view->lines - view->lineno - 1;
2928                 break;
2930         case REQ_MOVE_PAGE_UP:
2931                 steps = view->height > view->lineno
2932                       ? -view->lineno : -view->height;
2933                 break;
2935         case REQ_MOVE_PAGE_DOWN:
2936                 steps = view->lineno + view->height >= view->lines
2937                       ? view->lines - view->lineno - 1 : view->height;
2938                 break;
2940         case REQ_MOVE_UP:
2941                 steps = -1;
2942                 break;
2944         case REQ_MOVE_DOWN:
2945                 steps = 1;
2946                 break;
2948         default:
2949                 die("request %d not handled in switch", request);
2950         }
2952         if (steps <= 0 && view->lineno == 0) {
2953                 report("Cannot move beyond the first line");
2954                 return;
2956         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2957                 report("Cannot move beyond the last line");
2958                 return;
2959         }
2961         /* Move the current line */
2962         view->lineno += steps;
2963         assert(0 <= view->lineno && view->lineno < view->lines);
2965         /* Check whether the view needs to be scrolled */
2966         if (view->lineno < view->offset ||
2967             view->lineno >= view->offset + view->height) {
2968                 scroll_steps = steps;
2969                 if (steps < 0 && -steps > view->offset) {
2970                         scroll_steps = -view->offset;
2972                 } else if (steps > 0) {
2973                         if (view->lineno == view->lines - 1 &&
2974                             view->lines > view->height) {
2975                                 scroll_steps = view->lines - view->offset - 1;
2976                                 if (scroll_steps >= view->height)
2977                                         scroll_steps -= view->height - 1;
2978                         }
2979                 }
2980         }
2982         if (!view_is_displayed(view)) {
2983                 view->offset += scroll_steps;
2984                 assert(0 <= view->offset && view->offset < view->lines);
2985                 view->ops->select(view, &view->line[view->lineno]);
2986                 return;
2987         }
2989         /* Repaint the old "current" line if we be scrolling */
2990         if (ABS(steps) < view->height)
2991                 draw_view_line(view, view->lineno - steps - view->offset);
2993         if (scroll_steps) {
2994                 do_scroll_view(view, scroll_steps);
2995                 return;
2996         }
2998         /* Draw the current line */
2999         draw_view_line(view, view->lineno - view->offset);
3001         wnoutrefresh(view->win);
3002         report("");
3006 /*
3007  * Searching
3008  */
3010 static void search_view(struct view *view, enum request request);
3012 static bool
3013 grep_text(struct view *view, const char *text[])
3015         regmatch_t pmatch;
3016         size_t i;
3018         for (i = 0; text[i]; i++)
3019                 if (*text[i] &&
3020                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3021                         return TRUE;
3022         return FALSE;
3025 static void
3026 select_view_line(struct view *view, unsigned long lineno)
3028         unsigned long old_lineno = view->lineno;
3029         unsigned long old_offset = view->offset;
3031         if (goto_view_line(view, view->offset, lineno)) {
3032                 if (view_is_displayed(view)) {
3033                         if (old_offset != view->offset) {
3034                                 redraw_view(view);
3035                         } else {
3036                                 draw_view_line(view, old_lineno - view->offset);
3037                                 draw_view_line(view, view->lineno - view->offset);
3038                                 wnoutrefresh(view->win);
3039                         }
3040                 } else {
3041                         view->ops->select(view, &view->line[view->lineno]);
3042                 }
3043         }
3046 static void
3047 find_next(struct view *view, enum request request)
3049         unsigned long lineno = view->lineno;
3050         int direction;
3052         if (!*view->grep) {
3053                 if (!*opt_search)
3054                         report("No previous search");
3055                 else
3056                         search_view(view, request);
3057                 return;
3058         }
3060         switch (request) {
3061         case REQ_SEARCH:
3062         case REQ_FIND_NEXT:
3063                 direction = 1;
3064                 break;
3066         case REQ_SEARCH_BACK:
3067         case REQ_FIND_PREV:
3068                 direction = -1;
3069                 break;
3071         default:
3072                 return;
3073         }
3075         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3076                 lineno += direction;
3078         /* Note, lineno is unsigned long so will wrap around in which case it
3079          * will become bigger than view->lines. */
3080         for (; lineno < view->lines; lineno += direction) {
3081                 if (view->ops->grep(view, &view->line[lineno])) {
3082                         select_view_line(view, lineno);
3083                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3084                         return;
3085                 }
3086         }
3088         report("No match found for '%s'", view->grep);
3091 static void
3092 search_view(struct view *view, enum request request)
3094         int regex_err;
3096         if (view->regex) {
3097                 regfree(view->regex);
3098                 *view->grep = 0;
3099         } else {
3100                 view->regex = calloc(1, sizeof(*view->regex));
3101                 if (!view->regex)
3102                         return;
3103         }
3105         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3106         if (regex_err != 0) {
3107                 char buf[SIZEOF_STR] = "unknown error";
3109                 regerror(regex_err, view->regex, buf, sizeof(buf));
3110                 report("Search failed: %s", buf);
3111                 return;
3112         }
3114         string_copy(view->grep, opt_search);
3116         find_next(view, request);
3119 /*
3120  * Incremental updating
3121  */
3123 static void
3124 reset_view(struct view *view)
3126         int i;
3128         for (i = 0; i < view->lines; i++)
3129                 free(view->line[i].data);
3130         free(view->line);
3132         view->p_offset = view->offset;
3133         view->p_yoffset = view->yoffset;
3134         view->p_lineno = view->lineno;
3136         view->line = NULL;
3137         view->offset = 0;
3138         view->yoffset = 0;
3139         view->lines  = 0;
3140         view->lineno = 0;
3141         view->vid[0] = 0;
3142         view->update_secs = 0;
3145 static void
3146 free_argv(const char *argv[])
3148         int argc;
3150         for (argc = 0; argv[argc]; argc++)
3151                 free((void *) argv[argc]);
3154 static const char *
3155 format_arg(const char *name)
3157         static struct {
3158                 const char *name;
3159                 size_t namelen;
3160                 const char *value;
3161                 const char *value_if_empty;
3162         } vars[] = {
3163 #define FORMAT_VAR(name, value, value_if_empty) \
3164         { name, STRING_SIZE(name), value, value_if_empty }
3165                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3166                 FORMAT_VAR("%(file)",           opt_file,       ""),
3167                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3168                 FORMAT_VAR("%(head)",           ref_head,       ""),
3169                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3170                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3171                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3172         };
3173         int i;
3175         for (i = 0; i < ARRAY_SIZE(vars); i++)
3176                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3177                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3179         report("Unknown replacement: `%s`", name);
3180         return NULL;
3183 static bool
3184 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3186         char buf[SIZEOF_STR];
3187         int argc;
3188         bool noreplace = flags == FORMAT_NONE;
3190         free_argv(dst_argv);
3192         for (argc = 0; src_argv[argc]; argc++) {
3193                 const char *arg = src_argv[argc];
3194                 size_t bufpos = 0;
3196                 while (arg) {
3197                         char *next = strstr(arg, "%(");
3198                         int len = next - arg;
3199                         const char *value;
3201                         if (!next || noreplace) {
3202                                 len = strlen(arg);
3203                                 value = "";
3205                         } else {
3206                                 value = format_arg(next);
3208                                 if (!value) {
3209                                         return FALSE;
3210                                 }
3211                         }
3213                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3214                                 return FALSE;
3216                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3217                 }
3219                 dst_argv[argc] = strdup(buf);
3220                 if (!dst_argv[argc])
3221                         break;
3222         }
3224         dst_argv[argc] = NULL;
3226         return src_argv[argc] == NULL;
3229 static bool
3230 restore_view_position(struct view *view)
3232         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3233                 return FALSE;
3235         /* Changing the view position cancels the restoring. */
3236         /* FIXME: Changing back to the first line is not detected. */
3237         if (view->offset != 0 || view->lineno != 0) {
3238                 view->p_restore = FALSE;
3239                 return FALSE;
3240         }
3242         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3243             view_is_displayed(view))
3244                 werase(view->win);
3246         view->yoffset = view->p_yoffset;
3247         view->p_restore = FALSE;
3249         return TRUE;
3252 static void
3253 end_update(struct view *view, bool force)
3255         if (!view->pipe)
3256                 return;
3257         while (!view->ops->read(view, NULL))
3258                 if (!force)
3259                         return;
3260         if (force)
3261                 io_kill(view->pipe);
3262         io_done(view->pipe);
3263         view->pipe = NULL;
3266 static void
3267 setup_update(struct view *view, const char *vid)
3269         reset_view(view);
3270         string_copy_rev(view->vid, vid);
3271         view->pipe = &view->io;
3272         view->start_time = time(NULL);
3275 static bool
3276 prepare_update(struct view *view, const char *argv[], const char *dir)
3278         if (view->pipe)
3279                 end_update(view, TRUE);
3280         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3283 static bool
3284 prepare_update_file(struct view *view, const char *name)
3286         if (view->pipe)
3287                 end_update(view, TRUE);
3288         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3291 static bool
3292 begin_update(struct view *view, bool refresh)
3294         if (view->pipe)
3295                 end_update(view, TRUE);
3297         if (!refresh) {
3298                 if (view->ops->prepare) {
3299                         if (!view->ops->prepare(view))
3300                                 return FALSE;
3301                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3302                         return FALSE;
3303                 }
3305                 /* Put the current ref_* value to the view title ref
3306                  * member. This is needed by the blob view. Most other
3307                  * views sets it automatically after loading because the
3308                  * first line is a commit line. */
3309                 string_copy_rev(view->ref, view->id);
3310         }
3312         if (!io_start(&view->io))
3313                 return FALSE;
3315         setup_update(view, view->id);
3317         return TRUE;
3320 static bool
3321 update_view(struct view *view)
3323         char out_buffer[BUFSIZ * 2];
3324         char *line;
3325         /* Clear the view and redraw everything since the tree sorting
3326          * might have rearranged things. */
3327         bool redraw = view->lines == 0;
3328         bool can_read = TRUE;
3330         if (!view->pipe)
3331                 return TRUE;
3333         if (!io_can_read(view->pipe)) {
3334                 if (view->lines == 0 && view_is_displayed(view)) {
3335                         time_t secs = time(NULL) - view->start_time;
3337                         if (secs > 1 && secs > view->update_secs) {
3338                                 if (view->update_secs == 0)
3339                                         redraw_view(view);
3340                                 update_view_title(view);
3341                                 view->update_secs = secs;
3342                         }
3343                 }
3344                 return TRUE;
3345         }
3347         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3348                 if (opt_iconv_in != ICONV_NONE) {
3349                         ICONV_CONST char *inbuf = line;
3350                         size_t inlen = strlen(line) + 1;
3352                         char *outbuf = out_buffer;
3353                         size_t outlen = sizeof(out_buffer);
3355                         size_t ret;
3357                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3358                         if (ret != (size_t) -1)
3359                                 line = out_buffer;
3360                 }
3362                 if (!view->ops->read(view, line)) {
3363                         report("Allocation failure");
3364                         end_update(view, TRUE);
3365                         return FALSE;
3366                 }
3367         }
3369         {
3370                 unsigned long lines = view->lines;
3371                 int digits;
3373                 for (digits = 0; lines; digits++)
3374                         lines /= 10;
3376                 /* Keep the displayed view in sync with line number scaling. */
3377                 if (digits != view->digits) {
3378                         view->digits = digits;
3379                         if (opt_line_number || view->type == VIEW_BLAME)
3380                                 redraw = TRUE;
3381                 }
3382         }
3384         if (io_error(view->pipe)) {
3385                 report("Failed to read: %s", io_strerror(view->pipe));
3386                 end_update(view, TRUE);
3388         } else if (io_eof(view->pipe)) {
3389                 if (view_is_displayed(view))
3390                         report("");
3391                 end_update(view, FALSE);
3392         }
3394         if (restore_view_position(view))
3395                 redraw = TRUE;
3397         if (!view_is_displayed(view))
3398                 return TRUE;
3400         if (redraw)
3401                 redraw_view_from(view, 0);
3402         else
3403                 redraw_view_dirty(view);
3405         /* Update the title _after_ the redraw so that if the redraw picks up a
3406          * commit reference in view->ref it'll be available here. */
3407         update_view_title(view);
3408         return TRUE;
3411 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3413 static struct line *
3414 add_line_data(struct view *view, void *data, enum line_type type)
3416         struct line *line;
3418         if (!realloc_lines(&view->line, view->lines, 1))
3419                 return NULL;
3421         line = &view->line[view->lines++];
3422         memset(line, 0, sizeof(*line));
3423         line->type = type;
3424         line->data = data;
3425         line->dirty = 1;
3427         return line;
3430 static struct line *
3431 add_line_text(struct view *view, const char *text, enum line_type type)
3433         char *data = text ? strdup(text) : NULL;
3435         return data ? add_line_data(view, data, type) : NULL;
3438 static struct line *
3439 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3441         char buf[SIZEOF_STR];
3442         va_list args;
3444         va_start(args, fmt);
3445         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3446                 buf[0] = 0;
3447         va_end(args);
3449         return buf[0] ? add_line_text(view, buf, type) : NULL;
3452 /*
3453  * View opening
3454  */
3456 enum open_flags {
3457         OPEN_DEFAULT = 0,       /* Use default view switching. */
3458         OPEN_SPLIT = 1,         /* Split current view. */
3459         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3460         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3461         OPEN_PREPARED = 32,     /* Open already prepared command. */
3462 };
3464 static void
3465 open_view(struct view *prev, enum request request, enum open_flags flags)
3467         bool split = !!(flags & OPEN_SPLIT);
3468         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3469         bool nomaximize = !!(flags & OPEN_REFRESH);
3470         struct view *view = VIEW(request);
3471         int nviews = displayed_views();
3472         struct view *base_view = display[0];
3474         if (view == prev && nviews == 1 && !reload) {
3475                 report("Already in %s view", view->name);
3476                 return;
3477         }
3479         if (view->git_dir && !opt_git_dir[0]) {
3480                 report("The %s view is disabled in pager view", view->name);
3481                 return;
3482         }
3484         if (split) {
3485                 display[1] = view;
3486                 current_view = 1;
3487                 view->parent = prev;
3488         } else if (!nomaximize) {
3489                 /* Maximize the current view. */
3490                 memset(display, 0, sizeof(display));
3491                 current_view = 0;
3492                 display[current_view] = view;
3493         }
3495         /* No prev signals that this is the first loaded view. */
3496         if (prev && view != prev) {
3497                 view->prev = prev;
3498         }
3500         /* Resize the view when switching between split- and full-screen,
3501          * or when switching between two different full-screen views. */
3502         if (nviews != displayed_views() ||
3503             (nviews == 1 && base_view != display[0]))
3504                 resize_display();
3506         if (view->ops->open) {
3507                 if (view->pipe)
3508                         end_update(view, TRUE);
3509                 if (!view->ops->open(view)) {
3510                         report("Failed to load %s view", view->name);
3511                         return;
3512                 }
3513                 restore_view_position(view);
3515         } else if ((reload || strcmp(view->vid, view->id)) &&
3516                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3517                 report("Failed to load %s view", view->name);
3518                 return;
3519         }
3521         if (split && prev->lineno - prev->offset >= prev->height) {
3522                 /* Take the title line into account. */
3523                 int lines = prev->lineno - prev->offset - prev->height + 1;
3525                 /* Scroll the view that was split if the current line is
3526                  * outside the new limited view. */
3527                 do_scroll_view(prev, lines);
3528         }
3530         if (prev && view != prev && split && view_is_displayed(prev)) {
3531                 /* "Blur" the previous view. */
3532                 update_view_title(prev);
3533         }
3535         if (view->pipe && view->lines == 0) {
3536                 /* Clear the old view and let the incremental updating refill
3537                  * the screen. */
3538                 werase(view->win);
3539                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3540                 report("");
3541         } else if (view_is_displayed(view)) {
3542                 redraw_view(view);
3543                 report("");
3544         }
3547 static void
3548 open_external_viewer(const char *argv[], const char *dir)
3550         def_prog_mode();           /* save current tty modes */
3551         endwin();                  /* restore original tty modes */
3552         io_run_fg(argv, dir);
3553         fprintf(stderr, "Press Enter to continue");
3554         getc(opt_tty);
3555         reset_prog_mode();
3556         redraw_display(TRUE);
3559 static void
3560 open_mergetool(const char *file)
3562         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3564         open_external_viewer(mergetool_argv, opt_cdup);
3567 static void
3568 open_editor(const char *file)
3570         const char *editor_argv[] = { "vi", file, NULL };
3571         const char *editor;
3573         editor = getenv("GIT_EDITOR");
3574         if (!editor && *opt_editor)
3575                 editor = opt_editor;
3576         if (!editor)
3577                 editor = getenv("VISUAL");
3578         if (!editor)
3579                 editor = getenv("EDITOR");
3580         if (!editor)
3581                 editor = "vi";
3583         editor_argv[0] = editor;
3584         open_external_viewer(editor_argv, opt_cdup);
3587 static void
3588 open_run_request(enum request request)
3590         struct run_request *req = get_run_request(request);
3591         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3593         if (!req) {
3594                 report("Unknown run request");
3595                 return;
3596         }
3598         if (format_argv(argv, req->argv, FORMAT_ALL))
3599                 open_external_viewer(argv, NULL);
3600         free_argv(argv);
3603 /*
3604  * User request switch noodle
3605  */
3607 static int
3608 view_driver(struct view *view, enum request request)
3610         int i;
3612         if (request == REQ_NONE)
3613                 return TRUE;
3615         if (request > REQ_NONE) {
3616                 open_run_request(request);
3617                 /* FIXME: When all views can refresh always do this. */
3618                 if (view->refresh)
3619                         request = REQ_REFRESH;
3620                 else
3621                         return TRUE;
3622         }
3624         if (view && view->lines) {
3625                 request = view->ops->request(view, request, &view->line[view->lineno]);
3626                 if (request == REQ_NONE)
3627                         return TRUE;
3628         }
3630         switch (request) {
3631         case REQ_MOVE_UP:
3632         case REQ_MOVE_DOWN:
3633         case REQ_MOVE_PAGE_UP:
3634         case REQ_MOVE_PAGE_DOWN:
3635         case REQ_MOVE_FIRST_LINE:
3636         case REQ_MOVE_LAST_LINE:
3637                 move_view(view, request);
3638                 break;
3640         case REQ_SCROLL_LEFT:
3641         case REQ_SCROLL_RIGHT:
3642         case REQ_SCROLL_LINE_DOWN:
3643         case REQ_SCROLL_LINE_UP:
3644         case REQ_SCROLL_PAGE_DOWN:
3645         case REQ_SCROLL_PAGE_UP:
3646                 scroll_view(view, request);
3647                 break;
3649         case REQ_VIEW_BLAME:
3650                 if (!opt_file[0]) {
3651                         report("No file chosen, press %s to open tree view",
3652                                get_key(view->keymap, REQ_VIEW_TREE));
3653                         break;
3654                 }
3655                 open_view(view, request, OPEN_DEFAULT);
3656                 break;
3658         case REQ_VIEW_BLOB:
3659                 if (!ref_blob[0]) {
3660                         report("No file chosen, press %s to open tree view",
3661                                get_key(view->keymap, REQ_VIEW_TREE));
3662                         break;
3663                 }
3664                 open_view(view, request, OPEN_DEFAULT);
3665                 break;
3667         case REQ_VIEW_PAGER:
3668                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3669                         report("No pager content, press %s to run command from prompt",
3670                                get_key(view->keymap, REQ_PROMPT));
3671                         break;
3672                 }
3673                 open_view(view, request, OPEN_DEFAULT);
3674                 break;
3676         case REQ_VIEW_STAGE:
3677                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3678                         report("No stage content, press %s to open the status view and choose file",
3679                                get_key(view->keymap, REQ_VIEW_STATUS));
3680                         break;
3681                 }
3682                 open_view(view, request, OPEN_DEFAULT);
3683                 break;
3685         case REQ_VIEW_STATUS:
3686                 if (opt_is_inside_work_tree == FALSE) {
3687                         report("The status view requires a working tree");
3688                         break;
3689                 }
3690                 open_view(view, request, OPEN_DEFAULT);
3691                 break;
3693         case REQ_VIEW_MAIN:
3694         case REQ_VIEW_DIFF:
3695         case REQ_VIEW_LOG:
3696         case REQ_VIEW_TREE:
3697         case REQ_VIEW_HELP:
3698         case REQ_VIEW_BRANCH:
3699                 open_view(view, request, OPEN_DEFAULT);
3700                 break;
3702         case REQ_NEXT:
3703         case REQ_PREVIOUS:
3704                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3706                 if (view->parent) {
3707                         int line;
3709                         view = view->parent;
3710                         line = view->lineno;
3711                         move_view(view, request);
3712                         if (view_is_displayed(view))
3713                                 update_view_title(view);
3714                         if (line != view->lineno)
3715                                 view->ops->request(view, REQ_ENTER,
3716                                                    &view->line[view->lineno]);
3718                 } else {
3719                         move_view(view, request);
3720                 }
3721                 break;
3723         case REQ_VIEW_NEXT:
3724         {
3725                 int nviews = displayed_views();
3726                 int next_view = (current_view + 1) % nviews;
3728                 if (next_view == current_view) {
3729                         report("Only one view is displayed");
3730                         break;
3731                 }
3733                 current_view = next_view;
3734                 /* Blur out the title of the previous view. */
3735                 update_view_title(view);
3736                 report("");
3737                 break;
3738         }
3739         case REQ_REFRESH:
3740                 report("Refreshing is not yet supported for the %s view", view->name);
3741                 break;
3743         case REQ_MAXIMIZE:
3744                 if (displayed_views() == 2)
3745                         maximize_view(view);
3746                 break;
3748         case REQ_OPTIONS:
3749                 open_option_menu();
3750                 break;
3752         case REQ_TOGGLE_LINENO:
3753                 toggle_view_option(&opt_line_number, "line numbers");
3754                 break;
3756         case REQ_TOGGLE_DATE:
3757                 toggle_date();
3758                 break;
3760         case REQ_TOGGLE_AUTHOR:
3761                 toggle_author();
3762                 break;
3764         case REQ_TOGGLE_REV_GRAPH:
3765                 toggle_view_option(&opt_rev_graph, "revision graph display");
3766                 break;
3768         case REQ_TOGGLE_REFS:
3769                 toggle_view_option(&opt_show_refs, "reference display");
3770                 break;
3772         case REQ_TOGGLE_SORT_FIELD:
3773         case REQ_TOGGLE_SORT_ORDER:
3774                 report("Sorting is not yet supported for the %s view", view->name);
3775                 break;
3777         case REQ_SEARCH:
3778         case REQ_SEARCH_BACK:
3779                 search_view(view, request);
3780                 break;
3782         case REQ_FIND_NEXT:
3783         case REQ_FIND_PREV:
3784                 find_next(view, request);
3785                 break;
3787         case REQ_STOP_LOADING:
3788                 foreach_view(view, i) {
3789                         if (view->pipe)
3790                                 report("Stopped loading the %s view", view->name),
3791                         end_update(view, TRUE);
3792                 }
3793                 break;
3795         case REQ_SHOW_VERSION:
3796                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3797                 return TRUE;
3799         case REQ_SCREEN_REDRAW:
3800                 redraw_display(TRUE);
3801                 break;
3803         case REQ_EDIT:
3804                 report("Nothing to edit");
3805                 break;
3807         case REQ_ENTER:
3808                 report("Nothing to enter");
3809                 break;
3811         case REQ_VIEW_CLOSE:
3812                 /* XXX: Mark closed views by letting view->prev point to the
3813                  * view itself. Parents to closed view should never be
3814                  * followed. */
3815                 if (view->prev && view->prev != view) {
3816                         maximize_view(view->prev);
3817                         view->prev = view;
3818                         break;
3819                 }
3820                 /* Fall-through */
3821         case REQ_QUIT:
3822                 return FALSE;
3824         default:
3825                 report("Unknown key, press %s for help",
3826                        get_key(view->keymap, REQ_VIEW_HELP));
3827                 return TRUE;
3828         }
3830         return TRUE;
3834 /*
3835  * View backend utilities
3836  */
3838 enum sort_field {
3839         ORDERBY_NAME,
3840         ORDERBY_DATE,
3841         ORDERBY_AUTHOR,
3842 };
3844 struct sort_state {
3845         const enum sort_field *fields;
3846         size_t size, current;
3847         bool reverse;
3848 };
3850 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3851 #define get_sort_field(state) ((state).fields[(state).current])
3852 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3854 static void
3855 sort_view(struct view *view, enum request request, struct sort_state *state,
3856           int (*compare)(const void *, const void *))
3858         switch (request) {
3859         case REQ_TOGGLE_SORT_FIELD:
3860                 state->current = (state->current + 1) % state->size;
3861                 break;
3863         case REQ_TOGGLE_SORT_ORDER:
3864                 state->reverse = !state->reverse;
3865                 break;
3866         default:
3867                 die("Not a sort request");
3868         }
3870         qsort(view->line, view->lines, sizeof(*view->line), compare);
3871         redraw_view(view);
3874 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3876 /* Small author cache to reduce memory consumption. It uses binary
3877  * search to lookup or find place to position new entries. No entries
3878  * are ever freed. */
3879 static const char *
3880 get_author(const char *name)
3882         static const char **authors;
3883         static size_t authors_size;
3884         int from = 0, to = authors_size - 1;
3886         while (from <= to) {
3887                 size_t pos = (to + from) / 2;
3888                 int cmp = strcmp(name, authors[pos]);
3890                 if (!cmp)
3891                         return authors[pos];
3893                 if (cmp < 0)
3894                         to = pos - 1;
3895                 else
3896                         from = pos + 1;
3897         }
3899         if (!realloc_authors(&authors, authors_size, 1))
3900                 return NULL;
3901         name = strdup(name);
3902         if (!name)
3903                 return NULL;
3905         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3906         authors[from] = name;
3907         authors_size++;
3909         return name;
3912 static void
3913 parse_timesec(struct time *time, const char *sec)
3915         time->sec = (time_t) atol(sec);
3918 static void
3919 parse_timezone(struct time *time, const char *zone)
3921         long tz;
3923         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3924         tz += ('0' - zone[2]) * 60 * 60;
3925         tz += ('0' - zone[3]) * 60 * 10;
3926         tz += ('0' - zone[4]) * 60;
3928         if (zone[0] == '-')
3929                 tz = -tz;
3931         time->tz = tz;
3932         time->sec -= tz;
3935 /* Parse author lines where the name may be empty:
3936  *      author  <email@address.tld> 1138474660 +0100
3937  */
3938 static void
3939 parse_author_line(char *ident, const char **author, struct time *time)
3941         char *nameend = strchr(ident, '<');
3942         char *emailend = strchr(ident, '>');
3944         if (nameend && emailend)
3945                 *nameend = *emailend = 0;
3946         ident = chomp_string(ident);
3947         if (!*ident) {
3948                 if (nameend)
3949                         ident = chomp_string(nameend + 1);
3950                 if (!*ident)
3951                         ident = "Unknown";
3952         }
3954         *author = get_author(ident);
3956         /* Parse epoch and timezone */
3957         if (emailend && emailend[1] == ' ') {
3958                 char *secs = emailend + 2;
3959                 char *zone = strchr(secs, ' ');
3961                 parse_timesec(time, secs);
3963                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3964                         parse_timezone(time, zone + 1);
3965         }
3968 static bool
3969 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3971         char rev[SIZEOF_REV];
3972         const char *revlist_argv[] = {
3973                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3974         };
3975         struct menu_item *items;
3976         char text[SIZEOF_STR];
3977         bool ok = TRUE;
3978         int i;
3980         items = calloc(*parents + 1, sizeof(*items));
3981         if (!items)
3982                 return FALSE;
3984         for (i = 0; i < *parents; i++) {
3985                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3986                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3987                     !(items[i].text = strdup(text))) {
3988                         ok = FALSE;
3989                         break;
3990                 }
3991         }
3993         if (ok) {
3994                 *parents = 0;
3995                 ok = prompt_menu("Select parent", items, parents);
3996         }
3997         for (i = 0; items[i].text; i++)
3998                 free((char *) items[i].text);
3999         free(items);
4000         return ok;
4003 static bool
4004 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4006         char buf[SIZEOF_STR * 4];
4007         const char *revlist_argv[] = {
4008                 "git", "log", "--no-color", "-1",
4009                         "--pretty=format:%P", id, "--", path, NULL
4010         };
4011         int parents;
4013         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4014             (parents = strlen(buf) / 40) < 0) {
4015                 report("Failed to get parent information");
4016                 return FALSE;
4018         } else if (parents == 0) {
4019                 if (path)
4020                         report("Path '%s' does not exist in the parent", path);
4021                 else
4022                         report("The selected commit has no parents");
4023                 return FALSE;
4024         }
4026         if (parents == 1)
4027                 parents = 0;
4028         else if (!open_commit_parent_menu(buf, &parents))
4029                 return FALSE;
4031         string_copy_rev(rev, &buf[41 * parents]);
4032         return TRUE;
4035 /*
4036  * Pager backend
4037  */
4039 static bool
4040 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4042         char text[SIZEOF_STR];
4044         if (opt_line_number && draw_lineno(view, lineno))
4045                 return TRUE;
4047         string_expand(text, sizeof(text), line->data, opt_tab_size);
4048         draw_text(view, line->type, text, TRUE);
4049         return TRUE;
4052 static bool
4053 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4055         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4056         char ref[SIZEOF_STR];
4058         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4059                 return TRUE;
4061         /* This is the only fatal call, since it can "corrupt" the buffer. */
4062         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4063                 return FALSE;
4065         return TRUE;
4068 static void
4069 add_pager_refs(struct view *view, struct line *line)
4071         char buf[SIZEOF_STR];
4072         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4073         struct ref_list *list;
4074         size_t bufpos = 0, i;
4075         const char *sep = "Refs: ";
4076         bool is_tag = FALSE;
4078         assert(line->type == LINE_COMMIT);
4080         list = get_ref_list(commit_id);
4081         if (!list) {
4082                 if (view->type == VIEW_DIFF)
4083                         goto try_add_describe_ref;
4084                 return;
4085         }
4087         for (i = 0; i < list->size; i++) {
4088                 struct ref *ref = list->refs[i];
4089                 const char *fmt = ref->tag    ? "%s[%s]" :
4090                                   ref->remote ? "%s<%s>" : "%s%s";
4092                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4093                         return;
4094                 sep = ", ";
4095                 if (ref->tag)
4096                         is_tag = TRUE;
4097         }
4099         if (!is_tag && view->type == VIEW_DIFF) {
4100 try_add_describe_ref:
4101                 /* Add <tag>-g<commit_id> "fake" reference. */
4102                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4103                         return;
4104         }
4106         if (bufpos == 0)
4107                 return;
4109         add_line_text(view, buf, LINE_PP_REFS);
4112 static bool
4113 pager_read(struct view *view, char *data)
4115         struct line *line;
4117         if (!data)
4118                 return TRUE;
4120         line = add_line_text(view, data, get_line_type(data));
4121         if (!line)
4122                 return FALSE;
4124         if (line->type == LINE_COMMIT &&
4125             (view->type == VIEW_DIFF ||
4126              view->type == VIEW_LOG))
4127                 add_pager_refs(view, line);
4129         return TRUE;
4132 static enum request
4133 pager_request(struct view *view, enum request request, struct line *line)
4135         int split = 0;
4137         if (request != REQ_ENTER)
4138                 return request;
4140         if (line->type == LINE_COMMIT &&
4141            (view->type == VIEW_LOG ||
4142             view->type == VIEW_PAGER)) {
4143                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4144                 split = 1;
4145         }
4147         /* Always scroll the view even if it was split. That way
4148          * you can use Enter to scroll through the log view and
4149          * split open each commit diff. */
4150         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4152         /* FIXME: A minor workaround. Scrolling the view will call report("")
4153          * but if we are scrolling a non-current view this won't properly
4154          * update the view title. */
4155         if (split)
4156                 update_view_title(view);
4158         return REQ_NONE;
4161 static bool
4162 pager_grep(struct view *view, struct line *line)
4164         const char *text[] = { line->data, NULL };
4166         return grep_text(view, text);
4169 static void
4170 pager_select(struct view *view, struct line *line)
4172         if (line->type == LINE_COMMIT) {
4173                 char *text = (char *)line->data + STRING_SIZE("commit ");
4175                 if (view->type != VIEW_PAGER)
4176                         string_copy_rev(view->ref, text);
4177                 string_copy_rev(ref_commit, text);
4178         }
4181 static struct view_ops pager_ops = {
4182         "line",
4183         NULL,
4184         NULL,
4185         pager_read,
4186         pager_draw,
4187         pager_request,
4188         pager_grep,
4189         pager_select,
4190 };
4192 static const char *log_argv[SIZEOF_ARG] = {
4193         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4194 };
4196 static enum request
4197 log_request(struct view *view, enum request request, struct line *line)
4199         switch (request) {
4200         case REQ_REFRESH:
4201                 load_refs();
4202                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4203                 return REQ_NONE;
4204         default:
4205                 return pager_request(view, request, line);
4206         }
4209 static struct view_ops log_ops = {
4210         "line",
4211         log_argv,
4212         NULL,
4213         pager_read,
4214         pager_draw,
4215         log_request,
4216         pager_grep,
4217         pager_select,
4218 };
4220 static const char *diff_argv[SIZEOF_ARG] = {
4221         "git", "show", "--pretty=fuller", "--no-color", "--root",
4222                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4223 };
4225 static struct view_ops diff_ops = {
4226         "line",
4227         diff_argv,
4228         NULL,
4229         pager_read,
4230         pager_draw,
4231         pager_request,
4232         pager_grep,
4233         pager_select,
4234 };
4236 /*
4237  * Help backend
4238  */
4240 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4242 static bool
4243 help_open_keymap_title(struct view *view, enum keymap keymap)
4245         struct line *line;
4247         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4248                                help_keymap_hidden[keymap] ? '+' : '-',
4249                                enum_name(keymap_table[keymap]));
4250         if (line)
4251                 line->other = keymap;
4253         return help_keymap_hidden[keymap];
4256 static void
4257 help_open_keymap(struct view *view, enum keymap keymap)
4259         const char *group = NULL;
4260         char buf[SIZEOF_STR];
4261         size_t bufpos;
4262         bool add_title = TRUE;
4263         int i;
4265         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4266                 const char *key = NULL;
4268                 if (req_info[i].request == REQ_NONE)
4269                         continue;
4271                 if (!req_info[i].request) {
4272                         group = req_info[i].help;
4273                         continue;
4274                 }
4276                 key = get_keys(keymap, req_info[i].request, TRUE);
4277                 if (!key || !*key)
4278                         continue;
4280                 if (add_title && help_open_keymap_title(view, keymap))
4281                         return;
4282                 add_title = FALSE;
4284                 if (group) {
4285                         add_line_text(view, group, LINE_HELP_GROUP);
4286                         group = NULL;
4287                 }
4289                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4290                                 enum_name(req_info[i]), req_info[i].help);
4291         }
4293         group = "External commands:";
4295         for (i = 0; i < run_requests; i++) {
4296                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4297                 const char *key;
4298                 int argc;
4300                 if (!req || req->keymap != keymap)
4301                         continue;
4303                 key = get_key_name(req->key);
4304                 if (!*key)
4305                         key = "(no key defined)";
4307                 if (add_title && help_open_keymap_title(view, keymap))
4308                         return;
4309                 if (group) {
4310                         add_line_text(view, group, LINE_HELP_GROUP);
4311                         group = NULL;
4312                 }
4314                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4315                         if (!string_format_from(buf, &bufpos, "%s%s",
4316                                                 argc ? " " : "", req->argv[argc]))
4317                                 return;
4319                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4320         }
4323 static bool
4324 help_open(struct view *view)
4326         enum keymap keymap;
4328         reset_view(view);
4329         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4330         add_line_text(view, "", LINE_DEFAULT);
4332         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4333                 help_open_keymap(view, keymap);
4335         return TRUE;
4338 static enum request
4339 help_request(struct view *view, enum request request, struct line *line)
4341         switch (request) {
4342         case REQ_ENTER:
4343                 if (line->type == LINE_HELP_KEYMAP) {
4344                         help_keymap_hidden[line->other] =
4345                                 !help_keymap_hidden[line->other];
4346                         view->p_restore = TRUE;
4347                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4348                 }
4350                 return REQ_NONE;
4351         default:
4352                 return pager_request(view, request, line);
4353         }
4356 static struct view_ops help_ops = {
4357         "line",
4358         NULL,
4359         help_open,
4360         NULL,
4361         pager_draw,
4362         help_request,
4363         pager_grep,
4364         pager_select,
4365 };
4368 /*
4369  * Tree backend
4370  */
4372 struct tree_stack_entry {
4373         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4374         unsigned long lineno;           /* Line number to restore */
4375         char *name;                     /* Position of name in opt_path */
4376 };
4378 /* The top of the path stack. */
4379 static struct tree_stack_entry *tree_stack = NULL;
4380 unsigned long tree_lineno = 0;
4382 static void
4383 pop_tree_stack_entry(void)
4385         struct tree_stack_entry *entry = tree_stack;
4387         tree_lineno = entry->lineno;
4388         entry->name[0] = 0;
4389         tree_stack = entry->prev;
4390         free(entry);
4393 static void
4394 push_tree_stack_entry(const char *name, unsigned long lineno)
4396         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4397         size_t pathlen = strlen(opt_path);
4399         if (!entry)
4400                 return;
4402         entry->prev = tree_stack;
4403         entry->name = opt_path + pathlen;
4404         tree_stack = entry;
4406         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4407                 pop_tree_stack_entry();
4408                 return;
4409         }
4411         /* Move the current line to the first tree entry. */
4412         tree_lineno = 1;
4413         entry->lineno = lineno;
4416 /* Parse output from git-ls-tree(1):
4417  *
4418  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4419  */
4421 #define SIZEOF_TREE_ATTR \
4422         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4424 #define SIZEOF_TREE_MODE \
4425         STRING_SIZE("100644 ")
4427 #define TREE_ID_OFFSET \
4428         STRING_SIZE("100644 blob ")
4430 struct tree_entry {
4431         char id[SIZEOF_REV];
4432         mode_t mode;
4433         struct time time;               /* Date from the author ident. */
4434         const char *author;             /* Author of the commit. */
4435         char name[1];
4436 };
4438 static const char *
4439 tree_path(const struct line *line)
4441         return ((struct tree_entry *) line->data)->name;
4444 static int
4445 tree_compare_entry(const struct line *line1, const struct line *line2)
4447         if (line1->type != line2->type)
4448                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4449         return strcmp(tree_path(line1), tree_path(line2));
4452 static const enum sort_field tree_sort_fields[] = {
4453         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4454 };
4455 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4457 static int
4458 tree_compare(const void *l1, const void *l2)
4460         const struct line *line1 = (const struct line *) l1;
4461         const struct line *line2 = (const struct line *) l2;
4462         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4463         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4465         if (line1->type == LINE_TREE_HEAD)
4466                 return -1;
4467         if (line2->type == LINE_TREE_HEAD)
4468                 return 1;
4470         switch (get_sort_field(tree_sort_state)) {
4471         case ORDERBY_DATE:
4472                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4474         case ORDERBY_AUTHOR:
4475                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4477         case ORDERBY_NAME:
4478         default:
4479                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4480         }
4484 static struct line *
4485 tree_entry(struct view *view, enum line_type type, const char *path,
4486            const char *mode, const char *id)
4488         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4489         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4491         if (!entry || !line) {
4492                 free(entry);
4493                 return NULL;
4494         }
4496         strncpy(entry->name, path, strlen(path));
4497         if (mode)
4498                 entry->mode = strtoul(mode, NULL, 8);
4499         if (id)
4500                 string_copy_rev(entry->id, id);
4502         return line;
4505 static bool
4506 tree_read_date(struct view *view, char *text, bool *read_date)
4508         static const char *author_name;
4509         static struct time author_time;
4511         if (!text && *read_date) {
4512                 *read_date = FALSE;
4513                 return TRUE;
4515         } else if (!text) {
4516                 char *path = *opt_path ? opt_path : ".";
4517                 /* Find next entry to process */
4518                 const char *log_file[] = {
4519                         "git", "log", "--no-color", "--pretty=raw",
4520                                 "--cc", "--raw", view->id, "--", path, NULL
4521                 };
4522                 struct io io = {};
4524                 if (!view->lines) {
4525                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4526                         report("Tree is empty");
4527                         return TRUE;
4528                 }
4530                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4531                         report("Failed to load tree data");
4532                         return TRUE;
4533                 }
4535                 io_done(view->pipe);
4536                 view->io = io;
4537                 *read_date = TRUE;
4538                 return FALSE;
4540         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4541                 parse_author_line(text + STRING_SIZE("author "),
4542                                   &author_name, &author_time);
4544         } else if (*text == ':') {
4545                 char *pos;
4546                 size_t annotated = 1;
4547                 size_t i;
4549                 pos = strchr(text, '\t');
4550                 if (!pos)
4551                         return TRUE;
4552                 text = pos + 1;
4553                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4554                         text += strlen(opt_path);
4555                 pos = strchr(text, '/');
4556                 if (pos)
4557                         *pos = 0;
4559                 for (i = 1; i < view->lines; i++) {
4560                         struct line *line = &view->line[i];
4561                         struct tree_entry *entry = line->data;
4563                         annotated += !!entry->author;
4564                         if (entry->author || strcmp(entry->name, text))
4565                                 continue;
4567                         entry->author = author_name;
4568                         entry->time = author_time;
4569                         line->dirty = 1;
4570                         break;
4571                 }
4573                 if (annotated == view->lines)
4574                         io_kill(view->pipe);
4575         }
4576         return TRUE;
4579 static bool
4580 tree_read(struct view *view, char *text)
4582         static bool read_date = FALSE;
4583         struct tree_entry *data;
4584         struct line *entry, *line;
4585         enum line_type type;
4586         size_t textlen = text ? strlen(text) : 0;
4587         char *path = text + SIZEOF_TREE_ATTR;
4589         if (read_date || !text)
4590                 return tree_read_date(view, text, &read_date);
4592         if (textlen <= SIZEOF_TREE_ATTR)
4593                 return FALSE;
4594         if (view->lines == 0 &&
4595             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4596                 return FALSE;
4598         /* Strip the path part ... */
4599         if (*opt_path) {
4600                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4601                 size_t striplen = strlen(opt_path);
4603                 if (pathlen > striplen)
4604                         memmove(path, path + striplen,
4605                                 pathlen - striplen + 1);
4607                 /* Insert "link" to parent directory. */
4608                 if (view->lines == 1 &&
4609                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4610                         return FALSE;
4611         }
4613         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4614         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4615         if (!entry)
4616                 return FALSE;
4617         data = entry->data;
4619         /* Skip "Directory ..." and ".." line. */
4620         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4621                 if (tree_compare_entry(line, entry) <= 0)
4622                         continue;
4624                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4626                 line->data = data;
4627                 line->type = type;
4628                 for (; line <= entry; line++)
4629                         line->dirty = line->cleareol = 1;
4630                 return TRUE;
4631         }
4633         if (tree_lineno > view->lineno) {
4634                 view->lineno = tree_lineno;
4635                 tree_lineno = 0;
4636         }
4638         return TRUE;
4641 static bool
4642 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4644         struct tree_entry *entry = line->data;
4646         if (line->type == LINE_TREE_HEAD) {
4647                 if (draw_text(view, line->type, "Directory path /", TRUE))
4648                         return TRUE;
4649         } else {
4650                 if (draw_mode(view, entry->mode))
4651                         return TRUE;
4653                 if (opt_author && draw_author(view, entry->author))
4654                         return TRUE;
4656                 if (opt_date && draw_date(view, &entry->time))
4657                         return TRUE;
4658         }
4659         if (draw_text(view, line->type, entry->name, TRUE))
4660                 return TRUE;
4661         return TRUE;
4664 static void
4665 open_blob_editor()
4667         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4668         int fd = mkstemp(file);
4670         if (fd == -1)
4671                 report("Failed to create temporary file");
4672         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4673                 report("Failed to save blob data to file");
4674         else
4675                 open_editor(file);
4676         if (fd != -1)
4677                 unlink(file);
4680 static enum request
4681 tree_request(struct view *view, enum request request, struct line *line)
4683         enum open_flags flags;
4685         switch (request) {
4686         case REQ_VIEW_BLAME:
4687                 if (line->type != LINE_TREE_FILE) {
4688                         report("Blame only supported for files");
4689                         return REQ_NONE;
4690                 }
4692                 string_copy(opt_ref, view->vid);
4693                 return request;
4695         case REQ_EDIT:
4696                 if (line->type != LINE_TREE_FILE) {
4697                         report("Edit only supported for files");
4698                 } else if (!is_head_commit(view->vid)) {
4699                         open_blob_editor();
4700                 } else {
4701                         open_editor(opt_file);
4702                 }
4703                 return REQ_NONE;
4705         case REQ_TOGGLE_SORT_FIELD:
4706         case REQ_TOGGLE_SORT_ORDER:
4707                 sort_view(view, request, &tree_sort_state, tree_compare);
4708                 return REQ_NONE;
4710         case REQ_PARENT:
4711                 if (!*opt_path) {
4712                         /* quit view if at top of tree */
4713                         return REQ_VIEW_CLOSE;
4714                 }
4715                 /* fake 'cd  ..' */
4716                 line = &view->line[1];
4717                 break;
4719         case REQ_ENTER:
4720                 break;
4722         default:
4723                 return request;
4724         }
4726         /* Cleanup the stack if the tree view is at a different tree. */
4727         while (!*opt_path && tree_stack)
4728                 pop_tree_stack_entry();
4730         switch (line->type) {
4731         case LINE_TREE_DIR:
4732                 /* Depending on whether it is a subdirectory or parent link
4733                  * mangle the path buffer. */
4734                 if (line == &view->line[1] && *opt_path) {
4735                         pop_tree_stack_entry();
4737                 } else {
4738                         const char *basename = tree_path(line);
4740                         push_tree_stack_entry(basename, view->lineno);
4741                 }
4743                 /* Trees and subtrees share the same ID, so they are not not
4744                  * unique like blobs. */
4745                 flags = OPEN_RELOAD;
4746                 request = REQ_VIEW_TREE;
4747                 break;
4749         case LINE_TREE_FILE:
4750                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4751                 request = REQ_VIEW_BLOB;
4752                 break;
4754         default:
4755                 return REQ_NONE;
4756         }
4758         open_view(view, request, flags);
4759         if (request == REQ_VIEW_TREE)
4760                 view->lineno = tree_lineno;
4762         return REQ_NONE;
4765 static bool
4766 tree_grep(struct view *view, struct line *line)
4768         struct tree_entry *entry = line->data;
4769         const char *text[] = {
4770                 entry->name,
4771                 opt_author ? entry->author : "",
4772                 mkdate(&entry->time, opt_date),
4773                 NULL
4774         };
4776         return grep_text(view, text);
4779 static void
4780 tree_select(struct view *view, struct line *line)
4782         struct tree_entry *entry = line->data;
4784         if (line->type == LINE_TREE_FILE) {
4785                 string_copy_rev(ref_blob, entry->id);
4786                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4788         } else if (line->type != LINE_TREE_DIR) {
4789                 return;
4790         }
4792         string_copy_rev(view->ref, entry->id);
4795 static bool
4796 tree_prepare(struct view *view)
4798         if (view->lines == 0 && opt_prefix[0]) {
4799                 char *pos = opt_prefix;
4801                 while (pos && *pos) {
4802                         char *end = strchr(pos, '/');
4804                         if (end)
4805                                 *end = 0;
4806                         push_tree_stack_entry(pos, 0);
4807                         pos = end;
4808                         if (end) {
4809                                 *end = '/';
4810                                 pos++;
4811                         }
4812                 }
4814         } else if (strcmp(view->vid, view->id)) {
4815                 opt_path[0] = 0;
4816         }
4818         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4821 static const char *tree_argv[SIZEOF_ARG] = {
4822         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4823 };
4825 static struct view_ops tree_ops = {
4826         "file",
4827         tree_argv,
4828         NULL,
4829         tree_read,
4830         tree_draw,
4831         tree_request,
4832         tree_grep,
4833         tree_select,
4834         tree_prepare,
4835 };
4837 static bool
4838 blob_read(struct view *view, char *line)
4840         if (!line)
4841                 return TRUE;
4842         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4845 static enum request
4846 blob_request(struct view *view, enum request request, struct line *line)
4848         switch (request) {
4849         case REQ_EDIT:
4850                 open_blob_editor();
4851                 return REQ_NONE;
4852         default:
4853                 return pager_request(view, request, line);
4854         }
4857 static const char *blob_argv[SIZEOF_ARG] = {
4858         "git", "cat-file", "blob", "%(blob)", NULL
4859 };
4861 static struct view_ops blob_ops = {
4862         "line",
4863         blob_argv,
4864         NULL,
4865         blob_read,
4866         pager_draw,
4867         blob_request,
4868         pager_grep,
4869         pager_select,
4870 };
4872 /*
4873  * Blame backend
4874  *
4875  * Loading the blame view is a two phase job:
4876  *
4877  *  1. File content is read either using opt_file from the
4878  *     filesystem or using git-cat-file.
4879  *  2. Then blame information is incrementally added by
4880  *     reading output from git-blame.
4881  */
4883 static const char *blame_head_argv[] = {
4884         "git", "blame", "--incremental", "--", "%(file)", NULL
4885 };
4887 static const char *blame_ref_argv[] = {
4888         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4889 };
4891 static const char *blame_cat_file_argv[] = {
4892         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4893 };
4895 struct blame_commit {
4896         char id[SIZEOF_REV];            /* SHA1 ID. */
4897         char title[128];                /* First line of the commit message. */
4898         const char *author;             /* Author of the commit. */
4899         struct time time;               /* Date from the author ident. */
4900         char filename[128];             /* Name of file. */
4901         bool has_previous;              /* Was a "previous" line detected. */
4902 };
4904 struct blame {
4905         struct blame_commit *commit;
4906         unsigned long lineno;
4907         char text[1];
4908 };
4910 static bool
4911 blame_open(struct view *view)
4913         char path[SIZEOF_STR];
4915         if (!view->prev && *opt_prefix) {
4916                 string_copy(path, opt_file);
4917                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4918                         return FALSE;
4919         }
4921         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4922                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4923                         return FALSE;
4924         }
4926         setup_update(view, opt_file);
4927         string_format(view->ref, "%s ...", opt_file);
4929         return TRUE;
4932 static struct blame_commit *
4933 get_blame_commit(struct view *view, const char *id)
4935         size_t i;
4937         for (i = 0; i < view->lines; i++) {
4938                 struct blame *blame = view->line[i].data;
4940                 if (!blame->commit)
4941                         continue;
4943                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4944                         return blame->commit;
4945         }
4947         {
4948                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4950                 if (commit)
4951                         string_ncopy(commit->id, id, SIZEOF_REV);
4952                 return commit;
4953         }
4956 static bool
4957 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4959         const char *pos = *posref;
4961         *posref = NULL;
4962         pos = strchr(pos + 1, ' ');
4963         if (!pos || !isdigit(pos[1]))
4964                 return FALSE;
4965         *number = atoi(pos + 1);
4966         if (*number < min || *number > max)
4967                 return FALSE;
4969         *posref = pos;
4970         return TRUE;
4973 static struct blame_commit *
4974 parse_blame_commit(struct view *view, const char *text, int *blamed)
4976         struct blame_commit *commit;
4977         struct blame *blame;
4978         const char *pos = text + SIZEOF_REV - 2;
4979         size_t orig_lineno = 0;
4980         size_t lineno;
4981         size_t group;
4983         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4984                 return NULL;
4986         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4987             !parse_number(&pos, &lineno, 1, view->lines) ||
4988             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4989                 return NULL;
4991         commit = get_blame_commit(view, text);
4992         if (!commit)
4993                 return NULL;
4995         *blamed += group;
4996         while (group--) {
4997                 struct line *line = &view->line[lineno + group - 1];
4999                 blame = line->data;
5000                 blame->commit = commit;
5001                 blame->lineno = orig_lineno + group - 1;
5002                 line->dirty = 1;
5003         }
5005         return commit;
5008 static bool
5009 blame_read_file(struct view *view, const char *line, bool *read_file)
5011         if (!line) {
5012                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
5013                 struct io io = {};
5015                 if (view->lines == 0 && !view->prev)
5016                         die("No blame exist for %s", view->vid);
5018                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
5019                         report("Failed to load blame data");
5020                         return TRUE;
5021                 }
5023                 io_done(view->pipe);
5024                 view->io = io;
5025                 *read_file = FALSE;
5026                 return FALSE;
5028         } else {
5029                 size_t linelen = strlen(line);
5030                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5032                 if (!blame)
5033                         return FALSE;
5035                 blame->commit = NULL;
5036                 strncpy(blame->text, line, linelen);
5037                 blame->text[linelen] = 0;
5038                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5039         }
5042 static bool
5043 match_blame_header(const char *name, char **line)
5045         size_t namelen = strlen(name);
5046         bool matched = !strncmp(name, *line, namelen);
5048         if (matched)
5049                 *line += namelen;
5051         return matched;
5054 static bool
5055 blame_read(struct view *view, char *line)
5057         static struct blame_commit *commit = NULL;
5058         static int blamed = 0;
5059         static bool read_file = TRUE;
5061         if (read_file)
5062                 return blame_read_file(view, line, &read_file);
5064         if (!line) {
5065                 /* Reset all! */
5066                 commit = NULL;
5067                 blamed = 0;
5068                 read_file = TRUE;
5069                 string_format(view->ref, "%s", view->vid);
5070                 if (view_is_displayed(view)) {
5071                         update_view_title(view);
5072                         redraw_view_from(view, 0);
5073                 }
5074                 return TRUE;
5075         }
5077         if (!commit) {
5078                 commit = parse_blame_commit(view, line, &blamed);
5079                 string_format(view->ref, "%s %2d%%", view->vid,
5080                               view->lines ? blamed * 100 / view->lines : 0);
5082         } else if (match_blame_header("author ", &line)) {
5083                 commit->author = get_author(line);
5085         } else if (match_blame_header("author-time ", &line)) {
5086                 parse_timesec(&commit->time, line);
5088         } else if (match_blame_header("author-tz ", &line)) {
5089                 parse_timezone(&commit->time, line);
5091         } else if (match_blame_header("summary ", &line)) {
5092                 string_ncopy(commit->title, line, strlen(line));
5094         } else if (match_blame_header("previous ", &line)) {
5095                 commit->has_previous = TRUE;
5097         } else if (match_blame_header("filename ", &line)) {
5098                 string_ncopy(commit->filename, line, strlen(line));
5099                 commit = NULL;
5100         }
5102         return TRUE;
5105 static bool
5106 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5108         struct blame *blame = line->data;
5109         struct time *time = NULL;
5110         const char *id = NULL, *author = NULL;
5111         char text[SIZEOF_STR];
5113         if (blame->commit && *blame->commit->filename) {
5114                 id = blame->commit->id;
5115                 author = blame->commit->author;
5116                 time = &blame->commit->time;
5117         }
5119         if (opt_date && draw_date(view, time))
5120                 return TRUE;
5122         if (opt_author && draw_author(view, author))
5123                 return TRUE;
5125         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5126                 return TRUE;
5128         if (draw_lineno(view, lineno))
5129                 return TRUE;
5131         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5132         draw_text(view, LINE_DEFAULT, text, TRUE);
5133         return TRUE;
5136 static bool
5137 check_blame_commit(struct blame *blame, bool check_null_id)
5139         if (!blame->commit)
5140                 report("Commit data not loaded yet");
5141         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5142                 report("No commit exist for the selected line");
5143         else
5144                 return TRUE;
5145         return FALSE;
5148 static void
5149 setup_blame_parent_line(struct view *view, struct blame *blame)
5151         const char *diff_tree_argv[] = {
5152                 "git", "diff-tree", "-U0", blame->commit->id,
5153                         "--", blame->commit->filename, NULL
5154         };
5155         struct io io = {};
5156         int parent_lineno = -1;
5157         int blamed_lineno = -1;
5158         char *line;
5160         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5161                 return;
5163         while ((line = io_get(&io, '\n', TRUE))) {
5164                 if (*line == '@') {
5165                         char *pos = strchr(line, '+');
5167                         parent_lineno = atoi(line + 4);
5168                         if (pos)
5169                                 blamed_lineno = atoi(pos + 1);
5171                 } else if (*line == '+' && parent_lineno != -1) {
5172                         if (blame->lineno == blamed_lineno - 1 &&
5173                             !strcmp(blame->text, line + 1)) {
5174                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5175                                 break;
5176                         }
5177                         blamed_lineno++;
5178                 }
5179         }
5181         io_done(&io);
5184 static enum request
5185 blame_request(struct view *view, enum request request, struct line *line)
5187         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5188         struct blame *blame = line->data;
5190         switch (request) {
5191         case REQ_VIEW_BLAME:
5192                 if (check_blame_commit(blame, TRUE)) {
5193                         string_copy(opt_ref, blame->commit->id);
5194                         string_copy(opt_file, blame->commit->filename);
5195                         if (blame->lineno)
5196                                 view->lineno = blame->lineno;
5197                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5198                 }
5199                 break;
5201         case REQ_PARENT:
5202                 if (check_blame_commit(blame, TRUE) &&
5203                     select_commit_parent(blame->commit->id, opt_ref,
5204                                          blame->commit->filename)) {
5205                         string_copy(opt_file, blame->commit->filename);
5206                         setup_blame_parent_line(view, blame);
5207                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5208                 }
5209                 break;
5211         case REQ_ENTER:
5212                 if (!check_blame_commit(blame, FALSE))
5213                         break;
5215                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5216                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5217                         break;
5219                 if (!strcmp(blame->commit->id, NULL_ID)) {
5220                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5221                         const char *diff_index_argv[] = {
5222                                 "git", "diff-index", "--root", "--patch-with-stat",
5223                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5224                         };
5226                         if (!blame->commit->has_previous) {
5227                                 diff_index_argv[1] = "diff";
5228                                 diff_index_argv[2] = "--no-color";
5229                                 diff_index_argv[6] = "--";
5230                                 diff_index_argv[7] = "/dev/null";
5231                         }
5233                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5234                                 report("Failed to allocate diff command");
5235                                 break;
5236                         }
5237                         flags |= OPEN_PREPARED;
5238                 }
5240                 open_view(view, REQ_VIEW_DIFF, flags);
5241                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5242                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5243                 break;
5245         default:
5246                 return request;
5247         }
5249         return REQ_NONE;
5252 static bool
5253 blame_grep(struct view *view, struct line *line)
5255         struct blame *blame = line->data;
5256         struct blame_commit *commit = blame->commit;
5257         const char *text[] = {
5258                 blame->text,
5259                 commit ? commit->title : "",
5260                 commit ? commit->id : "",
5261                 commit && opt_author ? commit->author : "",
5262                 commit ? mkdate(&commit->time, opt_date) : "",
5263                 NULL
5264         };
5266         return grep_text(view, text);
5269 static void
5270 blame_select(struct view *view, struct line *line)
5272         struct blame *blame = line->data;
5273         struct blame_commit *commit = blame->commit;
5275         if (!commit)
5276                 return;
5278         if (!strcmp(commit->id, NULL_ID))
5279                 string_ncopy(ref_commit, "HEAD", 4);
5280         else
5281                 string_copy_rev(ref_commit, commit->id);
5284 static struct view_ops blame_ops = {
5285         "line",
5286         NULL,
5287         blame_open,
5288         blame_read,
5289         blame_draw,
5290         blame_request,
5291         blame_grep,
5292         blame_select,
5293 };
5295 /*
5296  * Branch backend
5297  */
5299 struct branch {
5300         const char *author;             /* Author of the last commit. */
5301         struct time time;               /* Date of the last activity. */
5302         const struct ref *ref;          /* Name and commit ID information. */
5303 };
5305 static const struct ref branch_all;
5307 static const enum sort_field branch_sort_fields[] = {
5308         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5309 };
5310 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5312 static int
5313 branch_compare(const void *l1, const void *l2)
5315         const struct branch *branch1 = ((const struct line *) l1)->data;
5316         const struct branch *branch2 = ((const struct line *) l2)->data;
5318         switch (get_sort_field(branch_sort_state)) {
5319         case ORDERBY_DATE:
5320                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5322         case ORDERBY_AUTHOR:
5323                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5325         case ORDERBY_NAME:
5326         default:
5327                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5328         }
5331 static bool
5332 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5334         struct branch *branch = line->data;
5335         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5337         if (opt_date && draw_date(view, &branch->time))
5338                 return TRUE;
5340         if (opt_author && draw_author(view, branch->author))
5341                 return TRUE;
5343         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5344         return TRUE;
5347 static enum request
5348 branch_request(struct view *view, enum request request, struct line *line)
5350         struct branch *branch = line->data;
5352         switch (request) {
5353         case REQ_REFRESH:
5354                 load_refs();
5355                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5356                 return REQ_NONE;
5358         case REQ_TOGGLE_SORT_FIELD:
5359         case REQ_TOGGLE_SORT_ORDER:
5360                 sort_view(view, request, &branch_sort_state, branch_compare);
5361                 return REQ_NONE;
5363         case REQ_ENTER:
5364                 if (branch->ref == &branch_all) {
5365                         const char *all_branches_argv[] = {
5366                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5367                                       "--topo-order", "--all", NULL
5368                         };
5369                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5371                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5372                                 report("Failed to load view of all branches");
5373                                 return REQ_NONE;
5374                         }
5375                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5376                 } else {
5377                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5378                 }
5379                 return REQ_NONE;
5381         default:
5382                 return request;
5383         }
5386 static bool
5387 branch_read(struct view *view, char *line)
5389         static char id[SIZEOF_REV];
5390         struct branch *reference;
5391         size_t i;
5393         if (!line)
5394                 return TRUE;
5396         switch (get_line_type(line)) {
5397         case LINE_COMMIT:
5398                 string_copy_rev(id, line + STRING_SIZE("commit "));
5399                 return TRUE;
5401         case LINE_AUTHOR:
5402                 for (i = 0, reference = NULL; i < view->lines; i++) {
5403                         struct branch *branch = view->line[i].data;
5405                         if (strcmp(branch->ref->id, id))
5406                                 continue;
5408                         view->line[i].dirty = TRUE;
5409                         if (reference) {
5410                                 branch->author = reference->author;
5411                                 branch->time = reference->time;
5412                                 continue;
5413                         }
5415                         parse_author_line(line + STRING_SIZE("author "),
5416                                           &branch->author, &branch->time);
5417                         reference = branch;
5418                 }
5419                 return TRUE;
5421         default:
5422                 return TRUE;
5423         }
5427 static bool
5428 branch_open_visitor(void *data, const struct ref *ref)
5430         struct view *view = data;
5431         struct branch *branch;
5433         if (ref->tag || ref->ltag || ref->remote)
5434                 return TRUE;
5436         branch = calloc(1, sizeof(*branch));
5437         if (!branch)
5438                 return FALSE;
5440         branch->ref = ref;
5441         return !!add_line_data(view, branch, LINE_DEFAULT);
5444 static bool
5445 branch_open(struct view *view)
5447         const char *branch_log[] = {
5448                 "git", "log", "--no-color", "--pretty=raw",
5449                         "--simplify-by-decoration", "--all", NULL
5450         };
5452         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5453                 report("Failed to load branch data");
5454                 return TRUE;
5455         }
5457         setup_update(view, view->id);
5458         branch_open_visitor(view, &branch_all);
5459         foreach_ref(branch_open_visitor, view);
5460         view->p_restore = TRUE;
5462         return TRUE;
5465 static bool
5466 branch_grep(struct view *view, struct line *line)
5468         struct branch *branch = line->data;
5469         const char *text[] = {
5470                 branch->ref->name,
5471                 branch->author,
5472                 NULL
5473         };
5475         return grep_text(view, text);
5478 static void
5479 branch_select(struct view *view, struct line *line)
5481         struct branch *branch = line->data;
5483         string_copy_rev(view->ref, branch->ref->id);
5484         string_copy_rev(ref_commit, branch->ref->id);
5485         string_copy_rev(ref_head, branch->ref->id);
5486         string_copy_rev(ref_branch, branch->ref->name);
5489 static struct view_ops branch_ops = {
5490         "branch",
5491         NULL,
5492         branch_open,
5493         branch_read,
5494         branch_draw,
5495         branch_request,
5496         branch_grep,
5497         branch_select,
5498 };
5500 /*
5501  * Status backend
5502  */
5504 struct status {
5505         char status;
5506         struct {
5507                 mode_t mode;
5508                 char rev[SIZEOF_REV];
5509                 char name[SIZEOF_STR];
5510         } old;
5511         struct {
5512                 mode_t mode;
5513                 char rev[SIZEOF_REV];
5514                 char name[SIZEOF_STR];
5515         } new;
5516 };
5518 static char status_onbranch[SIZEOF_STR];
5519 static struct status stage_status;
5520 static enum line_type stage_line_type;
5521 static size_t stage_chunks;
5522 static int *stage_chunk;
5524 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5526 /* This should work even for the "On branch" line. */
5527 static inline bool
5528 status_has_none(struct view *view, struct line *line)
5530         return line < view->line + view->lines && !line[1].data;
5533 /* Get fields from the diff line:
5534  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5535  */
5536 static inline bool
5537 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5539         const char *old_mode = buf +  1;
5540         const char *new_mode = buf +  8;
5541         const char *old_rev  = buf + 15;
5542         const char *new_rev  = buf + 56;
5543         const char *status   = buf + 97;
5545         if (bufsize < 98 ||
5546             old_mode[-1] != ':' ||
5547             new_mode[-1] != ' ' ||
5548             old_rev[-1]  != ' ' ||
5549             new_rev[-1]  != ' ' ||
5550             status[-1]   != ' ')
5551                 return FALSE;
5553         file->status = *status;
5555         string_copy_rev(file->old.rev, old_rev);
5556         string_copy_rev(file->new.rev, new_rev);
5558         file->old.mode = strtoul(old_mode, NULL, 8);
5559         file->new.mode = strtoul(new_mode, NULL, 8);
5561         file->old.name[0] = file->new.name[0] = 0;
5563         return TRUE;
5566 static bool
5567 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5569         struct status *unmerged = NULL;
5570         char *buf;
5571         struct io io = {};
5573         if (!io_run(&io, argv, opt_cdup, IO_RD))
5574                 return FALSE;
5576         add_line_data(view, NULL, type);
5578         while ((buf = io_get(&io, 0, TRUE))) {
5579                 struct status *file = unmerged;
5581                 if (!file) {
5582                         file = calloc(1, sizeof(*file));
5583                         if (!file || !add_line_data(view, file, type))
5584                                 goto error_out;
5585                 }
5587                 /* Parse diff info part. */
5588                 if (status) {
5589                         file->status = status;
5590                         if (status == 'A')
5591                                 string_copy(file->old.rev, NULL_ID);
5593                 } else if (!file->status || file == unmerged) {
5594                         if (!status_get_diff(file, buf, strlen(buf)))
5595                                 goto error_out;
5597                         buf = io_get(&io, 0, TRUE);
5598                         if (!buf)
5599                                 break;
5601                         /* Collapse all modified entries that follow an
5602                          * associated unmerged entry. */
5603                         if (unmerged == file) {
5604                                 unmerged->status = 'U';
5605                                 unmerged = NULL;
5606                         } else if (file->status == 'U') {
5607                                 unmerged = file;
5608                         }
5609                 }
5611                 /* Grab the old name for rename/copy. */
5612                 if (!*file->old.name &&
5613                     (file->status == 'R' || file->status == 'C')) {
5614                         string_ncopy(file->old.name, buf, strlen(buf));
5616                         buf = io_get(&io, 0, TRUE);
5617                         if (!buf)
5618                                 break;
5619                 }
5621                 /* git-ls-files just delivers a NUL separated list of
5622                  * file names similar to the second half of the
5623                  * git-diff-* output. */
5624                 string_ncopy(file->new.name, buf, strlen(buf));
5625                 if (!*file->old.name)
5626                         string_copy(file->old.name, file->new.name);
5627                 file = NULL;
5628         }
5630         if (io_error(&io)) {
5631 error_out:
5632                 io_done(&io);
5633                 return FALSE;
5634         }
5636         if (!view->line[view->lines - 1].data)
5637                 add_line_data(view, NULL, LINE_STAT_NONE);
5639         io_done(&io);
5640         return TRUE;
5643 /* Don't show unmerged entries in the staged section. */
5644 static const char *status_diff_index_argv[] = {
5645         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5646                              "--cached", "-M", "HEAD", NULL
5647 };
5649 static const char *status_diff_files_argv[] = {
5650         "git", "diff-files", "-z", NULL
5651 };
5653 static const char *status_list_other_argv[] = {
5654         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5655 };
5657 static const char *status_list_no_head_argv[] = {
5658         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5659 };
5661 static const char *update_index_argv[] = {
5662         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5663 };
5665 /* Restore the previous line number to stay in the context or select a
5666  * line with something that can be updated. */
5667 static void
5668 status_restore(struct view *view)
5670         if (view->p_lineno >= view->lines)
5671                 view->p_lineno = view->lines - 1;
5672         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5673                 view->p_lineno++;
5674         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5675                 view->p_lineno--;
5677         /* If the above fails, always skip the "On branch" line. */
5678         if (view->p_lineno < view->lines)
5679                 view->lineno = view->p_lineno;
5680         else
5681                 view->lineno = 1;
5683         if (view->lineno < view->offset)
5684                 view->offset = view->lineno;
5685         else if (view->offset + view->height <= view->lineno)
5686                 view->offset = view->lineno - view->height + 1;
5688         view->p_restore = FALSE;
5691 static void
5692 status_update_onbranch(void)
5694         static const char *paths[][2] = {
5695                 { "rebase-apply/rebasing",      "Rebasing" },
5696                 { "rebase-apply/applying",      "Applying mailbox" },
5697                 { "rebase-apply/",              "Rebasing mailbox" },
5698                 { "rebase-merge/interactive",   "Interactive rebase" },
5699                 { "rebase-merge/",              "Rebase merge" },
5700                 { "MERGE_HEAD",                 "Merging" },
5701                 { "BISECT_LOG",                 "Bisecting" },
5702                 { "HEAD",                       "On branch" },
5703         };
5704         char buf[SIZEOF_STR];
5705         struct stat stat;
5706         int i;
5708         if (is_initial_commit()) {
5709                 string_copy(status_onbranch, "Initial commit");
5710                 return;
5711         }
5713         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5714                 char *head = opt_head;
5716                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5717                     lstat(buf, &stat) < 0)
5718                         continue;
5720                 if (!*opt_head) {
5721                         struct io io = {};
5723                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5724                             io_read_buf(&io, buf, sizeof(buf))) {
5725                                 head = buf;
5726                                 if (!prefixcmp(head, "refs/heads/"))
5727                                         head += STRING_SIZE("refs/heads/");
5728                         }
5729                 }
5731                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5732                         string_copy(status_onbranch, opt_head);
5733                 return;
5734         }
5736         string_copy(status_onbranch, "Not currently on any branch");
5739 /* First parse staged info using git-diff-index(1), then parse unstaged
5740  * info using git-diff-files(1), and finally untracked files using
5741  * git-ls-files(1). */
5742 static bool
5743 status_open(struct view *view)
5745         reset_view(view);
5747         add_line_data(view, NULL, LINE_STAT_HEAD);
5748         status_update_onbranch();
5750         io_run_bg(update_index_argv);
5752         if (is_initial_commit()) {
5753                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5754                         return FALSE;
5755         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5756                 return FALSE;
5757         }
5759         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5760             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5761                 return FALSE;
5763         /* Restore the exact position or use the specialized restore
5764          * mode? */
5765         if (!view->p_restore)
5766                 status_restore(view);
5767         return TRUE;
5770 static bool
5771 status_draw(struct view *view, struct line *line, unsigned int lineno)
5773         struct status *status = line->data;
5774         enum line_type type;
5775         const char *text;
5777         if (!status) {
5778                 switch (line->type) {
5779                 case LINE_STAT_STAGED:
5780                         type = LINE_STAT_SECTION;
5781                         text = "Changes to be committed:";
5782                         break;
5784                 case LINE_STAT_UNSTAGED:
5785                         type = LINE_STAT_SECTION;
5786                         text = "Changed but not updated:";
5787                         break;
5789                 case LINE_STAT_UNTRACKED:
5790                         type = LINE_STAT_SECTION;
5791                         text = "Untracked files:";
5792                         break;
5794                 case LINE_STAT_NONE:
5795                         type = LINE_DEFAULT;
5796                         text = "  (no files)";
5797                         break;
5799                 case LINE_STAT_HEAD:
5800                         type = LINE_STAT_HEAD;
5801                         text = status_onbranch;
5802                         break;
5804                 default:
5805                         return FALSE;
5806                 }
5807         } else {
5808                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5810                 buf[0] = status->status;
5811                 if (draw_text(view, line->type, buf, TRUE))
5812                         return TRUE;
5813                 type = LINE_DEFAULT;
5814                 text = status->new.name;
5815         }
5817         draw_text(view, type, text, TRUE);
5818         return TRUE;
5821 static enum request
5822 status_load_error(struct view *view, struct view *stage, const char *path)
5824         if (displayed_views() == 2 || display[current_view] != view)
5825                 maximize_view(view);
5826         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5827         return REQ_NONE;
5830 static enum request
5831 status_enter(struct view *view, struct line *line)
5833         struct status *status = line->data;
5834         const char *oldpath = status ? status->old.name : NULL;
5835         /* Diffs for unmerged entries are empty when passing the new
5836          * path, so leave it empty. */
5837         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5838         const char *info;
5839         enum open_flags split;
5840         struct view *stage = VIEW(REQ_VIEW_STAGE);
5842         if (line->type == LINE_STAT_NONE ||
5843             (!status && line[1].type == LINE_STAT_NONE)) {
5844                 report("No file to diff");
5845                 return REQ_NONE;
5846         }
5848         switch (line->type) {
5849         case LINE_STAT_STAGED:
5850                 if (is_initial_commit()) {
5851                         const char *no_head_diff_argv[] = {
5852                                 "git", "diff", "--no-color", "--patch-with-stat",
5853                                         "--", "/dev/null", newpath, NULL
5854                         };
5856                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5857                                 return status_load_error(view, stage, newpath);
5858                 } else {
5859                         const char *index_show_argv[] = {
5860                                 "git", "diff-index", "--root", "--patch-with-stat",
5861                                         "-C", "-M", "--cached", "HEAD", "--",
5862                                         oldpath, newpath, NULL
5863                         };
5865                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5866                                 return status_load_error(view, stage, newpath);
5867                 }
5869                 if (status)
5870                         info = "Staged changes to %s";
5871                 else
5872                         info = "Staged changes";
5873                 break;
5875         case LINE_STAT_UNSTAGED:
5876         {
5877                 const char *files_show_argv[] = {
5878                         "git", "diff-files", "--root", "--patch-with-stat",
5879                                 "-C", "-M", "--", oldpath, newpath, NULL
5880                 };
5882                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5883                         return status_load_error(view, stage, newpath);
5884                 if (status)
5885                         info = "Unstaged changes to %s";
5886                 else
5887                         info = "Unstaged changes";
5888                 break;
5889         }
5890         case LINE_STAT_UNTRACKED:
5891                 if (!newpath) {
5892                         report("No file to show");
5893                         return REQ_NONE;
5894                 }
5896                 if (!suffixcmp(status->new.name, -1, "/")) {
5897                         report("Cannot display a directory");
5898                         return REQ_NONE;
5899                 }
5901                 if (!prepare_update_file(stage, newpath))
5902                         return status_load_error(view, stage, newpath);
5903                 info = "Untracked file %s";
5904                 break;
5906         case LINE_STAT_HEAD:
5907                 return REQ_NONE;
5909         default:
5910                 die("line type %d not handled in switch", line->type);
5911         }
5913         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5914         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5915         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5916                 if (status) {
5917                         stage_status = *status;
5918                 } else {
5919                         memset(&stage_status, 0, sizeof(stage_status));
5920                 }
5922                 stage_line_type = line->type;
5923                 stage_chunks = 0;
5924                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5925         }
5927         return REQ_NONE;
5930 static bool
5931 status_exists(struct status *status, enum line_type type)
5933         struct view *view = VIEW(REQ_VIEW_STATUS);
5934         unsigned long lineno;
5936         for (lineno = 0; lineno < view->lines; lineno++) {
5937                 struct line *line = &view->line[lineno];
5938                 struct status *pos = line->data;
5940                 if (line->type != type)
5941                         continue;
5942                 if (!pos && (!status || !status->status) && line[1].data) {
5943                         select_view_line(view, lineno);
5944                         return TRUE;
5945                 }
5946                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5947                         select_view_line(view, lineno);
5948                         return TRUE;
5949                 }
5950         }
5952         return FALSE;
5956 static bool
5957 status_update_prepare(struct io *io, enum line_type type)
5959         const char *staged_argv[] = {
5960                 "git", "update-index", "-z", "--index-info", NULL
5961         };
5962         const char *others_argv[] = {
5963                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5964         };
5966         switch (type) {
5967         case LINE_STAT_STAGED:
5968                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5970         case LINE_STAT_UNSTAGED:
5971         case LINE_STAT_UNTRACKED:
5972                 return io_run(io, others_argv, opt_cdup, IO_WR);
5974         default:
5975                 die("line type %d not handled in switch", type);
5976                 return FALSE;
5977         }
5980 static bool
5981 status_update_write(struct io *io, struct status *status, enum line_type type)
5983         char buf[SIZEOF_STR];
5984         size_t bufsize = 0;
5986         switch (type) {
5987         case LINE_STAT_STAGED:
5988                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5989                                         status->old.mode,
5990                                         status->old.rev,
5991                                         status->old.name, 0))
5992                         return FALSE;
5993                 break;
5995         case LINE_STAT_UNSTAGED:
5996         case LINE_STAT_UNTRACKED:
5997                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5998                         return FALSE;
5999                 break;
6001         default:
6002                 die("line type %d not handled in switch", type);
6003         }
6005         return io_write(io, buf, bufsize);
6008 static bool
6009 status_update_file(struct status *status, enum line_type type)
6011         struct io io = {};
6012         bool result;
6014         if (!status_update_prepare(&io, type))
6015                 return FALSE;
6017         result = status_update_write(&io, status, type);
6018         return io_done(&io) && result;
6021 static bool
6022 status_update_files(struct view *view, struct line *line)
6024         char buf[sizeof(view->ref)];
6025         struct io io = {};
6026         bool result = TRUE;
6027         struct line *pos = view->line + view->lines;
6028         int files = 0;
6029         int file, done;
6030         int cursor_y = -1, cursor_x = -1;
6032         if (!status_update_prepare(&io, line->type))
6033                 return FALSE;
6035         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6036                 files++;
6038         string_copy(buf, view->ref);
6039         getsyx(cursor_y, cursor_x);
6040         for (file = 0, done = 5; result && file < files; line++, file++) {
6041                 int almost_done = file * 100 / files;
6043                 if (almost_done > done) {
6044                         done = almost_done;
6045                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6046                                       file, files, done);
6047                         update_view_title(view);
6048                         setsyx(cursor_y, cursor_x);
6049                         doupdate();
6050                 }
6051                 result = status_update_write(&io, line->data, line->type);
6052         }
6053         string_copy(view->ref, buf);
6055         return io_done(&io) && result;
6058 static bool
6059 status_update(struct view *view)
6061         struct line *line = &view->line[view->lineno];
6063         assert(view->lines);
6065         if (!line->data) {
6066                 /* This should work even for the "On branch" line. */
6067                 if (line < view->line + view->lines && !line[1].data) {
6068                         report("Nothing to update");
6069                         return FALSE;
6070                 }
6072                 if (!status_update_files(view, line + 1)) {
6073                         report("Failed to update file status");
6074                         return FALSE;
6075                 }
6077         } else if (!status_update_file(line->data, line->type)) {
6078                 report("Failed to update file status");
6079                 return FALSE;
6080         }
6082         return TRUE;
6085 static bool
6086 status_revert(struct status *status, enum line_type type, bool has_none)
6088         if (!status || type != LINE_STAT_UNSTAGED) {
6089                 if (type == LINE_STAT_STAGED) {
6090                         report("Cannot revert changes to staged files");
6091                 } else if (type == LINE_STAT_UNTRACKED) {
6092                         report("Cannot revert changes to untracked files");
6093                 } else if (has_none) {
6094                         report("Nothing to revert");
6095                 } else {
6096                         report("Cannot revert changes to multiple files");
6097                 }
6099         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6100                 char mode[10] = "100644";
6101                 const char *reset_argv[] = {
6102                         "git", "update-index", "--cacheinfo", mode,
6103                                 status->old.rev, status->old.name, NULL
6104                 };
6105                 const char *checkout_argv[] = {
6106                         "git", "checkout", "--", status->old.name, NULL
6107                 };
6109                 if (status->status == 'U') {
6110                         string_format(mode, "%5o", status->old.mode);
6112                         if (status->old.mode == 0 && status->new.mode == 0) {
6113                                 reset_argv[2] = "--force-remove";
6114                                 reset_argv[3] = status->old.name;
6115                                 reset_argv[4] = NULL;
6116                         }
6118                         if (!io_run_fg(reset_argv, opt_cdup))
6119                                 return FALSE;
6120                         if (status->old.mode == 0 && status->new.mode == 0)
6121                                 return TRUE;
6122                 }
6124                 return io_run_fg(checkout_argv, opt_cdup);
6125         }
6127         return FALSE;
6130 static enum request
6131 status_request(struct view *view, enum request request, struct line *line)
6133         struct status *status = line->data;
6135         switch (request) {
6136         case REQ_STATUS_UPDATE:
6137                 if (!status_update(view))
6138                         return REQ_NONE;
6139                 break;
6141         case REQ_STATUS_REVERT:
6142                 if (!status_revert(status, line->type, status_has_none(view, line)))
6143                         return REQ_NONE;
6144                 break;
6146         case REQ_STATUS_MERGE:
6147                 if (!status || status->status != 'U') {
6148                         report("Merging only possible for files with unmerged status ('U').");
6149                         return REQ_NONE;
6150                 }
6151                 open_mergetool(status->new.name);
6152                 break;
6154         case REQ_EDIT:
6155                 if (!status)
6156                         return request;
6157                 if (status->status == 'D') {
6158                         report("File has been deleted.");
6159                         return REQ_NONE;
6160                 }
6162                 open_editor(status->new.name);
6163                 break;
6165         case REQ_VIEW_BLAME:
6166                 if (status)
6167                         opt_ref[0] = 0;
6168                 return request;
6170         case REQ_ENTER:
6171                 /* After returning the status view has been split to
6172                  * show the stage view. No further reloading is
6173                  * necessary. */
6174                 return status_enter(view, line);
6176         case REQ_REFRESH:
6177                 /* Simply reload the view. */
6178                 break;
6180         default:
6181                 return request;
6182         }
6184         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6186         return REQ_NONE;
6189 static void
6190 status_select(struct view *view, struct line *line)
6192         struct status *status = line->data;
6193         char file[SIZEOF_STR] = "all files";
6194         const char *text;
6195         const char *key;
6197         if (status && !string_format(file, "'%s'", status->new.name))
6198                 return;
6200         if (!status && line[1].type == LINE_STAT_NONE)
6201                 line++;
6203         switch (line->type) {
6204         case LINE_STAT_STAGED:
6205                 text = "Press %s to unstage %s for commit";
6206                 break;
6208         case LINE_STAT_UNSTAGED:
6209                 text = "Press %s to stage %s for commit";
6210                 break;
6212         case LINE_STAT_UNTRACKED:
6213                 text = "Press %s to stage %s for addition";
6214                 break;
6216         case LINE_STAT_HEAD:
6217         case LINE_STAT_NONE:
6218                 text = "Nothing to update";
6219                 break;
6221         default:
6222                 die("line type %d not handled in switch", line->type);
6223         }
6225         if (status && status->status == 'U') {
6226                 text = "Press %s to resolve conflict in %s";
6227                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6229         } else {
6230                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6231         }
6233         string_format(view->ref, text, key, file);
6234         if (status)
6235                 string_copy(opt_file, status->new.name);
6238 static bool
6239 status_grep(struct view *view, struct line *line)
6241         struct status *status = line->data;
6243         if (status) {
6244                 const char buf[2] = { status->status, 0 };
6245                 const char *text[] = { status->new.name, buf, NULL };
6247                 return grep_text(view, text);
6248         }
6250         return FALSE;
6253 static struct view_ops status_ops = {
6254         "file",
6255         NULL,
6256         status_open,
6257         NULL,
6258         status_draw,
6259         status_request,
6260         status_grep,
6261         status_select,
6262 };
6265 static bool
6266 stage_diff_write(struct io *io, struct line *line, struct line *end)
6268         while (line < end) {
6269                 if (!io_write(io, line->data, strlen(line->data)) ||
6270                     !io_write(io, "\n", 1))
6271                         return FALSE;
6272                 line++;
6273                 if (line->type == LINE_DIFF_CHUNK ||
6274                     line->type == LINE_DIFF_HEADER)
6275                         break;
6276         }
6278         return TRUE;
6281 static struct line *
6282 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6284         for (; view->line < line; line--)
6285                 if (line->type == type)
6286                         return line;
6288         return NULL;
6291 static bool
6292 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6294         const char *apply_argv[SIZEOF_ARG] = {
6295                 "git", "apply", "--whitespace=nowarn", NULL
6296         };
6297         struct line *diff_hdr;
6298         struct io io = {};
6299         int argc = 3;
6301         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6302         if (!diff_hdr)
6303                 return FALSE;
6305         if (!revert)
6306                 apply_argv[argc++] = "--cached";
6307         if (revert || stage_line_type == LINE_STAT_STAGED)
6308                 apply_argv[argc++] = "-R";
6309         apply_argv[argc++] = "-";
6310         apply_argv[argc++] = NULL;
6311         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6312                 return FALSE;
6314         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6315             !stage_diff_write(&io, chunk, view->line + view->lines))
6316                 chunk = NULL;
6318         io_done(&io);
6319         io_run_bg(update_index_argv);
6321         return chunk ? TRUE : FALSE;
6324 static bool
6325 stage_update(struct view *view, struct line *line)
6327         struct line *chunk = NULL;
6329         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6330                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6332         if (chunk) {
6333                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6334                         report("Failed to apply chunk");
6335                         return FALSE;
6336                 }
6338         } else if (!stage_status.status) {
6339                 view = VIEW(REQ_VIEW_STATUS);
6341                 for (line = view->line; line < view->line + view->lines; line++)
6342                         if (line->type == stage_line_type)
6343                                 break;
6345                 if (!status_update_files(view, line + 1)) {
6346                         report("Failed to update files");
6347                         return FALSE;
6348                 }
6350         } else if (!status_update_file(&stage_status, stage_line_type)) {
6351                 report("Failed to update file");
6352                 return FALSE;
6353         }
6355         return TRUE;
6358 static bool
6359 stage_revert(struct view *view, struct line *line)
6361         struct line *chunk = NULL;
6363         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6364                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6366         if (chunk) {
6367                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6368                         return FALSE;
6370                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6371                         report("Failed to revert chunk");
6372                         return FALSE;
6373                 }
6374                 return TRUE;
6376         } else {
6377                 return status_revert(stage_status.status ? &stage_status : NULL,
6378                                      stage_line_type, FALSE);
6379         }
6383 static void
6384 stage_next(struct view *view, struct line *line)
6386         int i;
6388         if (!stage_chunks) {
6389                 for (line = view->line; line < view->line + view->lines; line++) {
6390                         if (line->type != LINE_DIFF_CHUNK)
6391                                 continue;
6393                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6394                                 report("Allocation failure");
6395                                 return;
6396                         }
6398                         stage_chunk[stage_chunks++] = line - view->line;
6399                 }
6400         }
6402         for (i = 0; i < stage_chunks; i++) {
6403                 if (stage_chunk[i] > view->lineno) {
6404                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6405                         report("Chunk %d of %d", i + 1, stage_chunks);
6406                         return;
6407                 }
6408         }
6410         report("No next chunk found");
6413 static enum request
6414 stage_request(struct view *view, enum request request, struct line *line)
6416         switch (request) {
6417         case REQ_STATUS_UPDATE:
6418                 if (!stage_update(view, line))
6419                         return REQ_NONE;
6420                 break;
6422         case REQ_STATUS_REVERT:
6423                 if (!stage_revert(view, line))
6424                         return REQ_NONE;
6425                 break;
6427         case REQ_STAGE_NEXT:
6428                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6429                         report("File is untracked; press %s to add",
6430                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6431                         return REQ_NONE;
6432                 }
6433                 stage_next(view, line);
6434                 return REQ_NONE;
6436         case REQ_EDIT:
6437                 if (!stage_status.new.name[0])
6438                         return request;
6439                 if (stage_status.status == 'D') {
6440                         report("File has been deleted.");
6441                         return REQ_NONE;
6442                 }
6444                 open_editor(stage_status.new.name);
6445                 break;
6447         case REQ_REFRESH:
6448                 /* Reload everything ... */
6449                 break;
6451         case REQ_VIEW_BLAME:
6452                 if (stage_status.new.name[0]) {
6453                         string_copy(opt_file, stage_status.new.name);
6454                         opt_ref[0] = 0;
6455                 }
6456                 return request;
6458         case REQ_ENTER:
6459                 return pager_request(view, request, line);
6461         default:
6462                 return request;
6463         }
6465         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6466         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6468         /* Check whether the staged entry still exists, and close the
6469          * stage view if it doesn't. */
6470         if (!status_exists(&stage_status, stage_line_type)) {
6471                 status_restore(VIEW(REQ_VIEW_STATUS));
6472                 return REQ_VIEW_CLOSE;
6473         }
6475         if (stage_line_type == LINE_STAT_UNTRACKED) {
6476                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6477                         report("Cannot display a directory");
6478                         return REQ_NONE;
6479                 }
6481                 if (!prepare_update_file(view, stage_status.new.name)) {
6482                         report("Failed to open file: %s", strerror(errno));
6483                         return REQ_NONE;
6484                 }
6485         }
6486         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6488         return REQ_NONE;
6491 static struct view_ops stage_ops = {
6492         "line",
6493         NULL,
6494         NULL,
6495         pager_read,
6496         pager_draw,
6497         stage_request,
6498         pager_grep,
6499         pager_select,
6500 };
6503 /*
6504  * Revision graph
6505  */
6507 struct commit {
6508         char id[SIZEOF_REV];            /* SHA1 ID. */
6509         char title[128];                /* First line of the commit message. */
6510         const char *author;             /* Author of the commit. */
6511         struct time time;               /* Date from the author ident. */
6512         struct ref_list *refs;          /* Repository references. */
6513         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6514         size_t graph_size;              /* The width of the graph array. */
6515         bool has_parents;               /* Rewritten --parents seen. */
6516 };
6518 /* Size of rev graph with no  "padding" columns */
6519 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6521 struct rev_graph {
6522         struct rev_graph *prev, *next, *parents;
6523         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6524         size_t size;
6525         struct commit *commit;
6526         size_t pos;
6527         unsigned int boundary:1;
6528 };
6530 /* Parents of the commit being visualized. */
6531 static struct rev_graph graph_parents[4];
6533 /* The current stack of revisions on the graph. */
6534 static struct rev_graph graph_stacks[4] = {
6535         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6536         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6537         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6538         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6539 };
6541 static inline bool
6542 graph_parent_is_merge(struct rev_graph *graph)
6544         return graph->parents->size > 1;
6547 static inline void
6548 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6550         struct commit *commit = graph->commit;
6552         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6553                 commit->graph[commit->graph_size++] = symbol;
6556 static void
6557 clear_rev_graph(struct rev_graph *graph)
6559         graph->boundary = 0;
6560         graph->size = graph->pos = 0;
6561         graph->commit = NULL;
6562         memset(graph->parents, 0, sizeof(*graph->parents));
6565 static void
6566 done_rev_graph(struct rev_graph *graph)
6568         if (graph_parent_is_merge(graph) &&
6569             graph->pos < graph->size - 1 &&
6570             graph->next->size == graph->size + graph->parents->size - 1) {
6571                 size_t i = graph->pos + graph->parents->size - 1;
6573                 graph->commit->graph_size = i * 2;
6574                 while (i < graph->next->size - 1) {
6575                         append_to_rev_graph(graph, ' ');
6576                         append_to_rev_graph(graph, '\\');
6577                         i++;
6578                 }
6579         }
6581         clear_rev_graph(graph);
6584 static void
6585 push_rev_graph(struct rev_graph *graph, const char *parent)
6587         int i;
6589         /* "Collapse" duplicate parents lines.
6590          *
6591          * FIXME: This needs to also update update the drawn graph but
6592          * for now it just serves as a method for pruning graph lines. */
6593         for (i = 0; i < graph->size; i++)
6594                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6595                         return;
6597         if (graph->size < SIZEOF_REVITEMS) {
6598                 string_copy_rev(graph->rev[graph->size++], parent);
6599         }
6602 static chtype
6603 get_rev_graph_symbol(struct rev_graph *graph)
6605         chtype symbol;
6607         if (graph->boundary)
6608                 symbol = REVGRAPH_BOUND;
6609         else if (graph->parents->size == 0)
6610                 symbol = REVGRAPH_INIT;
6611         else if (graph_parent_is_merge(graph))
6612                 symbol = REVGRAPH_MERGE;
6613         else if (graph->pos >= graph->size)
6614                 symbol = REVGRAPH_BRANCH;
6615         else
6616                 symbol = REVGRAPH_COMMIT;
6618         return symbol;
6621 static void
6622 draw_rev_graph(struct rev_graph *graph)
6624         struct rev_filler {
6625                 chtype separator, line;
6626         };
6627         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6628         static struct rev_filler fillers[] = {
6629                 { ' ',  '|' },
6630                 { '`',  '.' },
6631                 { '\'', ' ' },
6632                 { '/',  ' ' },
6633         };
6634         chtype symbol = get_rev_graph_symbol(graph);
6635         struct rev_filler *filler;
6636         size_t i;
6638         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6639         filler = &fillers[DEFAULT];
6641         for (i = 0; i < graph->pos; i++) {
6642                 append_to_rev_graph(graph, filler->line);
6643                 if (graph_parent_is_merge(graph->prev) &&
6644                     graph->prev->pos == i)
6645                         filler = &fillers[RSHARP];
6647                 append_to_rev_graph(graph, filler->separator);
6648         }
6650         /* Place the symbol for this revision. */
6651         append_to_rev_graph(graph, symbol);
6653         if (graph->prev->size > graph->size)
6654                 filler = &fillers[RDIAG];
6655         else
6656                 filler = &fillers[DEFAULT];
6658         i++;
6660         for (; i < graph->size; i++) {
6661                 append_to_rev_graph(graph, filler->separator);
6662                 append_to_rev_graph(graph, filler->line);
6663                 if (graph_parent_is_merge(graph->prev) &&
6664                     i < graph->prev->pos + graph->parents->size)
6665                         filler = &fillers[RSHARP];
6666                 if (graph->prev->size > graph->size)
6667                         filler = &fillers[LDIAG];
6668         }
6670         if (graph->prev->size > graph->size) {
6671                 append_to_rev_graph(graph, filler->separator);
6672                 if (filler->line != ' ')
6673                         append_to_rev_graph(graph, filler->line);
6674         }
6677 /* Prepare the next rev graph */
6678 static void
6679 prepare_rev_graph(struct rev_graph *graph)
6681         size_t i;
6683         /* First, traverse all lines of revisions up to the active one. */
6684         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6685                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6686                         break;
6688                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6689         }
6691         /* Interleave the new revision parent(s). */
6692         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6693                 push_rev_graph(graph->next, graph->parents->rev[i]);
6695         /* Lastly, put any remaining revisions. */
6696         for (i = graph->pos + 1; i < graph->size; i++)
6697                 push_rev_graph(graph->next, graph->rev[i]);
6700 static void
6701 update_rev_graph(struct view *view, struct rev_graph *graph)
6703         /* If this is the finalizing update ... */
6704         if (graph->commit)
6705                 prepare_rev_graph(graph);
6707         /* Graph visualization needs a one rev look-ahead,
6708          * so the first update doesn't visualize anything. */
6709         if (!graph->prev->commit)
6710                 return;
6712         if (view->lines > 2)
6713                 view->line[view->lines - 3].dirty = 1;
6714         if (view->lines > 1)
6715                 view->line[view->lines - 2].dirty = 1;
6716         draw_rev_graph(graph->prev);
6717         done_rev_graph(graph->prev->prev);
6721 /*
6722  * Main view backend
6723  */
6725 static const char *main_argv[SIZEOF_ARG] = {
6726         "git", "log", "--no-color", "--pretty=raw", "--parents",
6727                       "--topo-order", "%(head)", NULL
6728 };
6730 static bool
6731 main_draw(struct view *view, struct line *line, unsigned int lineno)
6733         struct commit *commit = line->data;
6735         if (!commit->author)
6736                 return FALSE;
6738         if (opt_date && draw_date(view, &commit->time))
6739                 return TRUE;
6741         if (opt_author && draw_author(view, commit->author))
6742                 return TRUE;
6744         if (opt_rev_graph && commit->graph_size &&
6745             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6746                 return TRUE;
6748         if (opt_show_refs && commit->refs) {
6749                 size_t i;
6751                 for (i = 0; i < commit->refs->size; i++) {
6752                         struct ref *ref = commit->refs->refs[i];
6753                         enum line_type type;
6755                         if (ref->head)
6756                                 type = LINE_MAIN_HEAD;
6757                         else if (ref->ltag)
6758                                 type = LINE_MAIN_LOCAL_TAG;
6759                         else if (ref->tag)
6760                                 type = LINE_MAIN_TAG;
6761                         else if (ref->tracked)
6762                                 type = LINE_MAIN_TRACKED;
6763                         else if (ref->remote)
6764                                 type = LINE_MAIN_REMOTE;
6765                         else
6766                                 type = LINE_MAIN_REF;
6768                         if (draw_text(view, type, "[", TRUE) ||
6769                             draw_text(view, type, ref->name, TRUE) ||
6770                             draw_text(view, type, "]", TRUE))
6771                                 return TRUE;
6773                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6774                                 return TRUE;
6775                 }
6776         }
6778         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6779         return TRUE;
6782 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6783 static bool
6784 main_read(struct view *view, char *line)
6786         static struct rev_graph *graph = graph_stacks;
6787         enum line_type type;
6788         struct commit *commit;
6790         if (!line) {
6791                 int i;
6793                 if (!view->lines && !view->prev)
6794                         die("No revisions match the given arguments.");
6795                 if (view->lines > 0) {
6796                         commit = view->line[view->lines - 1].data;
6797                         view->line[view->lines - 1].dirty = 1;
6798                         if (!commit->author) {
6799                                 view->lines--;
6800                                 free(commit);
6801                                 graph->commit = NULL;
6802                         }
6803                 }
6804                 update_rev_graph(view, graph);
6806                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6807                         clear_rev_graph(&graph_stacks[i]);
6808                 return TRUE;
6809         }
6811         type = get_line_type(line);
6812         if (type == LINE_COMMIT) {
6813                 commit = calloc(1, sizeof(struct commit));
6814                 if (!commit)
6815                         return FALSE;
6817                 line += STRING_SIZE("commit ");
6818                 if (*line == '-') {
6819                         graph->boundary = 1;
6820                         line++;
6821                 }
6823                 string_copy_rev(commit->id, line);
6824                 commit->refs = get_ref_list(commit->id);
6825                 graph->commit = commit;
6826                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6828                 while ((line = strchr(line, ' '))) {
6829                         line++;
6830                         push_rev_graph(graph->parents, line);
6831                         commit->has_parents = TRUE;
6832                 }
6833                 return TRUE;
6834         }
6836         if (!view->lines)
6837                 return TRUE;
6838         commit = view->line[view->lines - 1].data;
6840         switch (type) {
6841         case LINE_PARENT:
6842                 if (commit->has_parents)
6843                         break;
6844                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6845                 break;
6847         case LINE_AUTHOR:
6848                 parse_author_line(line + STRING_SIZE("author "),
6849                                   &commit->author, &commit->time);
6850                 update_rev_graph(view, graph);
6851                 graph = graph->next;
6852                 break;
6854         default:
6855                 /* Fill in the commit title if it has not already been set. */
6856                 if (commit->title[0])
6857                         break;
6859                 /* Require titles to start with a non-space character at the
6860                  * offset used by git log. */
6861                 if (strncmp(line, "    ", 4))
6862                         break;
6863                 line += 4;
6864                 /* Well, if the title starts with a whitespace character,
6865                  * try to be forgiving.  Otherwise we end up with no title. */
6866                 while (isspace(*line))
6867                         line++;
6868                 if (*line == '\0')
6869                         break;
6870                 /* FIXME: More graceful handling of titles; append "..." to
6871                  * shortened titles, etc. */
6873                 string_expand(commit->title, sizeof(commit->title), line, 1);
6874                 view->line[view->lines - 1].dirty = 1;
6875         }
6877         return TRUE;
6880 static enum request
6881 main_request(struct view *view, enum request request, struct line *line)
6883         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6885         switch (request) {
6886         case REQ_ENTER:
6887                 open_view(view, REQ_VIEW_DIFF, flags);
6888                 break;
6889         case REQ_REFRESH:
6890                 load_refs();
6891                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6892                 break;
6893         default:
6894                 return request;
6895         }
6897         return REQ_NONE;
6900 static bool
6901 grep_refs(struct ref_list *list, regex_t *regex)
6903         regmatch_t pmatch;
6904         size_t i;
6906         if (!opt_show_refs || !list)
6907                 return FALSE;
6909         for (i = 0; i < list->size; i++) {
6910                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6911                         return TRUE;
6912         }
6914         return FALSE;
6917 static bool
6918 main_grep(struct view *view, struct line *line)
6920         struct commit *commit = line->data;
6921         const char *text[] = {
6922                 commit->title,
6923                 opt_author ? commit->author : "",
6924                 mkdate(&commit->time, opt_date),
6925                 NULL
6926         };
6928         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6931 static void
6932 main_select(struct view *view, struct line *line)
6934         struct commit *commit = line->data;
6936         string_copy_rev(view->ref, commit->id);
6937         string_copy_rev(ref_commit, view->ref);
6940 static struct view_ops main_ops = {
6941         "commit",
6942         main_argv,
6943         NULL,
6944         main_read,
6945         main_draw,
6946         main_request,
6947         main_grep,
6948         main_select,
6949 };
6952 /*
6953  * Status management
6954  */
6956 /* Whether or not the curses interface has been initialized. */
6957 static bool cursed = FALSE;
6959 /* Terminal hacks and workarounds. */
6960 static bool use_scroll_redrawwin;
6961 static bool use_scroll_status_wclear;
6963 /* The status window is used for polling keystrokes. */
6964 static WINDOW *status_win;
6966 /* Reading from the prompt? */
6967 static bool input_mode = FALSE;
6969 static bool status_empty = FALSE;
6971 /* Update status and title window. */
6972 static void
6973 report(const char *msg, ...)
6975         struct view *view = display[current_view];
6977         if (input_mode)
6978                 return;
6980         if (!view) {
6981                 char buf[SIZEOF_STR];
6982                 va_list args;
6984                 va_start(args, msg);
6985                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6986                         buf[sizeof(buf) - 1] = 0;
6987                         buf[sizeof(buf) - 2] = '.';
6988                         buf[sizeof(buf) - 3] = '.';
6989                         buf[sizeof(buf) - 4] = '.';
6990                 }
6991                 va_end(args);
6992                 die("%s", buf);
6993         }
6995         if (!status_empty || *msg) {
6996                 va_list args;
6998                 va_start(args, msg);
7000                 wmove(status_win, 0, 0);
7001                 if (view->has_scrolled && use_scroll_status_wclear)
7002                         wclear(status_win);
7003                 if (*msg) {
7004                         vwprintw(status_win, msg, args);
7005                         status_empty = FALSE;
7006                 } else {
7007                         status_empty = TRUE;
7008                 }
7009                 wclrtoeol(status_win);
7010                 wnoutrefresh(status_win);
7012                 va_end(args);
7013         }
7015         update_view_title(view);
7018 static void
7019 init_display(void)
7021         const char *term;
7022         int x, y;
7024         /* Initialize the curses library */
7025         if (isatty(STDIN_FILENO)) {
7026                 cursed = !!initscr();
7027                 opt_tty = stdin;
7028         } else {
7029                 /* Leave stdin and stdout alone when acting as a pager. */
7030                 opt_tty = fopen("/dev/tty", "r+");
7031                 if (!opt_tty)
7032                         die("Failed to open /dev/tty");
7033                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7034         }
7036         if (!cursed)
7037                 die("Failed to initialize curses");
7039         nonl();         /* Disable conversion and detect newlines from input. */
7040         cbreak();       /* Take input chars one at a time, no wait for \n */
7041         noecho();       /* Don't echo input */
7042         leaveok(stdscr, FALSE);
7044         if (has_colors())
7045                 init_colors();
7047         getmaxyx(stdscr, y, x);
7048         status_win = newwin(1, 0, y - 1, 0);
7049         if (!status_win)
7050                 die("Failed to create status window");
7052         /* Enable keyboard mapping */
7053         keypad(status_win, TRUE);
7054         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7056         TABSIZE = opt_tab_size;
7058         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7059         if (term && !strcmp(term, "gnome-terminal")) {
7060                 /* In the gnome-terminal-emulator, the message from
7061                  * scrolling up one line when impossible followed by
7062                  * scrolling down one line causes corruption of the
7063                  * status line. This is fixed by calling wclear. */
7064                 use_scroll_status_wclear = TRUE;
7065                 use_scroll_redrawwin = FALSE;
7067         } else if (term && !strcmp(term, "xrvt-xpm")) {
7068                 /* No problems with full optimizations in xrvt-(unicode)
7069                  * and aterm. */
7070                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7072         } else {
7073                 /* When scrolling in (u)xterm the last line in the
7074                  * scrolling direction will update slowly. */
7075                 use_scroll_redrawwin = TRUE;
7076                 use_scroll_status_wclear = FALSE;
7077         }
7080 static int
7081 get_input(int prompt_position)
7083         struct view *view;
7084         int i, key, cursor_y, cursor_x;
7085         bool loading = FALSE;
7087         if (prompt_position)
7088                 input_mode = TRUE;
7090         while (TRUE) {
7091                 foreach_view (view, i) {
7092                         update_view(view);
7093                         if (view_is_displayed(view) && view->has_scrolled &&
7094                             use_scroll_redrawwin)
7095                                 redrawwin(view->win);
7096                         view->has_scrolled = FALSE;
7097                         if (view->pipe)
7098                                 loading = TRUE;
7099                 }
7101                 /* Update the cursor position. */
7102                 if (prompt_position) {
7103                         getbegyx(status_win, cursor_y, cursor_x);
7104                         cursor_x = prompt_position;
7105                 } else {
7106                         view = display[current_view];
7107                         getbegyx(view->win, cursor_y, cursor_x);
7108                         cursor_x = view->width - 1;
7109                         cursor_y += view->lineno - view->offset;
7110                 }
7111                 setsyx(cursor_y, cursor_x);
7113                 /* Refresh, accept single keystroke of input */
7114                 doupdate();
7115                 nodelay(status_win, loading);
7116                 key = wgetch(status_win);
7118                 /* wgetch() with nodelay() enabled returns ERR when
7119                  * there's no input. */
7120                 if (key == ERR) {
7122                 } else if (key == KEY_RESIZE) {
7123                         int height, width;
7125                         getmaxyx(stdscr, height, width);
7127                         wresize(status_win, 1, width);
7128                         mvwin(status_win, height - 1, 0);
7129                         wnoutrefresh(status_win);
7130                         resize_display();
7131                         redraw_display(TRUE);
7133                 } else {
7134                         input_mode = FALSE;
7135                         return key;
7136                 }
7137         }
7140 static char *
7141 prompt_input(const char *prompt, input_handler handler, void *data)
7143         enum input_status status = INPUT_OK;
7144         static char buf[SIZEOF_STR];
7145         size_t pos = 0;
7147         buf[pos] = 0;
7149         while (status == INPUT_OK || status == INPUT_SKIP) {
7150                 int key;
7152                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7153                 wclrtoeol(status_win);
7155                 key = get_input(pos + 1);
7156                 switch (key) {
7157                 case KEY_RETURN:
7158                 case KEY_ENTER:
7159                 case '\n':
7160                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7161                         break;
7163                 case KEY_BACKSPACE:
7164                         if (pos > 0)
7165                                 buf[--pos] = 0;
7166                         else
7167                                 status = INPUT_CANCEL;
7168                         break;
7170                 case KEY_ESC:
7171                         status = INPUT_CANCEL;
7172                         break;
7174                 default:
7175                         if (pos >= sizeof(buf)) {
7176                                 report("Input string too long");
7177                                 return NULL;
7178                         }
7180                         status = handler(data, buf, key);
7181                         if (status == INPUT_OK)
7182                                 buf[pos++] = (char) key;
7183                 }
7184         }
7186         /* Clear the status window */
7187         status_empty = FALSE;
7188         report("");
7190         if (status == INPUT_CANCEL)
7191                 return NULL;
7193         buf[pos++] = 0;
7195         return buf;
7198 static enum input_status
7199 prompt_yesno_handler(void *data, char *buf, int c)
7201         if (c == 'y' || c == 'Y')
7202                 return INPUT_STOP;
7203         if (c == 'n' || c == 'N')
7204                 return INPUT_CANCEL;
7205         return INPUT_SKIP;
7208 static bool
7209 prompt_yesno(const char *prompt)
7211         char prompt2[SIZEOF_STR];
7213         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7214                 return FALSE;
7216         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7219 static enum input_status
7220 read_prompt_handler(void *data, char *buf, int c)
7222         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7225 static char *
7226 read_prompt(const char *prompt)
7228         return prompt_input(prompt, read_prompt_handler, NULL);
7231 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7233         enum input_status status = INPUT_OK;
7234         int size = 0;
7236         while (items[size].text)
7237                 size++;
7239         while (status == INPUT_OK) {
7240                 const struct menu_item *item = &items[*selected];
7241                 int key;
7242                 int i;
7244                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7245                           prompt, *selected + 1, size);
7246                 if (item->hotkey)
7247                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7248                 wprintw(status_win, "%s", item->text);
7249                 wclrtoeol(status_win);
7251                 key = get_input(COLS - 1);
7252                 switch (key) {
7253                 case KEY_RETURN:
7254                 case KEY_ENTER:
7255                 case '\n':
7256                         status = INPUT_STOP;
7257                         break;
7259                 case KEY_LEFT:
7260                 case KEY_UP:
7261                         *selected = *selected - 1;
7262                         if (*selected < 0)
7263                                 *selected = size - 1;
7264                         break;
7266                 case KEY_RIGHT:
7267                 case KEY_DOWN:
7268                         *selected = (*selected + 1) % size;
7269                         break;
7271                 case KEY_ESC:
7272                         status = INPUT_CANCEL;
7273                         break;
7275                 default:
7276                         for (i = 0; items[i].text; i++)
7277                                 if (items[i].hotkey == key) {
7278                                         *selected = i;
7279                                         status = INPUT_STOP;
7280                                         break;
7281                                 }
7282                 }
7283         }
7285         /* Clear the status window */
7286         status_empty = FALSE;
7287         report("");
7289         return status != INPUT_CANCEL;
7292 /*
7293  * Repository properties
7294  */
7296 static struct ref **refs = NULL;
7297 static size_t refs_size = 0;
7298 static struct ref *refs_head = NULL;
7300 static struct ref_list **ref_lists = NULL;
7301 static size_t ref_lists_size = 0;
7303 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7304 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7305 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7307 static int
7308 compare_refs(const void *ref1_, const void *ref2_)
7310         const struct ref *ref1 = *(const struct ref **)ref1_;
7311         const struct ref *ref2 = *(const struct ref **)ref2_;
7313         if (ref1->tag != ref2->tag)
7314                 return ref2->tag - ref1->tag;
7315         if (ref1->ltag != ref2->ltag)
7316                 return ref2->ltag - ref2->ltag;
7317         if (ref1->head != ref2->head)
7318                 return ref2->head - ref1->head;
7319         if (ref1->tracked != ref2->tracked)
7320                 return ref2->tracked - ref1->tracked;
7321         if (ref1->remote != ref2->remote)
7322                 return ref2->remote - ref1->remote;
7323         return strcmp(ref1->name, ref2->name);
7326 static void
7327 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7329         size_t i;
7331         for (i = 0; i < refs_size; i++)
7332                 if (!visitor(data, refs[i]))
7333                         break;
7336 static struct ref *
7337 get_ref_head()
7339         return refs_head;
7342 static struct ref_list *
7343 get_ref_list(const char *id)
7345         struct ref_list *list;
7346         size_t i;
7348         for (i = 0; i < ref_lists_size; i++)
7349                 if (!strcmp(id, ref_lists[i]->id))
7350                         return ref_lists[i];
7352         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7353                 return NULL;
7354         list = calloc(1, sizeof(*list));
7355         if (!list)
7356                 return NULL;
7358         for (i = 0; i < refs_size; i++) {
7359                 if (!strcmp(id, refs[i]->id) &&
7360                     realloc_refs_list(&list->refs, list->size, 1))
7361                         list->refs[list->size++] = refs[i];
7362         }
7364         if (!list->refs) {
7365                 free(list);
7366                 return NULL;
7367         }
7369         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7370         ref_lists[ref_lists_size++] = list;
7371         return list;
7374 static int
7375 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7377         struct ref *ref = NULL;
7378         bool tag = FALSE;
7379         bool ltag = FALSE;
7380         bool remote = FALSE;
7381         bool tracked = FALSE;
7382         bool head = FALSE;
7383         int from = 0, to = refs_size - 1;
7385         if (!prefixcmp(name, "refs/tags/")) {
7386                 if (!suffixcmp(name, namelen, "^{}")) {
7387                         namelen -= 3;
7388                         name[namelen] = 0;
7389                 } else {
7390                         ltag = TRUE;
7391                 }
7393                 tag = TRUE;
7394                 namelen -= STRING_SIZE("refs/tags/");
7395                 name    += STRING_SIZE("refs/tags/");
7397         } else if (!prefixcmp(name, "refs/remotes/")) {
7398                 remote = TRUE;
7399                 namelen -= STRING_SIZE("refs/remotes/");
7400                 name    += STRING_SIZE("refs/remotes/");
7401                 tracked  = !strcmp(opt_remote, name);
7403         } else if (!prefixcmp(name, "refs/heads/")) {
7404                 namelen -= STRING_SIZE("refs/heads/");
7405                 name    += STRING_SIZE("refs/heads/");
7406                 if (!strncmp(opt_head, name, namelen))
7407                         return OK;
7409         } else if (!strcmp(name, "HEAD")) {
7410                 head     = TRUE;
7411                 if (*opt_head) {
7412                         namelen  = strlen(opt_head);
7413                         name     = opt_head;
7414                 }
7415         }
7417         /* If we are reloading or it's an annotated tag, replace the
7418          * previous SHA1 with the resolved commit id; relies on the fact
7419          * git-ls-remote lists the commit id of an annotated tag right
7420          * before the commit id it points to. */
7421         while (from <= to) {
7422                 size_t pos = (to + from) / 2;
7423                 int cmp = strcmp(name, refs[pos]->name);
7425                 if (!cmp) {
7426                         ref = refs[pos];
7427                         break;
7428                 }
7430                 if (cmp < 0)
7431                         to = pos - 1;
7432                 else
7433                         from = pos + 1;
7434         }
7436         if (!ref) {
7437                 if (!realloc_refs(&refs, refs_size, 1))
7438                         return ERR;
7439                 ref = calloc(1, sizeof(*ref) + namelen);
7440                 if (!ref)
7441                         return ERR;
7442                 memmove(refs + from + 1, refs + from,
7443                         (refs_size - from) * sizeof(*refs));
7444                 refs[from] = ref;
7445                 strncpy(ref->name, name, namelen);
7446                 refs_size++;
7447         }
7449         ref->head = head;
7450         ref->tag = tag;
7451         ref->ltag = ltag;
7452         ref->remote = remote;
7453         ref->tracked = tracked;
7454         string_copy_rev(ref->id, id);
7456         if (head)
7457                 refs_head = ref;
7458         return OK;
7461 static int
7462 load_refs(void)
7464         const char *head_argv[] = {
7465                 "git", "symbolic-ref", "HEAD", NULL
7466         };
7467         static const char *ls_remote_argv[SIZEOF_ARG] = {
7468                 "git", "ls-remote", opt_git_dir, NULL
7469         };
7470         static bool init = FALSE;
7471         size_t i;
7473         if (!init) {
7474                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7475                         die("TIG_LS_REMOTE contains too many arguments");
7476                 init = TRUE;
7477         }
7479         if (!*opt_git_dir)
7480                 return OK;
7482         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7483             !prefixcmp(opt_head, "refs/heads/")) {
7484                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7486                 memmove(opt_head, offset, strlen(offset) + 1);
7487         }
7489         refs_head = NULL;
7490         for (i = 0; i < refs_size; i++)
7491                 refs[i]->id[0] = 0;
7493         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7494                 return ERR;
7496         /* Update the ref lists to reflect changes. */
7497         for (i = 0; i < ref_lists_size; i++) {
7498                 struct ref_list *list = ref_lists[i];
7499                 size_t old, new;
7501                 for (old = new = 0; old < list->size; old++)
7502                         if (!strcmp(list->id, list->refs[old]->id))
7503                                 list->refs[new++] = list->refs[old];
7504                 list->size = new;
7505         }
7507         return OK;
7510 static void
7511 set_remote_branch(const char *name, const char *value, size_t valuelen)
7513         if (!strcmp(name, ".remote")) {
7514                 string_ncopy(opt_remote, value, valuelen);
7516         } else if (*opt_remote && !strcmp(name, ".merge")) {
7517                 size_t from = strlen(opt_remote);
7519                 if (!prefixcmp(value, "refs/heads/"))
7520                         value += STRING_SIZE("refs/heads/");
7522                 if (!string_format_from(opt_remote, &from, "/%s", value))
7523                         opt_remote[0] = 0;
7524         }
7527 static void
7528 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7530         const char *argv[SIZEOF_ARG] = { name, "=" };
7531         int argc = 1 + (cmd == option_set_command);
7532         int error = ERR;
7534         if (!argv_from_string(argv, &argc, value))
7535                 config_msg = "Too many option arguments";
7536         else
7537                 error = cmd(argc, argv);
7539         if (error == ERR)
7540                 warn("Option 'tig.%s': %s", name, config_msg);
7543 static bool
7544 set_environment_variable(const char *name, const char *value)
7546         size_t len = strlen(name) + 1 + strlen(value) + 1;
7547         char *env = malloc(len);
7549         if (env &&
7550             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7551             putenv(env) == 0)
7552                 return TRUE;
7553         free(env);
7554         return FALSE;
7557 static void
7558 set_work_tree(const char *value)
7560         char cwd[SIZEOF_STR];
7562         if (!getcwd(cwd, sizeof(cwd)))
7563                 die("Failed to get cwd path: %s", strerror(errno));
7564         if (chdir(opt_git_dir) < 0)
7565                 die("Failed to chdir(%s): %s", strerror(errno));
7566         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7567                 die("Failed to get git path: %s", strerror(errno));
7568         if (chdir(cwd) < 0)
7569                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7570         if (chdir(value) < 0)
7571                 die("Failed to chdir(%s): %s", value, strerror(errno));
7572         if (!getcwd(cwd, sizeof(cwd)))
7573                 die("Failed to get cwd path: %s", strerror(errno));
7574         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7575                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7576         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7577                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7578         opt_is_inside_work_tree = TRUE;
7581 static int
7582 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7584         if (!strcmp(name, "i18n.commitencoding"))
7585                 string_ncopy(opt_encoding, value, valuelen);
7587         else if (!strcmp(name, "core.editor"))
7588                 string_ncopy(opt_editor, value, valuelen);
7590         else if (!strcmp(name, "core.worktree"))
7591                 set_work_tree(value);
7593         else if (!prefixcmp(name, "tig.color."))
7594                 set_repo_config_option(name + 10, value, option_color_command);
7596         else if (!prefixcmp(name, "tig.bind."))
7597                 set_repo_config_option(name + 9, value, option_bind_command);
7599         else if (!prefixcmp(name, "tig."))
7600                 set_repo_config_option(name + 4, value, option_set_command);
7602         else if (*opt_head && !prefixcmp(name, "branch.") &&
7603                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7604                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7606         return OK;
7609 static int
7610 load_git_config(void)
7612         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7614         return io_run_load(config_list_argv, "=", read_repo_config_option);
7617 static int
7618 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7620         if (!opt_git_dir[0]) {
7621                 string_ncopy(opt_git_dir, name, namelen);
7623         } else if (opt_is_inside_work_tree == -1) {
7624                 /* This can be 3 different values depending on the
7625                  * version of git being used. If git-rev-parse does not
7626                  * understand --is-inside-work-tree it will simply echo
7627                  * the option else either "true" or "false" is printed.
7628                  * Default to true for the unknown case. */
7629                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7631         } else if (*name == '.') {
7632                 string_ncopy(opt_cdup, name, namelen);
7634         } else {
7635                 string_ncopy(opt_prefix, name, namelen);
7636         }
7638         return OK;
7641 static int
7642 load_repo_info(void)
7644         const char *rev_parse_argv[] = {
7645                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7646                         "--show-cdup", "--show-prefix", NULL
7647         };
7649         return io_run_load(rev_parse_argv, "=", read_repo_info);
7653 /*
7654  * Main
7655  */
7657 static const char usage[] =
7658 "tig " TIG_VERSION " (" __DATE__ ")\n"
7659 "\n"
7660 "Usage: tig        [options] [revs] [--] [paths]\n"
7661 "   or: tig show   [options] [revs] [--] [paths]\n"
7662 "   or: tig blame  [rev] path\n"
7663 "   or: tig status\n"
7664 "   or: tig <      [git command output]\n"
7665 "\n"
7666 "Options:\n"
7667 "  -v, --version   Show version and exit\n"
7668 "  -h, --help      Show help message and exit";
7670 static void __NORETURN
7671 quit(int sig)
7673         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7674         if (cursed)
7675                 endwin();
7676         exit(0);
7679 static void __NORETURN
7680 die(const char *err, ...)
7682         va_list args;
7684         endwin();
7686         va_start(args, err);
7687         fputs("tig: ", stderr);
7688         vfprintf(stderr, err, args);
7689         fputs("\n", stderr);
7690         va_end(args);
7692         exit(1);
7695 static void
7696 warn(const char *msg, ...)
7698         va_list args;
7700         va_start(args, msg);
7701         fputs("tig warning: ", stderr);
7702         vfprintf(stderr, msg, args);
7703         fputs("\n", stderr);
7704         va_end(args);
7707 static enum request
7708 parse_options(int argc, const char *argv[])
7710         enum request request = REQ_VIEW_MAIN;
7711         const char *subcommand;
7712         bool seen_dashdash = FALSE;
7713         /* XXX: This is vulnerable to the user overriding options
7714          * required for the main view parser. */
7715         const char *custom_argv[SIZEOF_ARG] = {
7716                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7717                         "--topo-order", NULL
7718         };
7719         int i, j = 6;
7721         if (!isatty(STDIN_FILENO)) {
7722                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7723                 return REQ_VIEW_PAGER;
7724         }
7726         if (argc <= 1)
7727                 return REQ_NONE;
7729         subcommand = argv[1];
7730         if (!strcmp(subcommand, "status")) {
7731                 if (argc > 2)
7732                         warn("ignoring arguments after `%s'", subcommand);
7733                 return REQ_VIEW_STATUS;
7735         } else if (!strcmp(subcommand, "blame")) {
7736                 if (argc <= 2 || argc > 4)
7737                         die("invalid number of options to blame\n\n%s", usage);
7739                 i = 2;
7740                 if (argc == 4) {
7741                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7742                         i++;
7743                 }
7745                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7746                 return REQ_VIEW_BLAME;
7748         } else if (!strcmp(subcommand, "show")) {
7749                 request = REQ_VIEW_DIFF;
7751         } else {
7752                 subcommand = NULL;
7753         }
7755         if (subcommand) {
7756                 custom_argv[1] = subcommand;
7757                 j = 2;
7758         }
7760         for (i = 1 + !!subcommand; i < argc; i++) {
7761                 const char *opt = argv[i];
7763                 if (seen_dashdash || !strcmp(opt, "--")) {
7764                         seen_dashdash = TRUE;
7766                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7767                         printf("tig version %s\n", TIG_VERSION);
7768                         quit(0);
7770                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7771                         printf("%s\n", usage);
7772                         quit(0);
7773                 }
7775                 custom_argv[j++] = opt;
7776                 if (j >= ARRAY_SIZE(custom_argv))
7777                         die("command too long");
7778         }
7780         if (!prepare_update(VIEW(request), custom_argv, NULL))
7781                 die("Failed to format arguments");
7783         return request;
7786 int
7787 main(int argc, const char *argv[])
7789         const char *codeset = "UTF-8";
7790         enum request request = parse_options(argc, argv);
7791         struct view *view;
7792         size_t i;
7794         signal(SIGINT, quit);
7795         signal(SIGPIPE, SIG_IGN);
7797         if (setlocale(LC_ALL, "")) {
7798                 codeset = nl_langinfo(CODESET);
7799         }
7801         if (load_repo_info() == ERR)
7802                 die("Failed to load repo info.");
7804         if (load_options() == ERR)
7805                 die("Failed to load user config.");
7807         if (load_git_config() == ERR)
7808                 die("Failed to load repo config.");
7810         /* Require a git repository unless when running in pager mode. */
7811         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7812                 die("Not a git repository");
7814         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7815                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7816                 if (opt_iconv_in == ICONV_NONE)
7817                         die("Failed to initialize character set conversion");
7818         }
7820         if (codeset && strcmp(codeset, "UTF-8")) {
7821                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7822                 if (opt_iconv_out == ICONV_NONE)
7823                         die("Failed to initialize character set conversion");
7824         }
7826         if (load_refs() == ERR)
7827                 die("Failed to load refs.");
7829         foreach_view (view, i)
7830                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7831                         die("Too many arguments in the `%s` environment variable",
7832                             view->cmd_env);
7834         init_display();
7836         if (request != REQ_NONE)
7837                 open_view(NULL, request, OPEN_PREPARED);
7838         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7840         while (view_driver(display[current_view], request)) {
7841                 int key = get_input(0);
7843                 view = display[current_view];
7844                 request = get_keybinding(view->keymap, key);
7846                 /* Some low-level request handling. This keeps access to
7847                  * status_win restricted. */
7848                 switch (request) {
7849                 case REQ_NONE:
7850                         report("Unknown key, press %s for help",
7851                                get_key(view->keymap, REQ_VIEW_HELP));
7852                         break;
7853                 case REQ_PROMPT:
7854                 {
7855                         char *cmd = read_prompt(":");
7857                         if (cmd && isdigit(*cmd)) {
7858                                 int lineno = view->lineno + 1;
7860                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7861                                         select_view_line(view, lineno - 1);
7862                                         report("");
7863                                 } else {
7864                                         report("Unable to parse '%s' as a line number", cmd);
7865                                 }
7867                         } else if (cmd) {
7868                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7869                                 const char *argv[SIZEOF_ARG] = { "git" };
7870                                 int argc = 1;
7872                                 /* When running random commands, initially show the
7873                                  * command in the title. However, it maybe later be
7874                                  * overwritten if a commit line is selected. */
7875                                 string_ncopy(next->ref, cmd, strlen(cmd));
7877                                 if (!argv_from_string(argv, &argc, cmd)) {
7878                                         report("Too many arguments");
7879                                 } else if (!prepare_update(next, argv, NULL)) {
7880                                         report("Failed to format command");
7881                                 } else {
7882                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7883                                 }
7884                         }
7886                         request = REQ_NONE;
7887                         break;
7888                 }
7889                 case REQ_SEARCH:
7890                 case REQ_SEARCH_BACK:
7891                 {
7892                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7893                         char *search = read_prompt(prompt);
7895                         if (search)
7896                                 string_ncopy(opt_search, search, strlen(search));
7897                         else if (*opt_search)
7898                                 request = request == REQ_SEARCH ?
7899                                         REQ_FIND_NEXT :
7900                                         REQ_FIND_PREV;
7901                         else
7902                                 request = REQ_NONE;
7903                         break;
7904                 }
7905                 default:
7906                         break;
7907                 }
7908         }
7910         quit(0);
7912         return 0;