Code

Mark the argument array as freed in argv_free
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int head:1;    /* Is it the current HEAD? */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int ltag:1;    /* If so, is the tag local? */
127         unsigned int remote:1;  /* Is it a remote ref? */
128         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129         char name[1];           /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         size_t size;            /* Number of refs. */
135         struct ref **refs;      /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151         INPUT_OK,
152         INPUT_SKIP,
153         INPUT_STOP,
154         INPUT_CANCEL
155 };
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163         int hotkey;
164         const char *text;
165         void *data;
166 };
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
170 /*
171  * Allocation helpers ... Entering macro hell to never be seen again.
172  */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
175 static type *                                                                   \
176 name(type **mem, size_t size, size_t increase)                                  \
177 {                                                                               \
178         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
179         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180         type *tmp = *mem;                                                       \
181                                                                                 \
182         if (mem == NULL || num_chunks != num_chunks_new) {                      \
183                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184                 if (tmp)                                                        \
185                         *mem = tmp;                                             \
186         }                                                                       \
187                                                                                 \
188         return tmp;                                                             \
191 /*
192  * String helpers
193  */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198         if (srclen > dstlen - 1)
199                 srclen = dstlen - 1;
201         strncpy(dst, src, srclen);
202         dst[srclen] = 0;
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211         string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
222         size_t size, pos;
224         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225                 if (src[pos] == '\t') {
226                         size_t expanded = tabsize - (size % tabsize);
228                         if (expanded + size >= dstlen - 1)
229                                 expanded = dstlen - size - 1;
230                         memcpy(dst + size, "        ", expanded);
231                         size += expanded;
232                 } else {
233                         dst[size++] = src[pos];
234                 }
235         }
237         dst[size] = 0;
240 static char *
241 chomp_string(char *name)
243         int namelen;
245         while (isspace(*name))
246                 name++;
248         namelen = strlen(name) - 1;
249         while (namelen > 0 && isspace(name[namelen]))
250                 name[namelen--] = 0;
252         return name;
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
258         va_list args;
259         size_t pos = bufpos ? *bufpos : 0;
261         va_start(args, fmt);
262         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263         va_end(args);
265         if (bufpos)
266                 *bufpos = pos;
268         return pos >= bufsize ? FALSE : TRUE;
271 #define string_format(buf, fmt, args...) \
272         string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275         string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
280         size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284         /* Diff-Header == DIFF_HEADER */
285         for (i = 0; i < len; i++) {
286                 if (toupper(str1[i]) == toupper(str2[i]))
287                         continue;
289                 if (string_enum_sep(str1[i]) &&
290                     string_enum_sep(str2[i]))
291                         continue;
293                 return str1[i] - str2[i];
294         }
296         return 0;
299 #define enum_equals(entry, str, len) \
300         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303         const char *name;
304         int namelen;
305         int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
313         static char buf[SIZEOF_STR];
314         int bufpos;
316         for (bufpos = 0; bufpos <= namelen; bufpos++) {
317                 buf[bufpos] = tolower(name[bufpos]);
318                 if (buf[bufpos] == '_')
319                         buf[bufpos] = '-';
320         }
322         buf[bufpos] = 0;
323         return buf;
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
331         size_t namelen = strlen(name);
332         int i;
334         for (i = 0; i < map_size; i++)
335                 if (enum_equals(map[i], name, namelen)) {
336                         *value = map[i].value;
337                         return TRUE;
338                 }
340         return FALSE;
343 #define map_enum(attr, map, name) \
344         map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347         strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
352         size_t len = slen >= 0 ? slen : strlen(str);
353         size_t suffixlen = strlen(suffix);
355         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
359 /*
360  * Unicode / UTF-8 handling
361  *
362  * NOTE: Much of the following code for dealing with Unicode is derived from
363  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
365  */
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
370         if (c >= 0x1100 &&
371            (c <= 0x115f                         /* Hangul Jamo */
372             || c == 0x2329
373             || c == 0x232a
374             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
375                                                 /* CJK ... Yi */
376             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
377             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
378             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
379             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
380             || (c >= 0xffe0  && c <= 0xffe6)
381             || (c >= 0x20000 && c <= 0x2fffd)
382             || (c >= 0x30000 && c <= 0x3fffd)))
383                 return 2;
385         if (c == '\t')
386                 return tab_size;
388         return 1;
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392  * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
396         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
397         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
398         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
399         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
400         2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
401         3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
402 };
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
407         int c = *(unsigned char *) string;
409         return utf8_bytes[c];
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
416         unsigned long unicode;
418         switch (length) {
419         case 1:
420                 unicode  =   string[0];
421                 break;
422         case 2:
423                 unicode  =  (string[0] & 0x1f) << 6;
424                 unicode +=  (string[1] & 0x3f);
425                 break;
426         case 3:
427                 unicode  =  (string[0] & 0x0f) << 12;
428                 unicode += ((string[1] & 0x3f) << 6);
429                 unicode +=  (string[2] & 0x3f);
430                 break;
431         case 4:
432                 unicode  =  (string[0] & 0x0f) << 18;
433                 unicode += ((string[1] & 0x3f) << 12);
434                 unicode += ((string[2] & 0x3f) << 6);
435                 unicode +=  (string[3] & 0x3f);
436                 break;
437         case 5:
438                 unicode  =  (string[0] & 0x0f) << 24;
439                 unicode += ((string[1] & 0x3f) << 18);
440                 unicode += ((string[2] & 0x3f) << 12);
441                 unicode += ((string[3] & 0x3f) << 6);
442                 unicode +=  (string[4] & 0x3f);
443                 break;
444         case 6:
445                 unicode  =  (string[0] & 0x01) << 30;
446                 unicode += ((string[1] & 0x3f) << 24);
447                 unicode += ((string[2] & 0x3f) << 18);
448                 unicode += ((string[3] & 0x3f) << 12);
449                 unicode += ((string[4] & 0x3f) << 6);
450                 unicode +=  (string[5] & 0x3f);
451                 break;
452         default:
453                 return 0;
454         }
456         /* Invalid characters could return the special 0xfffd value but NUL
457          * should be just as good. */
458         return unicode > 0xffff ? 0 : unicode;
461 /* Calculates how much of string can be shown within the given maximum width
462  * and sets trimmed parameter to non-zero value if all of string could not be
463  * shown. If the reserve flag is TRUE, it will reserve at least one
464  * trailing character, which can be useful when drawing a delimiter.
465  *
466  * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
470         const char *string = *start;
471         const char *end = strchr(string, '\0');
472         unsigned char last_bytes = 0;
473         size_t last_ucwidth = 0;
475         *width = 0;
476         *trimmed = 0;
478         while (string < end) {
479                 unsigned char bytes = utf8_char_length(string, end);
480                 size_t ucwidth;
481                 unsigned long unicode;
483                 if (string + bytes > end)
484                         break;
486                 /* Change representation to figure out whether
487                  * it is a single- or double-width character. */
489                 unicode = utf8_to_unicode(string, bytes);
490                 /* FIXME: Graceful handling of invalid Unicode character. */
491                 if (!unicode)
492                         break;
494                 ucwidth = unicode_width(unicode, tab_size);
495                 if (skip > 0) {
496                         skip -= ucwidth <= skip ? ucwidth : skip;
497                         *start += bytes;
498                 }
499                 *width  += ucwidth;
500                 if (*width > max_width) {
501                         *trimmed = 1;
502                         *width -= ucwidth;
503                         if (reserve && *width == max_width) {
504                                 string -= last_bytes;
505                                 *width -= last_ucwidth;
506                         }
507                         break;
508                 }
510                 string  += bytes;
511                 last_bytes = ucwidth ? bytes : 0;
512                 last_ucwidth = ucwidth;
513         }
515         return string - *start;
519 #define DATE_INFO \
520         DATE_(NO), \
521         DATE_(DEFAULT), \
522         DATE_(LOCAL), \
523         DATE_(RELATIVE), \
524         DATE_(SHORT)
526 enum date {
527 #define DATE_(name) DATE_##name
528         DATE_INFO
529 #undef  DATE_
530 };
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534         DATE_INFO
535 #undef  DATE_
536 };
538 struct time {
539         time_t sec;
540         int tz;
541 };
543 static inline int timecmp(const struct time *t1, const struct time *t2)
545         return t1->sec - t2->sec;
548 static const char *
549 mkdate(const struct time *time, enum date date)
551         static char buf[DATE_COLS + 1];
552         static const struct enum_map reldate[] = {
553                 { "second", 1,                  60 * 2 },
554                 { "minute", 60,                 60 * 60 * 2 },
555                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
556                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
557                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
558                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
559         };
560         struct tm tm;
562         if (!date || !time || !time->sec)
563                 return "";
565         if (date == DATE_RELATIVE) {
566                 struct timeval now;
567                 time_t date = time->sec + time->tz;
568                 time_t seconds;
569                 int i;
571                 gettimeofday(&now, NULL);
572                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
573                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
574                         if (seconds >= reldate[i].value)
575                                 continue;
577                         seconds /= reldate[i].namelen;
578                         if (!string_format(buf, "%ld %s%s %s",
579                                            seconds, reldate[i].name,
580                                            seconds > 1 ? "s" : "",
581                                            now.tv_sec >= date ? "ago" : "ahead"))
582                                 break;
583                         return buf;
584                 }
585         }
587         if (date == DATE_LOCAL) {
588                 time_t date = time->sec + time->tz;
589                 localtime_r(&date, &tm);
590         }
591         else {
592                 gmtime_r(&time->sec, &tm);
593         }
594         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
598 #define AUTHOR_VALUES \
599         AUTHOR_(NO), \
600         AUTHOR_(FULL), \
601         AUTHOR_(ABBREVIATED)
603 enum author {
604 #define AUTHOR_(name) AUTHOR_##name
605         AUTHOR_VALUES,
606 #undef  AUTHOR_
607         AUTHOR_DEFAULT = AUTHOR_FULL
608 };
610 static const struct enum_map author_map[] = {
611 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
612         AUTHOR_VALUES
613 #undef  AUTHOR_
614 };
616 static const char *
617 get_author_initials(const char *author)
619         static char initials[AUTHOR_COLS * 6 + 1];
620         size_t pos = 0;
621         const char *end = strchr(author, '\0');
623 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
625         memset(initials, 0, sizeof(initials));
626         while (author < end) {
627                 unsigned char bytes;
628                 size_t i;
630                 while (is_initial_sep(*author))
631                         author++;
633                 bytes = utf8_char_length(author, end);
634                 if (bytes < sizeof(initials) - 1 - pos) {
635                         while (bytes--) {
636                                 initials[pos++] = *author++;
637                         }
638                 }
640                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
641                         if (i < sizeof(initials) - 1)
642                                 initials[i++] = *author;
643                 }
645                 initials[i++] = 0;
646         }
648         return initials;
652 static bool
653 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
655         int valuelen;
657         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
658                 bool advance = cmd[valuelen] != 0;
660                 cmd[valuelen] = 0;
661                 argv[(*argc)++] = chomp_string(cmd);
662                 cmd = chomp_string(cmd + valuelen + advance);
663         }
665         if (*argc < SIZEOF_ARG)
666                 argv[*argc] = NULL;
667         return *argc < SIZEOF_ARG;
670 static bool
671 argv_from_env(const char **argv, const char *name)
673         char *env = argv ? getenv(name) : NULL;
674         int argc = 0;
676         if (env && *env)
677                 env = strdup(env);
678         return !env || argv_from_string(argv, &argc, env);
681 static void
682 argv_free(const char *argv[])
684         int argc;
686         for (argc = 0; argv[argc]; argc++)
687                 free((void *) argv[argc]);
688         argv[0] = NULL;
691 static void
692 argv_copy(const char *dst[], const char *src[])
694         int argc;
696         for (argc = 0; src[argc]; argc++)
697                 dst[argc] = src[argc];
701 /*
702  * Executing external commands.
703  */
705 enum io_type {
706         IO_FD,                  /* File descriptor based IO. */
707         IO_BG,                  /* Execute command in the background. */
708         IO_FG,                  /* Execute command with same std{in,out,err}. */
709         IO_RD,                  /* Read only fork+exec IO. */
710         IO_WR,                  /* Write only fork+exec IO. */
711         IO_AP,                  /* Append fork+exec output to file. */
712 };
714 struct io {
715         enum io_type type;      /* The requested type of pipe. */
716         const char *dir;        /* Directory from which to execute. */
717         pid_t pid;              /* PID of spawned process. */
718         int pipe;               /* Pipe end for reading or writing. */
719         int error;              /* Error status. */
720         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
721         char *buf;              /* Read buffer. */
722         size_t bufalloc;        /* Allocated buffer size. */
723         size_t bufsize;         /* Buffer content size. */
724         char *bufpos;           /* Current buffer position. */
725         unsigned int eof:1;     /* Has end of file been reached. */
726 };
728 static void
729 io_reset(struct io *io)
731         io->pipe = -1;
732         io->pid = 0;
733         io->buf = io->bufpos = NULL;
734         io->bufalloc = io->bufsize = 0;
735         io->error = 0;
736         io->eof = 0;
739 static void
740 io_init(struct io *io, const char *dir, enum io_type type)
742         io_reset(io);
743         io->type = type;
744         io->dir = dir;
747 static void
748 io_prepare(struct io *io, const char *dir, enum io_type type, const char *argv[])
750         io_init(io, dir, type);
751         argv_copy(io->argv, argv);
754 static bool
755 io_format(struct io *io, const char *dir, enum io_type type,
756           const char *argv[], enum format_flags flags)
758         io_init(io, dir, type);
759         return format_argv(io->argv, argv, flags);
762 static bool
763 io_open(struct io *io, const char *fmt, ...)
765         char name[SIZEOF_STR] = "";
766         bool fits;
767         va_list args;
769         io_init(io, NULL, IO_FD);
771         va_start(args, fmt);
772         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
773         va_end(args);
775         if (!fits) {
776                 io->error = ENAMETOOLONG;
777                 return FALSE;
778         }
779         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
780         if (io->pipe == -1)
781                 io->error = errno;
782         return io->pipe != -1;
785 static bool
786 io_kill(struct io *io)
788         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
791 static bool
792 io_done(struct io *io)
794         pid_t pid = io->pid;
796         if (io->pipe != -1)
797                 close(io->pipe);
798         free(io->buf);
799         io_reset(io);
801         while (pid > 0) {
802                 int status;
803                 pid_t waiting = waitpid(pid, &status, 0);
805                 if (waiting < 0) {
806                         if (errno == EINTR)
807                                 continue;
808                         io->error = errno;
809                         return FALSE;
810                 }
812                 return waiting == pid &&
813                        !WIFSIGNALED(status) &&
814                        WIFEXITED(status) &&
815                        !WEXITSTATUS(status);
816         }
818         return TRUE;
821 static bool
822 io_start(struct io *io)
824         int pipefds[2] = { -1, -1 };
826         if (io->type == IO_FD)
827                 return TRUE;
829         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
830                 io->error = errno;
831                 return FALSE;
832         } else if (io->type == IO_AP) {
833                 pipefds[1] = io->pipe;
834         }
836         if ((io->pid = fork())) {
837                 if (io->pid == -1)
838                         io->error = errno;
839                 if (pipefds[!(io->type == IO_WR)] != -1)
840                         close(pipefds[!(io->type == IO_WR)]);
841                 if (io->pid != -1) {
842                         io->pipe = pipefds[!!(io->type == IO_WR)];
843                         return TRUE;
844                 }
846         } else {
847                 if (io->type != IO_FG) {
848                         int devnull = open("/dev/null", O_RDWR);
849                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
850                         int writefd = (io->type == IO_RD || io->type == IO_AP)
851                                                         ? pipefds[1] : devnull;
853                         dup2(readfd,  STDIN_FILENO);
854                         dup2(writefd, STDOUT_FILENO);
855                         dup2(devnull, STDERR_FILENO);
857                         close(devnull);
858                         if (pipefds[0] != -1)
859                                 close(pipefds[0]);
860                         if (pipefds[1] != -1)
861                                 close(pipefds[1]);
862                 }
864                 if (io->dir && *io->dir && chdir(io->dir) == -1)
865                         exit(errno);
867                 execvp(io->argv[0], (char *const*) io->argv);
868                 exit(errno);
869         }
871         if (pipefds[!!(io->type == IO_WR)] != -1)
872                 close(pipefds[!!(io->type == IO_WR)]);
873         return FALSE;
876 static bool
877 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
879         io_prepare(io, dir, type, argv);
880         return io_start(io);
883 static bool
884 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
886         struct io io = {};
888         io_prepare(&io, dir, type, argv);
889         io.pipe = fd;
890         return io_start(&io) && io_done(&io);
893 static bool
894 io_run_bg(const char **argv)
896         return io_complete(IO_BG, argv, NULL, -1);
899 static bool
900 io_run_fg(const char **argv, const char *dir)
902         return io_complete(IO_FG, argv, dir, -1);
905 static bool
906 io_run_append(const char **argv, int fd)
908         return io_complete(IO_AP, argv, NULL, -1);
911 static bool
912 io_run_rd(struct io *io, const char **argv, const char *dir)
914         return io_format(io, dir, IO_RD, argv, FORMAT_NONE) && io_start(io);
917 static bool
918 io_eof(struct io *io)
920         return io->eof;
923 static int
924 io_error(struct io *io)
926         return io->error;
929 static char *
930 io_strerror(struct io *io)
932         return strerror(io->error);
935 static bool
936 io_can_read(struct io *io)
938         struct timeval tv = { 0, 500 };
939         fd_set fds;
941         FD_ZERO(&fds);
942         FD_SET(io->pipe, &fds);
944         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
947 static ssize_t
948 io_read(struct io *io, void *buf, size_t bufsize)
950         do {
951                 ssize_t readsize = read(io->pipe, buf, bufsize);
953                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
954                         continue;
955                 else if (readsize == -1)
956                         io->error = errno;
957                 else if (readsize == 0)
958                         io->eof = 1;
959                 return readsize;
960         } while (1);
963 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
965 static char *
966 io_get(struct io *io, int c, bool can_read)
968         char *eol;
969         ssize_t readsize;
971         while (TRUE) {
972                 if (io->bufsize > 0) {
973                         eol = memchr(io->bufpos, c, io->bufsize);
974                         if (eol) {
975                                 char *line = io->bufpos;
977                                 *eol = 0;
978                                 io->bufpos = eol + 1;
979                                 io->bufsize -= io->bufpos - line;
980                                 return line;
981                         }
982                 }
984                 if (io_eof(io)) {
985                         if (io->bufsize) {
986                                 io->bufpos[io->bufsize] = 0;
987                                 io->bufsize = 0;
988                                 return io->bufpos;
989                         }
990                         return NULL;
991                 }
993                 if (!can_read)
994                         return NULL;
996                 if (io->bufsize > 0 && io->bufpos > io->buf)
997                         memmove(io->buf, io->bufpos, io->bufsize);
999                 if (io->bufalloc == io->bufsize) {
1000                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
1001                                 return NULL;
1002                         io->bufalloc += BUFSIZ;
1003                 }
1005                 io->bufpos = io->buf;
1006                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1007                 if (io_error(io))
1008                         return NULL;
1009                 io->bufsize += readsize;
1010         }
1013 static bool
1014 io_write(struct io *io, const void *buf, size_t bufsize)
1016         size_t written = 0;
1018         while (!io_error(io) && written < bufsize) {
1019                 ssize_t size;
1021                 size = write(io->pipe, buf + written, bufsize - written);
1022                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1023                         continue;
1024                 else if (size == -1)
1025                         io->error = errno;
1026                 else
1027                         written += size;
1028         }
1030         return written == bufsize;
1033 static bool
1034 io_read_buf(struct io *io, char buf[], size_t bufsize)
1036         char *result = io_get(io, '\n', TRUE);
1038         if (result) {
1039                 result = chomp_string(result);
1040                 string_ncopy_do(buf, bufsize, result, strlen(result));
1041         }
1043         return io_done(io) && result;
1046 static bool
1047 io_run_buf(const char **argv, char buf[], size_t bufsize)
1049         struct io io = {};
1051         io_prepare(&io, NULL, IO_RD, argv);
1052         return io_start(&io) && io_read_buf(&io, buf, bufsize);
1055 static int
1056 io_load(struct io *io, const char *separators,
1057         int (*read_property)(char *, size_t, char *, size_t))
1059         char *name;
1060         int state = OK;
1062         if (!io_start(io))
1063                 return ERR;
1065         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1066                 char *value;
1067                 size_t namelen;
1068                 size_t valuelen;
1070                 name = chomp_string(name);
1071                 namelen = strcspn(name, separators);
1073                 if (name[namelen]) {
1074                         name[namelen] = 0;
1075                         value = chomp_string(name + namelen + 1);
1076                         valuelen = strlen(value);
1078                 } else {
1079                         value = "";
1080                         valuelen = 0;
1081                 }
1083                 state = read_property(name, namelen, value, valuelen);
1084         }
1086         if (state != ERR && io_error(io))
1087                 state = ERR;
1088         io_done(io);
1090         return state;
1093 static int
1094 io_run_load(const char **argv, const char *separators,
1095             int (*read_property)(char *, size_t, char *, size_t))
1097         struct io io = {};
1099         io_prepare(&io, NULL, IO_RD, argv);
1100         return io_load(&io, separators, read_property);
1104 /*
1105  * User requests
1106  */
1108 #define REQ_INFO \
1109         /* XXX: Keep the view request first and in sync with views[]. */ \
1110         REQ_GROUP("View switching") \
1111         REQ_(VIEW_MAIN,         "Show main view"), \
1112         REQ_(VIEW_DIFF,         "Show diff view"), \
1113         REQ_(VIEW_LOG,          "Show log view"), \
1114         REQ_(VIEW_TREE,         "Show tree view"), \
1115         REQ_(VIEW_BLOB,         "Show blob view"), \
1116         REQ_(VIEW_BLAME,        "Show blame view"), \
1117         REQ_(VIEW_BRANCH,       "Show branch view"), \
1118         REQ_(VIEW_HELP,         "Show help page"), \
1119         REQ_(VIEW_PAGER,        "Show pager view"), \
1120         REQ_(VIEW_STATUS,       "Show status view"), \
1121         REQ_(VIEW_STAGE,        "Show stage view"), \
1122         \
1123         REQ_GROUP("View manipulation") \
1124         REQ_(ENTER,             "Enter current line and scroll"), \
1125         REQ_(NEXT,              "Move to next"), \
1126         REQ_(PREVIOUS,          "Move to previous"), \
1127         REQ_(PARENT,            "Move to parent"), \
1128         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1129         REQ_(REFRESH,           "Reload and refresh"), \
1130         REQ_(MAXIMIZE,          "Maximize the current view"), \
1131         REQ_(VIEW_CLOSE,        "Close the current view"), \
1132         REQ_(QUIT,              "Close all views and quit"), \
1133         \
1134         REQ_GROUP("View specific requests") \
1135         REQ_(STATUS_UPDATE,     "Update file status"), \
1136         REQ_(STATUS_REVERT,     "Revert file changes"), \
1137         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1138         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1139         \
1140         REQ_GROUP("Cursor navigation") \
1141         REQ_(MOVE_UP,           "Move cursor one line up"), \
1142         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1143         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1144         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1145         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1146         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1147         \
1148         REQ_GROUP("Scrolling") \
1149         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1150         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1151         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1152         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1153         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1154         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1155         \
1156         REQ_GROUP("Searching") \
1157         REQ_(SEARCH,            "Search the view"), \
1158         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1159         REQ_(FIND_NEXT,         "Find next search match"), \
1160         REQ_(FIND_PREV,         "Find previous search match"), \
1161         \
1162         REQ_GROUP("Option manipulation") \
1163         REQ_(OPTIONS,           "Open option menu"), \
1164         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1165         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1166         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1167         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1168         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1169         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1170         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1171         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1172         \
1173         REQ_GROUP("Misc") \
1174         REQ_(PROMPT,            "Bring up the prompt"), \
1175         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1176         REQ_(SHOW_VERSION,      "Show version information"), \
1177         REQ_(STOP_LOADING,      "Stop all loading views"), \
1178         REQ_(EDIT,              "Open in editor"), \
1179         REQ_(NONE,              "Do nothing")
1182 /* User action requests. */
1183 enum request {
1184 #define REQ_GROUP(help)
1185 #define REQ_(req, help) REQ_##req
1187         /* Offset all requests to avoid conflicts with ncurses getch values. */
1188         REQ_UNKNOWN = KEY_MAX + 1,
1189         REQ_OFFSET,
1190         REQ_INFO
1192 #undef  REQ_GROUP
1193 #undef  REQ_
1194 };
1196 struct request_info {
1197         enum request request;
1198         const char *name;
1199         int namelen;
1200         const char *help;
1201 };
1203 static const struct request_info req_info[] = {
1204 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1205 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1206         REQ_INFO
1207 #undef  REQ_GROUP
1208 #undef  REQ_
1209 };
1211 static enum request
1212 get_request(const char *name)
1214         int namelen = strlen(name);
1215         int i;
1217         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1218                 if (enum_equals(req_info[i], name, namelen))
1219                         return req_info[i].request;
1221         return REQ_UNKNOWN;
1225 /*
1226  * Options
1227  */
1229 /* Option and state variables. */
1230 static enum date opt_date               = DATE_DEFAULT;
1231 static enum author opt_author           = AUTHOR_DEFAULT;
1232 static bool opt_line_number             = FALSE;
1233 static bool opt_line_graphics           = TRUE;
1234 static bool opt_rev_graph               = FALSE;
1235 static bool opt_show_refs               = TRUE;
1236 static int opt_num_interval             = 5;
1237 static double opt_hscroll               = 0.50;
1238 static double opt_scale_split_view      = 2.0 / 3.0;
1239 static int opt_tab_size                 = 8;
1240 static int opt_author_cols              = AUTHOR_COLS;
1241 static char opt_path[SIZEOF_STR]        = "";
1242 static char opt_file[SIZEOF_STR]        = "";
1243 static char opt_ref[SIZEOF_REF]         = "";
1244 static char opt_head[SIZEOF_REF]        = "";
1245 static char opt_remote[SIZEOF_REF]      = "";
1246 static char opt_encoding[20]            = "UTF-8";
1247 static iconv_t opt_iconv_in             = ICONV_NONE;
1248 static iconv_t opt_iconv_out            = ICONV_NONE;
1249 static char opt_search[SIZEOF_STR]      = "";
1250 static char opt_cdup[SIZEOF_STR]        = "";
1251 static char opt_prefix[SIZEOF_STR]      = "";
1252 static char opt_git_dir[SIZEOF_STR]     = "";
1253 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1254 static char opt_editor[SIZEOF_STR]      = "";
1255 static FILE *opt_tty                    = NULL;
1257 #define is_initial_commit()     (!get_ref_head())
1258 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1261 /*
1262  * Line-oriented content detection.
1263  */
1265 #define LINE_INFO \
1266 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1267 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1268 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1269 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1270 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1271 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1272 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1273 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1274 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1275 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1276 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1277 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1278 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1279 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1280 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1281 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1282 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1283 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1284 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1285 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1286 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1287 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1288 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1289 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1290 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1291 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1292 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1293 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1294 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1295 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1296 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1297 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1298 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1299 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1301 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1302 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1303 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1304 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1305 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1306 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1307 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1308 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1309 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1310 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1311 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1312 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1313 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1314 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1315 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1316 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1317 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1318 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1319 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1320 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1321 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1322 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1323 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1324 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1326 enum line_type {
1327 #define LINE(type, line, fg, bg, attr) \
1328         LINE_##type
1329         LINE_INFO,
1330         LINE_NONE
1331 #undef  LINE
1332 };
1334 struct line_info {
1335         const char *name;       /* Option name. */
1336         int namelen;            /* Size of option name. */
1337         const char *line;       /* The start of line to match. */
1338         int linelen;            /* Size of string to match. */
1339         int fg, bg, attr;       /* Color and text attributes for the lines. */
1340 };
1342 static struct line_info line_info[] = {
1343 #define LINE(type, line, fg, bg, attr) \
1344         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1345         LINE_INFO
1346 #undef  LINE
1347 };
1349 static enum line_type
1350 get_line_type(const char *line)
1352         int linelen = strlen(line);
1353         enum line_type type;
1355         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1356                 /* Case insensitive search matches Signed-off-by lines better. */
1357                 if (linelen >= line_info[type].linelen &&
1358                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1359                         return type;
1361         return LINE_DEFAULT;
1364 static inline int
1365 get_line_attr(enum line_type type)
1367         assert(type < ARRAY_SIZE(line_info));
1368         return COLOR_PAIR(type) | line_info[type].attr;
1371 static struct line_info *
1372 get_line_info(const char *name)
1374         size_t namelen = strlen(name);
1375         enum line_type type;
1377         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1378                 if (enum_equals(line_info[type], name, namelen))
1379                         return &line_info[type];
1381         return NULL;
1384 static void
1385 init_colors(void)
1387         int default_bg = line_info[LINE_DEFAULT].bg;
1388         int default_fg = line_info[LINE_DEFAULT].fg;
1389         enum line_type type;
1391         start_color();
1393         if (assume_default_colors(default_fg, default_bg) == ERR) {
1394                 default_bg = COLOR_BLACK;
1395                 default_fg = COLOR_WHITE;
1396         }
1398         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1399                 struct line_info *info = &line_info[type];
1400                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1401                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1403                 init_pair(type, fg, bg);
1404         }
1407 struct line {
1408         enum line_type type;
1410         /* State flags */
1411         unsigned int selected:1;
1412         unsigned int dirty:1;
1413         unsigned int cleareol:1;
1414         unsigned int other:16;
1416         void *data;             /* User data */
1417 };
1420 /*
1421  * Keys
1422  */
1424 struct keybinding {
1425         int alias;
1426         enum request request;
1427 };
1429 static struct keybinding default_keybindings[] = {
1430         /* View switching */
1431         { 'm',          REQ_VIEW_MAIN },
1432         { 'd',          REQ_VIEW_DIFF },
1433         { 'l',          REQ_VIEW_LOG },
1434         { 't',          REQ_VIEW_TREE },
1435         { 'f',          REQ_VIEW_BLOB },
1436         { 'B',          REQ_VIEW_BLAME },
1437         { 'H',          REQ_VIEW_BRANCH },
1438         { 'p',          REQ_VIEW_PAGER },
1439         { 'h',          REQ_VIEW_HELP },
1440         { 'S',          REQ_VIEW_STATUS },
1441         { 'c',          REQ_VIEW_STAGE },
1443         /* View manipulation */
1444         { 'q',          REQ_VIEW_CLOSE },
1445         { KEY_TAB,      REQ_VIEW_NEXT },
1446         { KEY_RETURN,   REQ_ENTER },
1447         { KEY_UP,       REQ_PREVIOUS },
1448         { KEY_DOWN,     REQ_NEXT },
1449         { 'R',          REQ_REFRESH },
1450         { KEY_F(5),     REQ_REFRESH },
1451         { 'O',          REQ_MAXIMIZE },
1453         /* Cursor navigation */
1454         { 'k',          REQ_MOVE_UP },
1455         { 'j',          REQ_MOVE_DOWN },
1456         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1457         { KEY_END,      REQ_MOVE_LAST_LINE },
1458         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1459         { ' ',          REQ_MOVE_PAGE_DOWN },
1460         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1461         { 'b',          REQ_MOVE_PAGE_UP },
1462         { '-',          REQ_MOVE_PAGE_UP },
1464         /* Scrolling */
1465         { KEY_LEFT,     REQ_SCROLL_LEFT },
1466         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1467         { KEY_IC,       REQ_SCROLL_LINE_UP },
1468         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1469         { 'w',          REQ_SCROLL_PAGE_UP },
1470         { 's',          REQ_SCROLL_PAGE_DOWN },
1472         /* Searching */
1473         { '/',          REQ_SEARCH },
1474         { '?',          REQ_SEARCH_BACK },
1475         { 'n',          REQ_FIND_NEXT },
1476         { 'N',          REQ_FIND_PREV },
1478         /* Misc */
1479         { 'Q',          REQ_QUIT },
1480         { 'z',          REQ_STOP_LOADING },
1481         { 'v',          REQ_SHOW_VERSION },
1482         { 'r',          REQ_SCREEN_REDRAW },
1483         { 'o',          REQ_OPTIONS },
1484         { '.',          REQ_TOGGLE_LINENO },
1485         { 'D',          REQ_TOGGLE_DATE },
1486         { 'A',          REQ_TOGGLE_AUTHOR },
1487         { 'g',          REQ_TOGGLE_REV_GRAPH },
1488         { 'F',          REQ_TOGGLE_REFS },
1489         { 'I',          REQ_TOGGLE_SORT_ORDER },
1490         { 'i',          REQ_TOGGLE_SORT_FIELD },
1491         { ':',          REQ_PROMPT },
1492         { 'u',          REQ_STATUS_UPDATE },
1493         { '!',          REQ_STATUS_REVERT },
1494         { 'M',          REQ_STATUS_MERGE },
1495         { '@',          REQ_STAGE_NEXT },
1496         { ',',          REQ_PARENT },
1497         { 'e',          REQ_EDIT },
1498 };
1500 #define KEYMAP_INFO \
1501         KEYMAP_(GENERIC), \
1502         KEYMAP_(MAIN), \
1503         KEYMAP_(DIFF), \
1504         KEYMAP_(LOG), \
1505         KEYMAP_(TREE), \
1506         KEYMAP_(BLOB), \
1507         KEYMAP_(BLAME), \
1508         KEYMAP_(BRANCH), \
1509         KEYMAP_(PAGER), \
1510         KEYMAP_(HELP), \
1511         KEYMAP_(STATUS), \
1512         KEYMAP_(STAGE)
1514 enum keymap {
1515 #define KEYMAP_(name) KEYMAP_##name
1516         KEYMAP_INFO
1517 #undef  KEYMAP_
1518 };
1520 static const struct enum_map keymap_table[] = {
1521 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1522         KEYMAP_INFO
1523 #undef  KEYMAP_
1524 };
1526 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1528 struct keybinding_table {
1529         struct keybinding *data;
1530         size_t size;
1531 };
1533 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1535 static void
1536 add_keybinding(enum keymap keymap, enum request request, int key)
1538         struct keybinding_table *table = &keybindings[keymap];
1539         size_t i;
1541         for (i = 0; i < keybindings[keymap].size; i++) {
1542                 if (keybindings[keymap].data[i].alias == key) {
1543                         keybindings[keymap].data[i].request = request;
1544                         return;
1545                 }
1546         }
1548         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1549         if (!table->data)
1550                 die("Failed to allocate keybinding");
1551         table->data[table->size].alias = key;
1552         table->data[table->size++].request = request;
1554         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1555                 int i;
1557                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1558                         if (default_keybindings[i].alias == key)
1559                                 default_keybindings[i].request = REQ_NONE;
1560         }
1563 /* Looks for a key binding first in the given map, then in the generic map, and
1564  * lastly in the default keybindings. */
1565 static enum request
1566 get_keybinding(enum keymap keymap, int key)
1568         size_t i;
1570         for (i = 0; i < keybindings[keymap].size; i++)
1571                 if (keybindings[keymap].data[i].alias == key)
1572                         return keybindings[keymap].data[i].request;
1574         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1575                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1576                         return keybindings[KEYMAP_GENERIC].data[i].request;
1578         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1579                 if (default_keybindings[i].alias == key)
1580                         return default_keybindings[i].request;
1582         return (enum request) key;
1586 struct key {
1587         const char *name;
1588         int value;
1589 };
1591 static const struct key key_table[] = {
1592         { "Enter",      KEY_RETURN },
1593         { "Space",      ' ' },
1594         { "Backspace",  KEY_BACKSPACE },
1595         { "Tab",        KEY_TAB },
1596         { "Escape",     KEY_ESC },
1597         { "Left",       KEY_LEFT },
1598         { "Right",      KEY_RIGHT },
1599         { "Up",         KEY_UP },
1600         { "Down",       KEY_DOWN },
1601         { "Insert",     KEY_IC },
1602         { "Delete",     KEY_DC },
1603         { "Hash",       '#' },
1604         { "Home",       KEY_HOME },
1605         { "End",        KEY_END },
1606         { "PageUp",     KEY_PPAGE },
1607         { "PageDown",   KEY_NPAGE },
1608         { "F1",         KEY_F(1) },
1609         { "F2",         KEY_F(2) },
1610         { "F3",         KEY_F(3) },
1611         { "F4",         KEY_F(4) },
1612         { "F5",         KEY_F(5) },
1613         { "F6",         KEY_F(6) },
1614         { "F7",         KEY_F(7) },
1615         { "F8",         KEY_F(8) },
1616         { "F9",         KEY_F(9) },
1617         { "F10",        KEY_F(10) },
1618         { "F11",        KEY_F(11) },
1619         { "F12",        KEY_F(12) },
1620 };
1622 static int
1623 get_key_value(const char *name)
1625         int i;
1627         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1628                 if (!strcasecmp(key_table[i].name, name))
1629                         return key_table[i].value;
1631         if (strlen(name) == 1 && isprint(*name))
1632                 return (int) *name;
1634         return ERR;
1637 static const char *
1638 get_key_name(int key_value)
1640         static char key_char[] = "'X'";
1641         const char *seq = NULL;
1642         int key;
1644         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1645                 if (key_table[key].value == key_value)
1646                         seq = key_table[key].name;
1648         if (seq == NULL &&
1649             key_value < 127 &&
1650             isprint(key_value)) {
1651                 key_char[1] = (char) key_value;
1652                 seq = key_char;
1653         }
1655         return seq ? seq : "(no key)";
1658 static bool
1659 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1661         const char *sep = *pos > 0 ? ", " : "";
1662         const char *keyname = get_key_name(keybinding->alias);
1664         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1667 static bool
1668 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1669                            enum keymap keymap, bool all)
1671         int i;
1673         for (i = 0; i < keybindings[keymap].size; i++) {
1674                 if (keybindings[keymap].data[i].request == request) {
1675                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1676                                 return FALSE;
1677                         if (!all)
1678                                 break;
1679                 }
1680         }
1682         return TRUE;
1685 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1687 static const char *
1688 get_keys(enum keymap keymap, enum request request, bool all)
1690         static char buf[BUFSIZ];
1691         size_t pos = 0;
1692         int i;
1694         buf[pos] = 0;
1696         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1697                 return "Too many keybindings!";
1698         if (pos > 0 && !all)
1699                 return buf;
1701         if (keymap != KEYMAP_GENERIC) {
1702                 /* Only the generic keymap includes the default keybindings when
1703                  * listing all keys. */
1704                 if (all)
1705                         return buf;
1707                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1708                         return "Too many keybindings!";
1709                 if (pos)
1710                         return buf;
1711         }
1713         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1714                 if (default_keybindings[i].request == request) {
1715                         if (!append_key(buf, &pos, &default_keybindings[i]))
1716                                 return "Too many keybindings!";
1717                         if (!all)
1718                                 return buf;
1719                 }
1720         }
1722         return buf;
1725 struct run_request {
1726         enum keymap keymap;
1727         int key;
1728         const char *argv[SIZEOF_ARG];
1729 };
1731 static struct run_request *run_request;
1732 static size_t run_requests;
1734 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1736 static enum request
1737 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1739         struct run_request *req;
1741         if (argc >= ARRAY_SIZE(req->argv) - 1)
1742                 return REQ_NONE;
1744         if (!realloc_run_requests(&run_request, run_requests, 1))
1745                 return REQ_NONE;
1747         req = &run_request[run_requests];
1748         req->keymap = keymap;
1749         req->key = key;
1750         req->argv[0] = NULL;
1752         if (!format_argv(req->argv, argv, FORMAT_NONE))
1753                 return REQ_NONE;
1755         return REQ_NONE + ++run_requests;
1758 static struct run_request *
1759 get_run_request(enum request request)
1761         if (request <= REQ_NONE)
1762                 return NULL;
1763         return &run_request[request - REQ_NONE - 1];
1766 static void
1767 add_builtin_run_requests(void)
1769         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1770         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1771         const char *commit[] = { "git", "commit", NULL };
1772         const char *gc[] = { "git", "gc", NULL };
1773         struct {
1774                 enum keymap keymap;
1775                 int key;
1776                 int argc;
1777                 const char **argv;
1778         } reqs[] = {
1779                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1780                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1781                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1782                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1783         };
1784         int i;
1786         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1787                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1789                 if (req != reqs[i].key)
1790                         continue;
1791                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1792                 if (req != REQ_NONE)
1793                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1794         }
1797 /*
1798  * User config file handling.
1799  */
1801 static int   config_lineno;
1802 static bool  config_errors;
1803 static const char *config_msg;
1805 static const struct enum_map color_map[] = {
1806 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1807         COLOR_MAP(DEFAULT),
1808         COLOR_MAP(BLACK),
1809         COLOR_MAP(BLUE),
1810         COLOR_MAP(CYAN),
1811         COLOR_MAP(GREEN),
1812         COLOR_MAP(MAGENTA),
1813         COLOR_MAP(RED),
1814         COLOR_MAP(WHITE),
1815         COLOR_MAP(YELLOW),
1816 };
1818 static const struct enum_map attr_map[] = {
1819 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1820         ATTR_MAP(NORMAL),
1821         ATTR_MAP(BLINK),
1822         ATTR_MAP(BOLD),
1823         ATTR_MAP(DIM),
1824         ATTR_MAP(REVERSE),
1825         ATTR_MAP(STANDOUT),
1826         ATTR_MAP(UNDERLINE),
1827 };
1829 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1831 static int parse_step(double *opt, const char *arg)
1833         *opt = atoi(arg);
1834         if (!strchr(arg, '%'))
1835                 return OK;
1837         /* "Shift down" so 100% and 1 does not conflict. */
1838         *opt = (*opt - 1) / 100;
1839         if (*opt >= 1.0) {
1840                 *opt = 0.99;
1841                 config_msg = "Step value larger than 100%";
1842                 return ERR;
1843         }
1844         if (*opt < 0.0) {
1845                 *opt = 1;
1846                 config_msg = "Invalid step value";
1847                 return ERR;
1848         }
1849         return OK;
1852 static int
1853 parse_int(int *opt, const char *arg, int min, int max)
1855         int value = atoi(arg);
1857         if (min <= value && value <= max) {
1858                 *opt = value;
1859                 return OK;
1860         }
1862         config_msg = "Integer value out of bound";
1863         return ERR;
1866 static bool
1867 set_color(int *color, const char *name)
1869         if (map_enum(color, color_map, name))
1870                 return TRUE;
1871         if (!prefixcmp(name, "color"))
1872                 return parse_int(color, name + 5, 0, 255) == OK;
1873         return FALSE;
1876 /* Wants: object fgcolor bgcolor [attribute] */
1877 static int
1878 option_color_command(int argc, const char *argv[])
1880         struct line_info *info;
1882         if (argc < 3) {
1883                 config_msg = "Wrong number of arguments given to color command";
1884                 return ERR;
1885         }
1887         info = get_line_info(argv[0]);
1888         if (!info) {
1889                 static const struct enum_map obsolete[] = {
1890                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1891                         ENUM_MAP("main-date",   LINE_DATE),
1892                         ENUM_MAP("main-author", LINE_AUTHOR),
1893                 };
1894                 int index;
1896                 if (!map_enum(&index, obsolete, argv[0])) {
1897                         config_msg = "Unknown color name";
1898                         return ERR;
1899                 }
1900                 info = &line_info[index];
1901         }
1903         if (!set_color(&info->fg, argv[1]) ||
1904             !set_color(&info->bg, argv[2])) {
1905                 config_msg = "Unknown color";
1906                 return ERR;
1907         }
1909         info->attr = 0;
1910         while (argc-- > 3) {
1911                 int attr;
1913                 if (!set_attribute(&attr, argv[argc])) {
1914                         config_msg = "Unknown attribute";
1915                         return ERR;
1916                 }
1917                 info->attr |= attr;
1918         }
1920         return OK;
1923 static int parse_bool(bool *opt, const char *arg)
1925         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1926                 ? TRUE : FALSE;
1927         return OK;
1930 static int parse_enum_do(unsigned int *opt, const char *arg,
1931                          const struct enum_map *map, size_t map_size)
1933         bool is_true;
1935         assert(map_size > 1);
1937         if (map_enum_do(map, map_size, (int *) opt, arg))
1938                 return OK;
1940         if (parse_bool(&is_true, arg) != OK)
1941                 return ERR;
1943         *opt = is_true ? map[1].value : map[0].value;
1944         return OK;
1947 #define parse_enum(opt, arg, map) \
1948         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1950 static int
1951 parse_string(char *opt, const char *arg, size_t optsize)
1953         int arglen = strlen(arg);
1955         switch (arg[0]) {
1956         case '\"':
1957         case '\'':
1958                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1959                         config_msg = "Unmatched quotation";
1960                         return ERR;
1961                 }
1962                 arg += 1; arglen -= 2;
1963         default:
1964                 string_ncopy_do(opt, optsize, arg, arglen);
1965                 return OK;
1966         }
1969 /* Wants: name = value */
1970 static int
1971 option_set_command(int argc, const char *argv[])
1973         if (argc != 3) {
1974                 config_msg = "Wrong number of arguments given to set command";
1975                 return ERR;
1976         }
1978         if (strcmp(argv[1], "=")) {
1979                 config_msg = "No value assigned";
1980                 return ERR;
1981         }
1983         if (!strcmp(argv[0], "show-author"))
1984                 return parse_enum(&opt_author, argv[2], author_map);
1986         if (!strcmp(argv[0], "show-date"))
1987                 return parse_enum(&opt_date, argv[2], date_map);
1989         if (!strcmp(argv[0], "show-rev-graph"))
1990                 return parse_bool(&opt_rev_graph, argv[2]);
1992         if (!strcmp(argv[0], "show-refs"))
1993                 return parse_bool(&opt_show_refs, argv[2]);
1995         if (!strcmp(argv[0], "show-line-numbers"))
1996                 return parse_bool(&opt_line_number, argv[2]);
1998         if (!strcmp(argv[0], "line-graphics"))
1999                 return parse_bool(&opt_line_graphics, argv[2]);
2001         if (!strcmp(argv[0], "line-number-interval"))
2002                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2004         if (!strcmp(argv[0], "author-width"))
2005                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2007         if (!strcmp(argv[0], "horizontal-scroll"))
2008                 return parse_step(&opt_hscroll, argv[2]);
2010         if (!strcmp(argv[0], "split-view-height"))
2011                 return parse_step(&opt_scale_split_view, argv[2]);
2013         if (!strcmp(argv[0], "tab-size"))
2014                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2016         if (!strcmp(argv[0], "commit-encoding"))
2017                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2019         config_msg = "Unknown variable name";
2020         return ERR;
2023 /* Wants: mode request key */
2024 static int
2025 option_bind_command(int argc, const char *argv[])
2027         enum request request;
2028         int keymap = -1;
2029         int key;
2031         if (argc < 3) {
2032                 config_msg = "Wrong number of arguments given to bind command";
2033                 return ERR;
2034         }
2036         if (!set_keymap(&keymap, argv[0])) {
2037                 config_msg = "Unknown key map";
2038                 return ERR;
2039         }
2041         key = get_key_value(argv[1]);
2042         if (key == ERR) {
2043                 config_msg = "Unknown key";
2044                 return ERR;
2045         }
2047         request = get_request(argv[2]);
2048         if (request == REQ_UNKNOWN) {
2049                 static const struct enum_map obsolete[] = {
2050                         ENUM_MAP("cherry-pick",         REQ_NONE),
2051                         ENUM_MAP("screen-resize",       REQ_NONE),
2052                         ENUM_MAP("tree-parent",         REQ_PARENT),
2053                 };
2054                 int alias;
2056                 if (map_enum(&alias, obsolete, argv[2])) {
2057                         if (alias != REQ_NONE)
2058                                 add_keybinding(keymap, alias, key);
2059                         config_msg = "Obsolete request name";
2060                         return ERR;
2061                 }
2062         }
2063         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2064                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2065         if (request == REQ_UNKNOWN) {
2066                 config_msg = "Unknown request name";
2067                 return ERR;
2068         }
2070         add_keybinding(keymap, request, key);
2072         return OK;
2075 static int
2076 set_option(const char *opt, char *value)
2078         const char *argv[SIZEOF_ARG];
2079         int argc = 0;
2081         if (!argv_from_string(argv, &argc, value)) {
2082                 config_msg = "Too many option arguments";
2083                 return ERR;
2084         }
2086         if (!strcmp(opt, "color"))
2087                 return option_color_command(argc, argv);
2089         if (!strcmp(opt, "set"))
2090                 return option_set_command(argc, argv);
2092         if (!strcmp(opt, "bind"))
2093                 return option_bind_command(argc, argv);
2095         config_msg = "Unknown option command";
2096         return ERR;
2099 static int
2100 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2102         int status = OK;
2104         config_lineno++;
2105         config_msg = "Internal error";
2107         /* Check for comment markers, since read_properties() will
2108          * only ensure opt and value are split at first " \t". */
2109         optlen = strcspn(opt, "#");
2110         if (optlen == 0)
2111                 return OK;
2113         if (opt[optlen] != 0) {
2114                 config_msg = "No option value";
2115                 status = ERR;
2117         }  else {
2118                 /* Look for comment endings in the value. */
2119                 size_t len = strcspn(value, "#");
2121                 if (len < valuelen) {
2122                         valuelen = len;
2123                         value[valuelen] = 0;
2124                 }
2126                 status = set_option(opt, value);
2127         }
2129         if (status == ERR) {
2130                 warn("Error on line %d, near '%.*s': %s",
2131                      config_lineno, (int) optlen, opt, config_msg);
2132                 config_errors = TRUE;
2133         }
2135         /* Always keep going if errors are encountered. */
2136         return OK;
2139 static void
2140 load_option_file(const char *path)
2142         struct io io = {};
2144         /* It's OK that the file doesn't exist. */
2145         if (!io_open(&io, "%s", path))
2146                 return;
2148         config_lineno = 0;
2149         config_errors = FALSE;
2151         if (io_load(&io, " \t", read_option) == ERR ||
2152             config_errors == TRUE)
2153                 warn("Errors while loading %s.", path);
2156 static int
2157 load_options(void)
2159         const char *home = getenv("HOME");
2160         const char *tigrc_user = getenv("TIGRC_USER");
2161         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2162         char buf[SIZEOF_STR];
2164         if (!tigrc_system)
2165                 tigrc_system = SYSCONFDIR "/tigrc";
2166         load_option_file(tigrc_system);
2168         if (!tigrc_user) {
2169                 if (!home || !string_format(buf, "%s/.tigrc", home))
2170                         return ERR;
2171                 tigrc_user = buf;
2172         }
2173         load_option_file(tigrc_user);
2175         /* Add _after_ loading config files to avoid adding run requests
2176          * that conflict with keybindings. */
2177         add_builtin_run_requests();
2179         return OK;
2183 /*
2184  * The viewer
2185  */
2187 struct view;
2188 struct view_ops;
2190 /* The display array of active views and the index of the current view. */
2191 static struct view *display[2];
2192 static unsigned int current_view;
2194 #define foreach_displayed_view(view, i) \
2195         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2197 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2199 /* Current head and commit ID */
2200 static char ref_blob[SIZEOF_REF]        = "";
2201 static char ref_commit[SIZEOF_REF]      = "HEAD";
2202 static char ref_head[SIZEOF_REF]        = "HEAD";
2203 static char ref_branch[SIZEOF_REF]      = "";
2205 enum view_type {
2206         VIEW_MAIN,
2207         VIEW_DIFF,
2208         VIEW_LOG,
2209         VIEW_TREE,
2210         VIEW_BLOB,
2211         VIEW_BLAME,
2212         VIEW_BRANCH,
2213         VIEW_HELP,
2214         VIEW_PAGER,
2215         VIEW_STATUS,
2216         VIEW_STAGE,
2217 };
2219 struct view {
2220         enum view_type type;    /* View type */
2221         const char *name;       /* View name */
2222         const char *cmd_env;    /* Command line set via environment */
2223         const char *id;         /* Points to either of ref_{head,commit,blob} */
2225         struct view_ops *ops;   /* View operations */
2227         enum keymap keymap;     /* What keymap does this view have */
2228         bool git_dir;           /* Whether the view requires a git directory. */
2230         char ref[SIZEOF_REF];   /* Hovered commit reference */
2231         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2233         int height, width;      /* The width and height of the main window */
2234         WINDOW *win;            /* The main window */
2235         WINDOW *title;          /* The title window living below the main window */
2237         /* Navigation */
2238         unsigned long offset;   /* Offset of the window top */
2239         unsigned long yoffset;  /* Offset from the window side. */
2240         unsigned long lineno;   /* Current line number */
2241         unsigned long p_offset; /* Previous offset of the window top */
2242         unsigned long p_yoffset;/* Previous offset from the window side */
2243         unsigned long p_lineno; /* Previous current line number */
2244         bool p_restore;         /* Should the previous position be restored. */
2246         /* Searching */
2247         char grep[SIZEOF_STR];  /* Search string */
2248         regex_t *regex;         /* Pre-compiled regexp */
2250         /* If non-NULL, points to the view that opened this view. If this view
2251          * is closed tig will switch back to the parent view. */
2252         struct view *parent;
2253         struct view *prev;
2255         /* Buffering */
2256         size_t lines;           /* Total number of lines */
2257         struct line *line;      /* Line index */
2258         unsigned int digits;    /* Number of digits in the lines member. */
2260         /* Drawing */
2261         struct line *curline;   /* Line currently being drawn. */
2262         enum line_type curtype; /* Attribute currently used for drawing. */
2263         unsigned long col;      /* Column when drawing. */
2264         bool has_scrolled;      /* View was scrolled. */
2266         /* Loading */
2267         struct io io;
2268         struct io *pipe;
2269         time_t start_time;
2270         time_t update_secs;
2271 };
2273 struct view_ops {
2274         /* What type of content being displayed. Used in the title bar. */
2275         const char *type;
2276         /* Default command arguments. */
2277         const char **argv;
2278         /* Open and reads in all view content. */
2279         bool (*open)(struct view *view);
2280         /* Read one line; updates view->line. */
2281         bool (*read)(struct view *view, char *data);
2282         /* Draw one line; @lineno must be < view->height. */
2283         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2284         /* Depending on view handle a special requests. */
2285         enum request (*request)(struct view *view, enum request request, struct line *line);
2286         /* Search for regexp in a line. */
2287         bool (*grep)(struct view *view, struct line *line);
2288         /* Select line */
2289         void (*select)(struct view *view, struct line *line);
2290         /* Prepare view for loading */
2291         bool (*prepare)(struct view *view);
2292 };
2294 static struct view_ops blame_ops;
2295 static struct view_ops blob_ops;
2296 static struct view_ops diff_ops;
2297 static struct view_ops help_ops;
2298 static struct view_ops log_ops;
2299 static struct view_ops main_ops;
2300 static struct view_ops pager_ops;
2301 static struct view_ops stage_ops;
2302 static struct view_ops status_ops;
2303 static struct view_ops tree_ops;
2304 static struct view_ops branch_ops;
2306 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2307         { type, name, #env, ref, ops, map, git }
2309 #define VIEW_(id, name, ops, git, ref) \
2310         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2312 static struct view views[] = {
2313         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2314         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2315         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2316         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2317         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2318         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2319         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2320         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2321         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2322         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2323         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2324 };
2326 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2328 #define foreach_view(view, i) \
2329         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2331 #define view_is_displayed(view) \
2332         (view == display[0] || view == display[1])
2334 static enum request
2335 view_request(struct view *view, enum request request)
2337         if (!view || !view->lines)
2338                 return request;
2339         return view->ops->request(view, request, &view->line[view->lineno]);
2343 /*
2344  * View drawing.
2345  */
2347 static inline void
2348 set_view_attr(struct view *view, enum line_type type)
2350         if (!view->curline->selected && view->curtype != type) {
2351                 (void) wattrset(view->win, get_line_attr(type));
2352                 wchgat(view->win, -1, 0, type, NULL);
2353                 view->curtype = type;
2354         }
2357 static int
2358 draw_chars(struct view *view, enum line_type type, const char *string,
2359            int max_len, bool use_tilde)
2361         static char out_buffer[BUFSIZ * 2];
2362         int len = 0;
2363         int col = 0;
2364         int trimmed = FALSE;
2365         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2367         if (max_len <= 0)
2368                 return 0;
2370         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2372         set_view_attr(view, type);
2373         if (len > 0) {
2374                 if (opt_iconv_out != ICONV_NONE) {
2375                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2376                         size_t inlen = len + 1;
2378                         char *outbuf = out_buffer;
2379                         size_t outlen = sizeof(out_buffer);
2381                         size_t ret;
2383                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2384                         if (ret != (size_t) -1) {
2385                                 string = out_buffer;
2386                                 len = sizeof(out_buffer) - outlen;
2387                         }
2388                 }
2390                 waddnstr(view->win, string, len);
2391         }
2392         if (trimmed && use_tilde) {
2393                 set_view_attr(view, LINE_DELIMITER);
2394                 waddch(view->win, '~');
2395                 col++;
2396         }
2398         return col;
2401 static int
2402 draw_space(struct view *view, enum line_type type, int max, int spaces)
2404         static char space[] = "                    ";
2405         int col = 0;
2407         spaces = MIN(max, spaces);
2409         while (spaces > 0) {
2410                 int len = MIN(spaces, sizeof(space) - 1);
2412                 col += draw_chars(view, type, space, len, FALSE);
2413                 spaces -= len;
2414         }
2416         return col;
2419 static bool
2420 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2422         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2423         return view->width + view->yoffset <= view->col;
2426 static bool
2427 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2429         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2430         int max = view->width + view->yoffset - view->col;
2431         int i;
2433         if (max < size)
2434                 size = max;
2436         set_view_attr(view, type);
2437         /* Using waddch() instead of waddnstr() ensures that
2438          * they'll be rendered correctly for the cursor line. */
2439         for (i = skip; i < size; i++)
2440                 waddch(view->win, graphic[i]);
2442         view->col += size;
2443         if (size < max && skip <= size)
2444                 waddch(view->win, ' ');
2445         view->col++;
2447         return view->width + view->yoffset <= view->col;
2450 static bool
2451 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2453         int max = MIN(view->width + view->yoffset - view->col, len);
2454         int col;
2456         if (text)
2457                 col = draw_chars(view, type, text, max - 1, trim);
2458         else
2459                 col = draw_space(view, type, max - 1, max - 1);
2461         view->col += col;
2462         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2463         return view->width + view->yoffset <= view->col;
2466 static bool
2467 draw_date(struct view *view, struct time *time)
2469         const char *date = mkdate(time, opt_date);
2470         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2472         return draw_field(view, LINE_DATE, date, cols, FALSE);
2475 static bool
2476 draw_author(struct view *view, const char *author)
2478         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2479         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2481         if (abbreviate && author)
2482                 author = get_author_initials(author);
2484         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2487 static bool
2488 draw_mode(struct view *view, mode_t mode)
2490         const char *str;
2492         if (S_ISDIR(mode))
2493                 str = "drwxr-xr-x";
2494         else if (S_ISLNK(mode))
2495                 str = "lrwxrwxrwx";
2496         else if (S_ISGITLINK(mode))
2497                 str = "m---------";
2498         else if (S_ISREG(mode) && mode & S_IXUSR)
2499                 str = "-rwxr-xr-x";
2500         else if (S_ISREG(mode))
2501                 str = "-rw-r--r--";
2502         else
2503                 str = "----------";
2505         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2508 static bool
2509 draw_lineno(struct view *view, unsigned int lineno)
2511         char number[10];
2512         int digits3 = view->digits < 3 ? 3 : view->digits;
2513         int max = MIN(view->width + view->yoffset - view->col, digits3);
2514         char *text = NULL;
2515         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2517         lineno += view->offset + 1;
2518         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2519                 static char fmt[] = "%1ld";
2521                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2522                 if (string_format(number, fmt, lineno))
2523                         text = number;
2524         }
2525         if (text)
2526                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2527         else
2528                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2529         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2532 static bool
2533 draw_view_line(struct view *view, unsigned int lineno)
2535         struct line *line;
2536         bool selected = (view->offset + lineno == view->lineno);
2538         assert(view_is_displayed(view));
2540         if (view->offset + lineno >= view->lines)
2541                 return FALSE;
2543         line = &view->line[view->offset + lineno];
2545         wmove(view->win, lineno, 0);
2546         if (line->cleareol)
2547                 wclrtoeol(view->win);
2548         view->col = 0;
2549         view->curline = line;
2550         view->curtype = LINE_NONE;
2551         line->selected = FALSE;
2552         line->dirty = line->cleareol = 0;
2554         if (selected) {
2555                 set_view_attr(view, LINE_CURSOR);
2556                 line->selected = TRUE;
2557                 view->ops->select(view, line);
2558         }
2560         return view->ops->draw(view, line, lineno);
2563 static void
2564 redraw_view_dirty(struct view *view)
2566         bool dirty = FALSE;
2567         int lineno;
2569         for (lineno = 0; lineno < view->height; lineno++) {
2570                 if (view->offset + lineno >= view->lines)
2571                         break;
2572                 if (!view->line[view->offset + lineno].dirty)
2573                         continue;
2574                 dirty = TRUE;
2575                 if (!draw_view_line(view, lineno))
2576                         break;
2577         }
2579         if (!dirty)
2580                 return;
2581         wnoutrefresh(view->win);
2584 static void
2585 redraw_view_from(struct view *view, int lineno)
2587         assert(0 <= lineno && lineno < view->height);
2589         for (; lineno < view->height; lineno++) {
2590                 if (!draw_view_line(view, lineno))
2591                         break;
2592         }
2594         wnoutrefresh(view->win);
2597 static void
2598 redraw_view(struct view *view)
2600         werase(view->win);
2601         redraw_view_from(view, 0);
2605 static void
2606 update_view_title(struct view *view)
2608         char buf[SIZEOF_STR];
2609         char state[SIZEOF_STR];
2610         size_t bufpos = 0, statelen = 0;
2612         assert(view_is_displayed(view));
2614         if (view->type != VIEW_STATUS && view->lines) {
2615                 unsigned int view_lines = view->offset + view->height;
2616                 unsigned int lines = view->lines
2617                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2618                                    : 0;
2620                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2621                                    view->ops->type,
2622                                    view->lineno + 1,
2623                                    view->lines,
2624                                    lines);
2626         }
2628         if (view->pipe) {
2629                 time_t secs = time(NULL) - view->start_time;
2631                 /* Three git seconds are a long time ... */
2632                 if (secs > 2)
2633                         string_format_from(state, &statelen, " loading %lds", secs);
2634         }
2636         string_format_from(buf, &bufpos, "[%s]", view->name);
2637         if (*view->ref && bufpos < view->width) {
2638                 size_t refsize = strlen(view->ref);
2639                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2641                 if (minsize < view->width)
2642                         refsize = view->width - minsize + 7;
2643                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2644         }
2646         if (statelen && bufpos < view->width) {
2647                 string_format_from(buf, &bufpos, "%s", state);
2648         }
2650         if (view == display[current_view])
2651                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2652         else
2653                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2655         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2656         wclrtoeol(view->title);
2657         wnoutrefresh(view->title);
2660 static int
2661 apply_step(double step, int value)
2663         if (step >= 1)
2664                 return (int) step;
2665         value *= step + 0.01;
2666         return value ? value : 1;
2669 static void
2670 resize_display(void)
2672         int offset, i;
2673         struct view *base = display[0];
2674         struct view *view = display[1] ? display[1] : display[0];
2676         /* Setup window dimensions */
2678         getmaxyx(stdscr, base->height, base->width);
2680         /* Make room for the status window. */
2681         base->height -= 1;
2683         if (view != base) {
2684                 /* Horizontal split. */
2685                 view->width   = base->width;
2686                 view->height  = apply_step(opt_scale_split_view, base->height);
2687                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2688                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2689                 base->height -= view->height;
2691                 /* Make room for the title bar. */
2692                 view->height -= 1;
2693         }
2695         /* Make room for the title bar. */
2696         base->height -= 1;
2698         offset = 0;
2700         foreach_displayed_view (view, i) {
2701                 if (!view->win) {
2702                         view->win = newwin(view->height, 0, offset, 0);
2703                         if (!view->win)
2704                                 die("Failed to create %s view", view->name);
2706                         scrollok(view->win, FALSE);
2708                         view->title = newwin(1, 0, offset + view->height, 0);
2709                         if (!view->title)
2710                                 die("Failed to create title window");
2712                 } else {
2713                         wresize(view->win, view->height, view->width);
2714                         mvwin(view->win,   offset, 0);
2715                         mvwin(view->title, offset + view->height, 0);
2716                 }
2718                 offset += view->height + 1;
2719         }
2722 static void
2723 redraw_display(bool clear)
2725         struct view *view;
2726         int i;
2728         foreach_displayed_view (view, i) {
2729                 if (clear)
2730                         wclear(view->win);
2731                 redraw_view(view);
2732                 update_view_title(view);
2733         }
2737 /*
2738  * Option management
2739  */
2741 static void
2742 toggle_enum_option_do(unsigned int *opt, const char *help,
2743                       const struct enum_map *map, size_t size)
2745         *opt = (*opt + 1) % size;
2746         redraw_display(FALSE);
2747         report("Displaying %s %s", enum_name(map[*opt]), help);
2750 #define toggle_enum_option(opt, help, map) \
2751         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2753 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2754 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2756 static void
2757 toggle_view_option(bool *option, const char *help)
2759         *option = !*option;
2760         redraw_display(FALSE);
2761         report("%sabling %s", *option ? "En" : "Dis", help);
2764 static void
2765 open_option_menu(void)
2767         const struct menu_item menu[] = {
2768                 { '.', "line numbers", &opt_line_number },
2769                 { 'D', "date display", &opt_date },
2770                 { 'A', "author display", &opt_author },
2771                 { 'g', "revision graph display", &opt_rev_graph },
2772                 { 'F', "reference display", &opt_show_refs },
2773                 { 0 }
2774         };
2775         int selected = 0;
2777         if (prompt_menu("Toggle option", menu, &selected)) {
2778                 if (menu[selected].data == &opt_date)
2779                         toggle_date();
2780                 else if (menu[selected].data == &opt_author)
2781                         toggle_author();
2782                 else
2783                         toggle_view_option(menu[selected].data, menu[selected].text);
2784         }
2787 static void
2788 maximize_view(struct view *view)
2790         memset(display, 0, sizeof(display));
2791         current_view = 0;
2792         display[current_view] = view;
2793         resize_display();
2794         redraw_display(FALSE);
2795         report("");
2799 /*
2800  * Navigation
2801  */
2803 static bool
2804 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2806         if (lineno >= view->lines)
2807                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2809         if (offset > lineno || offset + view->height <= lineno) {
2810                 unsigned long half = view->height / 2;
2812                 if (lineno > half)
2813                         offset = lineno - half;
2814                 else
2815                         offset = 0;
2816         }
2818         if (offset != view->offset || lineno != view->lineno) {
2819                 view->offset = offset;
2820                 view->lineno = lineno;
2821                 return TRUE;
2822         }
2824         return FALSE;
2827 /* Scrolling backend */
2828 static void
2829 do_scroll_view(struct view *view, int lines)
2831         bool redraw_current_line = FALSE;
2833         /* The rendering expects the new offset. */
2834         view->offset += lines;
2836         assert(0 <= view->offset && view->offset < view->lines);
2837         assert(lines);
2839         /* Move current line into the view. */
2840         if (view->lineno < view->offset) {
2841                 view->lineno = view->offset;
2842                 redraw_current_line = TRUE;
2843         } else if (view->lineno >= view->offset + view->height) {
2844                 view->lineno = view->offset + view->height - 1;
2845                 redraw_current_line = TRUE;
2846         }
2848         assert(view->offset <= view->lineno && view->lineno < view->lines);
2850         /* Redraw the whole screen if scrolling is pointless. */
2851         if (view->height < ABS(lines)) {
2852                 redraw_view(view);
2854         } else {
2855                 int line = lines > 0 ? view->height - lines : 0;
2856                 int end = line + ABS(lines);
2858                 scrollok(view->win, TRUE);
2859                 wscrl(view->win, lines);
2860                 scrollok(view->win, FALSE);
2862                 while (line < end && draw_view_line(view, line))
2863                         line++;
2865                 if (redraw_current_line)
2866                         draw_view_line(view, view->lineno - view->offset);
2867                 wnoutrefresh(view->win);
2868         }
2870         view->has_scrolled = TRUE;
2871         report("");
2874 /* Scroll frontend */
2875 static void
2876 scroll_view(struct view *view, enum request request)
2878         int lines = 1;
2880         assert(view_is_displayed(view));
2882         switch (request) {
2883         case REQ_SCROLL_LEFT:
2884                 if (view->yoffset == 0) {
2885                         report("Cannot scroll beyond the first column");
2886                         return;
2887                 }
2888                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2889                         view->yoffset = 0;
2890                 else
2891                         view->yoffset -= apply_step(opt_hscroll, view->width);
2892                 redraw_view_from(view, 0);
2893                 report("");
2894                 return;
2895         case REQ_SCROLL_RIGHT:
2896                 view->yoffset += apply_step(opt_hscroll, view->width);
2897                 redraw_view(view);
2898                 report("");
2899                 return;
2900         case REQ_SCROLL_PAGE_DOWN:
2901                 lines = view->height;
2902         case REQ_SCROLL_LINE_DOWN:
2903                 if (view->offset + lines > view->lines)
2904                         lines = view->lines - view->offset;
2906                 if (lines == 0 || view->offset + view->height >= view->lines) {
2907                         report("Cannot scroll beyond the last line");
2908                         return;
2909                 }
2910                 break;
2912         case REQ_SCROLL_PAGE_UP:
2913                 lines = view->height;
2914         case REQ_SCROLL_LINE_UP:
2915                 if (lines > view->offset)
2916                         lines = view->offset;
2918                 if (lines == 0) {
2919                         report("Cannot scroll beyond the first line");
2920                         return;
2921                 }
2923                 lines = -lines;
2924                 break;
2926         default:
2927                 die("request %d not handled in switch", request);
2928         }
2930         do_scroll_view(view, lines);
2933 /* Cursor moving */
2934 static void
2935 move_view(struct view *view, enum request request)
2937         int scroll_steps = 0;
2938         int steps;
2940         switch (request) {
2941         case REQ_MOVE_FIRST_LINE:
2942                 steps = -view->lineno;
2943                 break;
2945         case REQ_MOVE_LAST_LINE:
2946                 steps = view->lines - view->lineno - 1;
2947                 break;
2949         case REQ_MOVE_PAGE_UP:
2950                 steps = view->height > view->lineno
2951                       ? -view->lineno : -view->height;
2952                 break;
2954         case REQ_MOVE_PAGE_DOWN:
2955                 steps = view->lineno + view->height >= view->lines
2956                       ? view->lines - view->lineno - 1 : view->height;
2957                 break;
2959         case REQ_MOVE_UP:
2960                 steps = -1;
2961                 break;
2963         case REQ_MOVE_DOWN:
2964                 steps = 1;
2965                 break;
2967         default:
2968                 die("request %d not handled in switch", request);
2969         }
2971         if (steps <= 0 && view->lineno == 0) {
2972                 report("Cannot move beyond the first line");
2973                 return;
2975         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2976                 report("Cannot move beyond the last line");
2977                 return;
2978         }
2980         /* Move the current line */
2981         view->lineno += steps;
2982         assert(0 <= view->lineno && view->lineno < view->lines);
2984         /* Check whether the view needs to be scrolled */
2985         if (view->lineno < view->offset ||
2986             view->lineno >= view->offset + view->height) {
2987                 scroll_steps = steps;
2988                 if (steps < 0 && -steps > view->offset) {
2989                         scroll_steps = -view->offset;
2991                 } else if (steps > 0) {
2992                         if (view->lineno == view->lines - 1 &&
2993                             view->lines > view->height) {
2994                                 scroll_steps = view->lines - view->offset - 1;
2995                                 if (scroll_steps >= view->height)
2996                                         scroll_steps -= view->height - 1;
2997                         }
2998                 }
2999         }
3001         if (!view_is_displayed(view)) {
3002                 view->offset += scroll_steps;
3003                 assert(0 <= view->offset && view->offset < view->lines);
3004                 view->ops->select(view, &view->line[view->lineno]);
3005                 return;
3006         }
3008         /* Repaint the old "current" line if we be scrolling */
3009         if (ABS(steps) < view->height)
3010                 draw_view_line(view, view->lineno - steps - view->offset);
3012         if (scroll_steps) {
3013                 do_scroll_view(view, scroll_steps);
3014                 return;
3015         }
3017         /* Draw the current line */
3018         draw_view_line(view, view->lineno - view->offset);
3020         wnoutrefresh(view->win);
3021         report("");
3025 /*
3026  * Searching
3027  */
3029 static void search_view(struct view *view, enum request request);
3031 static bool
3032 grep_text(struct view *view, const char *text[])
3034         regmatch_t pmatch;
3035         size_t i;
3037         for (i = 0; text[i]; i++)
3038                 if (*text[i] &&
3039                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3040                         return TRUE;
3041         return FALSE;
3044 static void
3045 select_view_line(struct view *view, unsigned long lineno)
3047         unsigned long old_lineno = view->lineno;
3048         unsigned long old_offset = view->offset;
3050         if (goto_view_line(view, view->offset, lineno)) {
3051                 if (view_is_displayed(view)) {
3052                         if (old_offset != view->offset) {
3053                                 redraw_view(view);
3054                         } else {
3055                                 draw_view_line(view, old_lineno - view->offset);
3056                                 draw_view_line(view, view->lineno - view->offset);
3057                                 wnoutrefresh(view->win);
3058                         }
3059                 } else {
3060                         view->ops->select(view, &view->line[view->lineno]);
3061                 }
3062         }
3065 static void
3066 find_next(struct view *view, enum request request)
3068         unsigned long lineno = view->lineno;
3069         int direction;
3071         if (!*view->grep) {
3072                 if (!*opt_search)
3073                         report("No previous search");
3074                 else
3075                         search_view(view, request);
3076                 return;
3077         }
3079         switch (request) {
3080         case REQ_SEARCH:
3081         case REQ_FIND_NEXT:
3082                 direction = 1;
3083                 break;
3085         case REQ_SEARCH_BACK:
3086         case REQ_FIND_PREV:
3087                 direction = -1;
3088                 break;
3090         default:
3091                 return;
3092         }
3094         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3095                 lineno += direction;
3097         /* Note, lineno is unsigned long so will wrap around in which case it
3098          * will become bigger than view->lines. */
3099         for (; lineno < view->lines; lineno += direction) {
3100                 if (view->ops->grep(view, &view->line[lineno])) {
3101                         select_view_line(view, lineno);
3102                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3103                         return;
3104                 }
3105         }
3107         report("No match found for '%s'", view->grep);
3110 static void
3111 search_view(struct view *view, enum request request)
3113         int regex_err;
3115         if (view->regex) {
3116                 regfree(view->regex);
3117                 *view->grep = 0;
3118         } else {
3119                 view->regex = calloc(1, sizeof(*view->regex));
3120                 if (!view->regex)
3121                         return;
3122         }
3124         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3125         if (regex_err != 0) {
3126                 char buf[SIZEOF_STR] = "unknown error";
3128                 regerror(regex_err, view->regex, buf, sizeof(buf));
3129                 report("Search failed: %s", buf);
3130                 return;
3131         }
3133         string_copy(view->grep, opt_search);
3135         find_next(view, request);
3138 /*
3139  * Incremental updating
3140  */
3142 static void
3143 reset_view(struct view *view)
3145         int i;
3147         for (i = 0; i < view->lines; i++)
3148                 free(view->line[i].data);
3149         free(view->line);
3151         view->p_offset = view->offset;
3152         view->p_yoffset = view->yoffset;
3153         view->p_lineno = view->lineno;
3155         view->line = NULL;
3156         view->offset = 0;
3157         view->yoffset = 0;
3158         view->lines  = 0;
3159         view->lineno = 0;
3160         view->vid[0] = 0;
3161         view->update_secs = 0;
3164 static const char *
3165 format_arg(const char *name)
3167         static struct {
3168                 const char *name;
3169                 size_t namelen;
3170                 const char *value;
3171                 const char *value_if_empty;
3172         } vars[] = {
3173 #define FORMAT_VAR(name, value, value_if_empty) \
3174         { name, STRING_SIZE(name), value, value_if_empty }
3175                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3176                 FORMAT_VAR("%(file)",           opt_file,       ""),
3177                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3178                 FORMAT_VAR("%(head)",           ref_head,       ""),
3179                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3180                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3181                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3182         };
3183         int i;
3185         for (i = 0; i < ARRAY_SIZE(vars); i++)
3186                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3187                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3189         report("Unknown replacement: `%s`", name);
3190         return NULL;
3193 static bool
3194 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3196         char buf[SIZEOF_STR];
3197         int argc;
3198         bool noreplace = flags == FORMAT_NONE;
3200         argv_free(dst_argv);
3202         for (argc = 0; src_argv[argc]; argc++) {
3203                 const char *arg = src_argv[argc];
3204                 size_t bufpos = 0;
3206                 while (arg) {
3207                         char *next = strstr(arg, "%(");
3208                         int len = next - arg;
3209                         const char *value;
3211                         if (!next || noreplace) {
3212                                 len = strlen(arg);
3213                                 value = "";
3215                         } else {
3216                                 value = format_arg(next);
3218                                 if (!value) {
3219                                         return FALSE;
3220                                 }
3221                         }
3223                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3224                                 return FALSE;
3226                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3227                 }
3229                 dst_argv[argc] = strdup(buf);
3230                 if (!dst_argv[argc])
3231                         break;
3232         }
3234         dst_argv[argc] = NULL;
3236         return src_argv[argc] == NULL;
3239 static bool
3240 restore_view_position(struct view *view)
3242         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3243                 return FALSE;
3245         /* Changing the view position cancels the restoring. */
3246         /* FIXME: Changing back to the first line is not detected. */
3247         if (view->offset != 0 || view->lineno != 0) {
3248                 view->p_restore = FALSE;
3249                 return FALSE;
3250         }
3252         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3253             view_is_displayed(view))
3254                 werase(view->win);
3256         view->yoffset = view->p_yoffset;
3257         view->p_restore = FALSE;
3259         return TRUE;
3262 static void
3263 end_update(struct view *view, bool force)
3265         if (!view->pipe)
3266                 return;
3267         while (!view->ops->read(view, NULL))
3268                 if (!force)
3269                         return;
3270         if (force)
3271                 io_kill(view->pipe);
3272         io_done(view->pipe);
3273         view->pipe = NULL;
3276 static void
3277 setup_update(struct view *view, const char *vid)
3279         reset_view(view);
3280         string_copy_rev(view->vid, vid);
3281         view->pipe = &view->io;
3282         view->start_time = time(NULL);
3285 static bool
3286 prepare_update(struct view *view, const char *argv[], const char *dir)
3288         if (view->pipe)
3289                 end_update(view, TRUE);
3290         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3293 static bool
3294 prepare_update_file(struct view *view, const char *name)
3296         if (view->pipe)
3297                 end_update(view, TRUE);
3298         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3301 static bool
3302 begin_update(struct view *view, bool refresh)
3304         if (view->pipe)
3305                 end_update(view, TRUE);
3307         if (!refresh) {
3308                 if (view->ops->prepare) {
3309                         if (!view->ops->prepare(view))
3310                                 return FALSE;
3311                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3312                         return FALSE;
3313                 }
3315                 /* Put the current ref_* value to the view title ref
3316                  * member. This is needed by the blob view. Most other
3317                  * views sets it automatically after loading because the
3318                  * first line is a commit line. */
3319                 string_copy_rev(view->ref, view->id);
3320         }
3322         if (!io_start(&view->io))
3323                 return FALSE;
3325         setup_update(view, view->id);
3327         return TRUE;
3330 static bool
3331 update_view(struct view *view)
3333         char out_buffer[BUFSIZ * 2];
3334         char *line;
3335         /* Clear the view and redraw everything since the tree sorting
3336          * might have rearranged things. */
3337         bool redraw = view->lines == 0;
3338         bool can_read = TRUE;
3340         if (!view->pipe)
3341                 return TRUE;
3343         if (!io_can_read(view->pipe)) {
3344                 if (view->lines == 0 && view_is_displayed(view)) {
3345                         time_t secs = time(NULL) - view->start_time;
3347                         if (secs > 1 && secs > view->update_secs) {
3348                                 if (view->update_secs == 0)
3349                                         redraw_view(view);
3350                                 update_view_title(view);
3351                                 view->update_secs = secs;
3352                         }
3353                 }
3354                 return TRUE;
3355         }
3357         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3358                 if (opt_iconv_in != ICONV_NONE) {
3359                         ICONV_CONST char *inbuf = line;
3360                         size_t inlen = strlen(line) + 1;
3362                         char *outbuf = out_buffer;
3363                         size_t outlen = sizeof(out_buffer);
3365                         size_t ret;
3367                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3368                         if (ret != (size_t) -1)
3369                                 line = out_buffer;
3370                 }
3372                 if (!view->ops->read(view, line)) {
3373                         report("Allocation failure");
3374                         end_update(view, TRUE);
3375                         return FALSE;
3376                 }
3377         }
3379         {
3380                 unsigned long lines = view->lines;
3381                 int digits;
3383                 for (digits = 0; lines; digits++)
3384                         lines /= 10;
3386                 /* Keep the displayed view in sync with line number scaling. */
3387                 if (digits != view->digits) {
3388                         view->digits = digits;
3389                         if (opt_line_number || view->type == VIEW_BLAME)
3390                                 redraw = TRUE;
3391                 }
3392         }
3394         if (io_error(view->pipe)) {
3395                 report("Failed to read: %s", io_strerror(view->pipe));
3396                 end_update(view, TRUE);
3398         } else if (io_eof(view->pipe)) {
3399                 if (view_is_displayed(view))
3400                         report("");
3401                 end_update(view, FALSE);
3402         }
3404         if (restore_view_position(view))
3405                 redraw = TRUE;
3407         if (!view_is_displayed(view))
3408                 return TRUE;
3410         if (redraw)
3411                 redraw_view_from(view, 0);
3412         else
3413                 redraw_view_dirty(view);
3415         /* Update the title _after_ the redraw so that if the redraw picks up a
3416          * commit reference in view->ref it'll be available here. */
3417         update_view_title(view);
3418         return TRUE;
3421 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3423 static struct line *
3424 add_line_data(struct view *view, void *data, enum line_type type)
3426         struct line *line;
3428         if (!realloc_lines(&view->line, view->lines, 1))
3429                 return NULL;
3431         line = &view->line[view->lines++];
3432         memset(line, 0, sizeof(*line));
3433         line->type = type;
3434         line->data = data;
3435         line->dirty = 1;
3437         return line;
3440 static struct line *
3441 add_line_text(struct view *view, const char *text, enum line_type type)
3443         char *data = text ? strdup(text) : NULL;
3445         return data ? add_line_data(view, data, type) : NULL;
3448 static struct line *
3449 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3451         char buf[SIZEOF_STR];
3452         va_list args;
3454         va_start(args, fmt);
3455         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3456                 buf[0] = 0;
3457         va_end(args);
3459         return buf[0] ? add_line_text(view, buf, type) : NULL;
3462 /*
3463  * View opening
3464  */
3466 enum open_flags {
3467         OPEN_DEFAULT = 0,       /* Use default view switching. */
3468         OPEN_SPLIT = 1,         /* Split current view. */
3469         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3470         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3471         OPEN_PREPARED = 32,     /* Open already prepared command. */
3472 };
3474 static void
3475 open_view(struct view *prev, enum request request, enum open_flags flags)
3477         bool split = !!(flags & OPEN_SPLIT);
3478         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3479         bool nomaximize = !!(flags & OPEN_REFRESH);
3480         struct view *view = VIEW(request);
3481         int nviews = displayed_views();
3482         struct view *base_view = display[0];
3484         if (view == prev && nviews == 1 && !reload) {
3485                 report("Already in %s view", view->name);
3486                 return;
3487         }
3489         if (view->git_dir && !opt_git_dir[0]) {
3490                 report("The %s view is disabled in pager view", view->name);
3491                 return;
3492         }
3494         if (split) {
3495                 display[1] = view;
3496                 current_view = 1;
3497                 view->parent = prev;
3498         } else if (!nomaximize) {
3499                 /* Maximize the current view. */
3500                 memset(display, 0, sizeof(display));
3501                 current_view = 0;
3502                 display[current_view] = view;
3503         }
3505         /* No prev signals that this is the first loaded view. */
3506         if (prev && view != prev) {
3507                 view->prev = prev;
3508         }
3510         /* Resize the view when switching between split- and full-screen,
3511          * or when switching between two different full-screen views. */
3512         if (nviews != displayed_views() ||
3513             (nviews == 1 && base_view != display[0]))
3514                 resize_display();
3516         if (view->ops->open) {
3517                 if (view->pipe)
3518                         end_update(view, TRUE);
3519                 if (!view->ops->open(view)) {
3520                         report("Failed to load %s view", view->name);
3521                         return;
3522                 }
3523                 restore_view_position(view);
3525         } else if ((reload || strcmp(view->vid, view->id)) &&
3526                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3527                 report("Failed to load %s view", view->name);
3528                 return;
3529         }
3531         if (split && prev->lineno - prev->offset >= prev->height) {
3532                 /* Take the title line into account. */
3533                 int lines = prev->lineno - prev->offset - prev->height + 1;
3535                 /* Scroll the view that was split if the current line is
3536                  * outside the new limited view. */
3537                 do_scroll_view(prev, lines);
3538         }
3540         if (prev && view != prev && split && view_is_displayed(prev)) {
3541                 /* "Blur" the previous view. */
3542                 update_view_title(prev);
3543         }
3545         if (view->pipe && view->lines == 0) {
3546                 /* Clear the old view and let the incremental updating refill
3547                  * the screen. */
3548                 werase(view->win);
3549                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3550                 report("");
3551         } else if (view_is_displayed(view)) {
3552                 redraw_view(view);
3553                 report("");
3554         }
3557 static void
3558 open_external_viewer(const char *argv[], const char *dir)
3560         def_prog_mode();           /* save current tty modes */
3561         endwin();                  /* restore original tty modes */
3562         io_run_fg(argv, dir);
3563         fprintf(stderr, "Press Enter to continue");
3564         getc(opt_tty);
3565         reset_prog_mode();
3566         redraw_display(TRUE);
3569 static void
3570 open_mergetool(const char *file)
3572         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3574         open_external_viewer(mergetool_argv, opt_cdup);
3577 static void
3578 open_editor(const char *file)
3580         const char *editor_argv[] = { "vi", file, NULL };
3581         const char *editor;
3583         editor = getenv("GIT_EDITOR");
3584         if (!editor && *opt_editor)
3585                 editor = opt_editor;
3586         if (!editor)
3587                 editor = getenv("VISUAL");
3588         if (!editor)
3589                 editor = getenv("EDITOR");
3590         if (!editor)
3591                 editor = "vi";
3593         editor_argv[0] = editor;
3594         open_external_viewer(editor_argv, opt_cdup);
3597 static void
3598 open_run_request(enum request request)
3600         struct run_request *req = get_run_request(request);
3601         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3603         if (!req) {
3604                 report("Unknown run request");
3605                 return;
3606         }
3608         if (format_argv(argv, req->argv, FORMAT_ALL))
3609                 open_external_viewer(argv, NULL);
3610         argv_free(argv);
3613 /*
3614  * User request switch noodle
3615  */
3617 static int
3618 view_driver(struct view *view, enum request request)
3620         int i;
3622         if (request == REQ_NONE)
3623                 return TRUE;
3625         if (request > REQ_NONE) {
3626                 open_run_request(request);
3627                 view_request(view, REQ_REFRESH);
3628                 return TRUE;
3629         }
3631         request = view_request(view, request);
3632         if (request == REQ_NONE)
3633                 return TRUE;
3635         switch (request) {
3636         case REQ_MOVE_UP:
3637         case REQ_MOVE_DOWN:
3638         case REQ_MOVE_PAGE_UP:
3639         case REQ_MOVE_PAGE_DOWN:
3640         case REQ_MOVE_FIRST_LINE:
3641         case REQ_MOVE_LAST_LINE:
3642                 move_view(view, request);
3643                 break;
3645         case REQ_SCROLL_LEFT:
3646         case REQ_SCROLL_RIGHT:
3647         case REQ_SCROLL_LINE_DOWN:
3648         case REQ_SCROLL_LINE_UP:
3649         case REQ_SCROLL_PAGE_DOWN:
3650         case REQ_SCROLL_PAGE_UP:
3651                 scroll_view(view, request);
3652                 break;
3654         case REQ_VIEW_BLAME:
3655                 if (!opt_file[0]) {
3656                         report("No file chosen, press %s to open tree view",
3657                                get_key(view->keymap, REQ_VIEW_TREE));
3658                         break;
3659                 }
3660                 open_view(view, request, OPEN_DEFAULT);
3661                 break;
3663         case REQ_VIEW_BLOB:
3664                 if (!ref_blob[0]) {
3665                         report("No file chosen, press %s to open tree view",
3666                                get_key(view->keymap, REQ_VIEW_TREE));
3667                         break;
3668                 }
3669                 open_view(view, request, OPEN_DEFAULT);
3670                 break;
3672         case REQ_VIEW_PAGER:
3673                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3674                         report("No pager content, press %s to run command from prompt",
3675                                get_key(view->keymap, REQ_PROMPT));
3676                         break;
3677                 }
3678                 open_view(view, request, OPEN_DEFAULT);
3679                 break;
3681         case REQ_VIEW_STAGE:
3682                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3683                         report("No stage content, press %s to open the status view and choose file",
3684                                get_key(view->keymap, REQ_VIEW_STATUS));
3685                         break;
3686                 }
3687                 open_view(view, request, OPEN_DEFAULT);
3688                 break;
3690         case REQ_VIEW_STATUS:
3691                 if (opt_is_inside_work_tree == FALSE) {
3692                         report("The status view requires a working tree");
3693                         break;
3694                 }
3695                 open_view(view, request, OPEN_DEFAULT);
3696                 break;
3698         case REQ_VIEW_MAIN:
3699         case REQ_VIEW_DIFF:
3700         case REQ_VIEW_LOG:
3701         case REQ_VIEW_TREE:
3702         case REQ_VIEW_HELP:
3703         case REQ_VIEW_BRANCH:
3704                 open_view(view, request, OPEN_DEFAULT);
3705                 break;
3707         case REQ_NEXT:
3708         case REQ_PREVIOUS:
3709                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3711                 if (view->parent) {
3712                         int line;
3714                         view = view->parent;
3715                         line = view->lineno;
3716                         move_view(view, request);
3717                         if (view_is_displayed(view))
3718                                 update_view_title(view);
3719                         if (line != view->lineno)
3720                                 view_request(view, REQ_ENTER);
3721                 } else {
3722                         move_view(view, request);
3723                 }
3724                 break;
3726         case REQ_VIEW_NEXT:
3727         {
3728                 int nviews = displayed_views();
3729                 int next_view = (current_view + 1) % nviews;
3731                 if (next_view == current_view) {
3732                         report("Only one view is displayed");
3733                         break;
3734                 }
3736                 current_view = next_view;
3737                 /* Blur out the title of the previous view. */
3738                 update_view_title(view);
3739                 report("");
3740                 break;
3741         }
3742         case REQ_REFRESH:
3743                 report("Refreshing is not yet supported for the %s view", view->name);
3744                 break;
3746         case REQ_MAXIMIZE:
3747                 if (displayed_views() == 2)
3748                         maximize_view(view);
3749                 break;
3751         case REQ_OPTIONS:
3752                 open_option_menu();
3753                 break;
3755         case REQ_TOGGLE_LINENO:
3756                 toggle_view_option(&opt_line_number, "line numbers");
3757                 break;
3759         case REQ_TOGGLE_DATE:
3760                 toggle_date();
3761                 break;
3763         case REQ_TOGGLE_AUTHOR:
3764                 toggle_author();
3765                 break;
3767         case REQ_TOGGLE_REV_GRAPH:
3768                 toggle_view_option(&opt_rev_graph, "revision graph display");
3769                 break;
3771         case REQ_TOGGLE_REFS:
3772                 toggle_view_option(&opt_show_refs, "reference display");
3773                 break;
3775         case REQ_TOGGLE_SORT_FIELD:
3776         case REQ_TOGGLE_SORT_ORDER:
3777                 report("Sorting is not yet supported for the %s view", view->name);
3778                 break;
3780         case REQ_SEARCH:
3781         case REQ_SEARCH_BACK:
3782                 search_view(view, request);
3783                 break;
3785         case REQ_FIND_NEXT:
3786         case REQ_FIND_PREV:
3787                 find_next(view, request);
3788                 break;
3790         case REQ_STOP_LOADING:
3791                 foreach_view(view, i) {
3792                         if (view->pipe)
3793                                 report("Stopped loading the %s view", view->name),
3794                         end_update(view, TRUE);
3795                 }
3796                 break;
3798         case REQ_SHOW_VERSION:
3799                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3800                 return TRUE;
3802         case REQ_SCREEN_REDRAW:
3803                 redraw_display(TRUE);
3804                 break;
3806         case REQ_EDIT:
3807                 report("Nothing to edit");
3808                 break;
3810         case REQ_ENTER:
3811                 report("Nothing to enter");
3812                 break;
3814         case REQ_VIEW_CLOSE:
3815                 /* XXX: Mark closed views by letting view->prev point to the
3816                  * view itself. Parents to closed view should never be
3817                  * followed. */
3818                 if (view->prev && view->prev != view) {
3819                         maximize_view(view->prev);
3820                         view->prev = view;
3821                         break;
3822                 }
3823                 /* Fall-through */
3824         case REQ_QUIT:
3825                 return FALSE;
3827         default:
3828                 report("Unknown key, press %s for help",
3829                        get_key(view->keymap, REQ_VIEW_HELP));
3830                 return TRUE;
3831         }
3833         return TRUE;
3837 /*
3838  * View backend utilities
3839  */
3841 enum sort_field {
3842         ORDERBY_NAME,
3843         ORDERBY_DATE,
3844         ORDERBY_AUTHOR,
3845 };
3847 struct sort_state {
3848         const enum sort_field *fields;
3849         size_t size, current;
3850         bool reverse;
3851 };
3853 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3854 #define get_sort_field(state) ((state).fields[(state).current])
3855 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3857 static void
3858 sort_view(struct view *view, enum request request, struct sort_state *state,
3859           int (*compare)(const void *, const void *))
3861         switch (request) {
3862         case REQ_TOGGLE_SORT_FIELD:
3863                 state->current = (state->current + 1) % state->size;
3864                 break;
3866         case REQ_TOGGLE_SORT_ORDER:
3867                 state->reverse = !state->reverse;
3868                 break;
3869         default:
3870                 die("Not a sort request");
3871         }
3873         qsort(view->line, view->lines, sizeof(*view->line), compare);
3874         redraw_view(view);
3877 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3879 /* Small author cache to reduce memory consumption. It uses binary
3880  * search to lookup or find place to position new entries. No entries
3881  * are ever freed. */
3882 static const char *
3883 get_author(const char *name)
3885         static const char **authors;
3886         static size_t authors_size;
3887         int from = 0, to = authors_size - 1;
3889         while (from <= to) {
3890                 size_t pos = (to + from) / 2;
3891                 int cmp = strcmp(name, authors[pos]);
3893                 if (!cmp)
3894                         return authors[pos];
3896                 if (cmp < 0)
3897                         to = pos - 1;
3898                 else
3899                         from = pos + 1;
3900         }
3902         if (!realloc_authors(&authors, authors_size, 1))
3903                 return NULL;
3904         name = strdup(name);
3905         if (!name)
3906                 return NULL;
3908         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3909         authors[from] = name;
3910         authors_size++;
3912         return name;
3915 static void
3916 parse_timesec(struct time *time, const char *sec)
3918         time->sec = (time_t) atol(sec);
3921 static void
3922 parse_timezone(struct time *time, const char *zone)
3924         long tz;
3926         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3927         tz += ('0' - zone[2]) * 60 * 60;
3928         tz += ('0' - zone[3]) * 60 * 10;
3929         tz += ('0' - zone[4]) * 60;
3931         if (zone[0] == '-')
3932                 tz = -tz;
3934         time->tz = tz;
3935         time->sec -= tz;
3938 /* Parse author lines where the name may be empty:
3939  *      author  <email@address.tld> 1138474660 +0100
3940  */
3941 static void
3942 parse_author_line(char *ident, const char **author, struct time *time)
3944         char *nameend = strchr(ident, '<');
3945         char *emailend = strchr(ident, '>');
3947         if (nameend && emailend)
3948                 *nameend = *emailend = 0;
3949         ident = chomp_string(ident);
3950         if (!*ident) {
3951                 if (nameend)
3952                         ident = chomp_string(nameend + 1);
3953                 if (!*ident)
3954                         ident = "Unknown";
3955         }
3957         *author = get_author(ident);
3959         /* Parse epoch and timezone */
3960         if (emailend && emailend[1] == ' ') {
3961                 char *secs = emailend + 2;
3962                 char *zone = strchr(secs, ' ');
3964                 parse_timesec(time, secs);
3966                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3967                         parse_timezone(time, zone + 1);
3968         }
3971 static bool
3972 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3974         char rev[SIZEOF_REV];
3975         const char *revlist_argv[] = {
3976                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3977         };
3978         struct menu_item *items;
3979         char text[SIZEOF_STR];
3980         bool ok = TRUE;
3981         int i;
3983         items = calloc(*parents + 1, sizeof(*items));
3984         if (!items)
3985                 return FALSE;
3987         for (i = 0; i < *parents; i++) {
3988                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3989                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3990                     !(items[i].text = strdup(text))) {
3991                         ok = FALSE;
3992                         break;
3993                 }
3994         }
3996         if (ok) {
3997                 *parents = 0;
3998                 ok = prompt_menu("Select parent", items, parents);
3999         }
4000         for (i = 0; items[i].text; i++)
4001                 free((char *) items[i].text);
4002         free(items);
4003         return ok;
4006 static bool
4007 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4009         char buf[SIZEOF_STR * 4];
4010         const char *revlist_argv[] = {
4011                 "git", "log", "--no-color", "-1",
4012                         "--pretty=format:%P", id, "--", path, NULL
4013         };
4014         int parents;
4016         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4017             (parents = strlen(buf) / 40) < 0) {
4018                 report("Failed to get parent information");
4019                 return FALSE;
4021         } else if (parents == 0) {
4022                 if (path)
4023                         report("Path '%s' does not exist in the parent", path);
4024                 else
4025                         report("The selected commit has no parents");
4026                 return FALSE;
4027         }
4029         if (parents == 1)
4030                 parents = 0;
4031         else if (!open_commit_parent_menu(buf, &parents))
4032                 return FALSE;
4034         string_copy_rev(rev, &buf[41 * parents]);
4035         return TRUE;
4038 /*
4039  * Pager backend
4040  */
4042 static bool
4043 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4045         char text[SIZEOF_STR];
4047         if (opt_line_number && draw_lineno(view, lineno))
4048                 return TRUE;
4050         string_expand(text, sizeof(text), line->data, opt_tab_size);
4051         draw_text(view, line->type, text, TRUE);
4052         return TRUE;
4055 static bool
4056 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4058         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4059         char ref[SIZEOF_STR];
4061         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4062                 return TRUE;
4064         /* This is the only fatal call, since it can "corrupt" the buffer. */
4065         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4066                 return FALSE;
4068         return TRUE;
4071 static void
4072 add_pager_refs(struct view *view, struct line *line)
4074         char buf[SIZEOF_STR];
4075         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4076         struct ref_list *list;
4077         size_t bufpos = 0, i;
4078         const char *sep = "Refs: ";
4079         bool is_tag = FALSE;
4081         assert(line->type == LINE_COMMIT);
4083         list = get_ref_list(commit_id);
4084         if (!list) {
4085                 if (view->type == VIEW_DIFF)
4086                         goto try_add_describe_ref;
4087                 return;
4088         }
4090         for (i = 0; i < list->size; i++) {
4091                 struct ref *ref = list->refs[i];
4092                 const char *fmt = ref->tag    ? "%s[%s]" :
4093                                   ref->remote ? "%s<%s>" : "%s%s";
4095                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4096                         return;
4097                 sep = ", ";
4098                 if (ref->tag)
4099                         is_tag = TRUE;
4100         }
4102         if (!is_tag && view->type == VIEW_DIFF) {
4103 try_add_describe_ref:
4104                 /* Add <tag>-g<commit_id> "fake" reference. */
4105                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4106                         return;
4107         }
4109         if (bufpos == 0)
4110                 return;
4112         add_line_text(view, buf, LINE_PP_REFS);
4115 static bool
4116 pager_read(struct view *view, char *data)
4118         struct line *line;
4120         if (!data)
4121                 return TRUE;
4123         line = add_line_text(view, data, get_line_type(data));
4124         if (!line)
4125                 return FALSE;
4127         if (line->type == LINE_COMMIT &&
4128             (view->type == VIEW_DIFF ||
4129              view->type == VIEW_LOG))
4130                 add_pager_refs(view, line);
4132         return TRUE;
4135 static enum request
4136 pager_request(struct view *view, enum request request, struct line *line)
4138         int split = 0;
4140         if (request != REQ_ENTER)
4141                 return request;
4143         if (line->type == LINE_COMMIT &&
4144            (view->type == VIEW_LOG ||
4145             view->type == VIEW_PAGER)) {
4146                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4147                 split = 1;
4148         }
4150         /* Always scroll the view even if it was split. That way
4151          * you can use Enter to scroll through the log view and
4152          * split open each commit diff. */
4153         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4155         /* FIXME: A minor workaround. Scrolling the view will call report("")
4156          * but if we are scrolling a non-current view this won't properly
4157          * update the view title. */
4158         if (split)
4159                 update_view_title(view);
4161         return REQ_NONE;
4164 static bool
4165 pager_grep(struct view *view, struct line *line)
4167         const char *text[] = { line->data, NULL };
4169         return grep_text(view, text);
4172 static void
4173 pager_select(struct view *view, struct line *line)
4175         if (line->type == LINE_COMMIT) {
4176                 char *text = (char *)line->data + STRING_SIZE("commit ");
4178                 if (view->type != VIEW_PAGER)
4179                         string_copy_rev(view->ref, text);
4180                 string_copy_rev(ref_commit, text);
4181         }
4184 static struct view_ops pager_ops = {
4185         "line",
4186         NULL,
4187         NULL,
4188         pager_read,
4189         pager_draw,
4190         pager_request,
4191         pager_grep,
4192         pager_select,
4193 };
4195 static const char *log_argv[SIZEOF_ARG] = {
4196         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4197 };
4199 static enum request
4200 log_request(struct view *view, enum request request, struct line *line)
4202         switch (request) {
4203         case REQ_REFRESH:
4204                 load_refs();
4205                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4206                 return REQ_NONE;
4207         default:
4208                 return pager_request(view, request, line);
4209         }
4212 static struct view_ops log_ops = {
4213         "line",
4214         log_argv,
4215         NULL,
4216         pager_read,
4217         pager_draw,
4218         log_request,
4219         pager_grep,
4220         pager_select,
4221 };
4223 static const char *diff_argv[SIZEOF_ARG] = {
4224         "git", "show", "--pretty=fuller", "--no-color", "--root",
4225                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4226 };
4228 static struct view_ops diff_ops = {
4229         "line",
4230         diff_argv,
4231         NULL,
4232         pager_read,
4233         pager_draw,
4234         pager_request,
4235         pager_grep,
4236         pager_select,
4237 };
4239 /*
4240  * Help backend
4241  */
4243 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4245 static bool
4246 help_open_keymap_title(struct view *view, enum keymap keymap)
4248         struct line *line;
4250         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4251                                help_keymap_hidden[keymap] ? '+' : '-',
4252                                enum_name(keymap_table[keymap]));
4253         if (line)
4254                 line->other = keymap;
4256         return help_keymap_hidden[keymap];
4259 static void
4260 help_open_keymap(struct view *view, enum keymap keymap)
4262         const char *group = NULL;
4263         char buf[SIZEOF_STR];
4264         size_t bufpos;
4265         bool add_title = TRUE;
4266         int i;
4268         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4269                 const char *key = NULL;
4271                 if (req_info[i].request == REQ_NONE)
4272                         continue;
4274                 if (!req_info[i].request) {
4275                         group = req_info[i].help;
4276                         continue;
4277                 }
4279                 key = get_keys(keymap, req_info[i].request, TRUE);
4280                 if (!key || !*key)
4281                         continue;
4283                 if (add_title && help_open_keymap_title(view, keymap))
4284                         return;
4285                 add_title = FALSE;
4287                 if (group) {
4288                         add_line_text(view, group, LINE_HELP_GROUP);
4289                         group = NULL;
4290                 }
4292                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4293                                 enum_name(req_info[i]), req_info[i].help);
4294         }
4296         group = "External commands:";
4298         for (i = 0; i < run_requests; i++) {
4299                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4300                 const char *key;
4301                 int argc;
4303                 if (!req || req->keymap != keymap)
4304                         continue;
4306                 key = get_key_name(req->key);
4307                 if (!*key)
4308                         key = "(no key defined)";
4310                 if (add_title && help_open_keymap_title(view, keymap))
4311                         return;
4312                 if (group) {
4313                         add_line_text(view, group, LINE_HELP_GROUP);
4314                         group = NULL;
4315                 }
4317                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4318                         if (!string_format_from(buf, &bufpos, "%s%s",
4319                                                 argc ? " " : "", req->argv[argc]))
4320                                 return;
4322                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4323         }
4326 static bool
4327 help_open(struct view *view)
4329         enum keymap keymap;
4331         reset_view(view);
4332         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4333         add_line_text(view, "", LINE_DEFAULT);
4335         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4336                 help_open_keymap(view, keymap);
4338         return TRUE;
4341 static enum request
4342 help_request(struct view *view, enum request request, struct line *line)
4344         switch (request) {
4345         case REQ_ENTER:
4346                 if (line->type == LINE_HELP_KEYMAP) {
4347                         help_keymap_hidden[line->other] =
4348                                 !help_keymap_hidden[line->other];
4349                         view->p_restore = TRUE;
4350                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4351                 }
4353                 return REQ_NONE;
4354         default:
4355                 return pager_request(view, request, line);
4356         }
4359 static struct view_ops help_ops = {
4360         "line",
4361         NULL,
4362         help_open,
4363         NULL,
4364         pager_draw,
4365         help_request,
4366         pager_grep,
4367         pager_select,
4368 };
4371 /*
4372  * Tree backend
4373  */
4375 struct tree_stack_entry {
4376         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4377         unsigned long lineno;           /* Line number to restore */
4378         char *name;                     /* Position of name in opt_path */
4379 };
4381 /* The top of the path stack. */
4382 static struct tree_stack_entry *tree_stack = NULL;
4383 unsigned long tree_lineno = 0;
4385 static void
4386 pop_tree_stack_entry(void)
4388         struct tree_stack_entry *entry = tree_stack;
4390         tree_lineno = entry->lineno;
4391         entry->name[0] = 0;
4392         tree_stack = entry->prev;
4393         free(entry);
4396 static void
4397 push_tree_stack_entry(const char *name, unsigned long lineno)
4399         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4400         size_t pathlen = strlen(opt_path);
4402         if (!entry)
4403                 return;
4405         entry->prev = tree_stack;
4406         entry->name = opt_path + pathlen;
4407         tree_stack = entry;
4409         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4410                 pop_tree_stack_entry();
4411                 return;
4412         }
4414         /* Move the current line to the first tree entry. */
4415         tree_lineno = 1;
4416         entry->lineno = lineno;
4419 /* Parse output from git-ls-tree(1):
4420  *
4421  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4422  */
4424 #define SIZEOF_TREE_ATTR \
4425         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4427 #define SIZEOF_TREE_MODE \
4428         STRING_SIZE("100644 ")
4430 #define TREE_ID_OFFSET \
4431         STRING_SIZE("100644 blob ")
4433 struct tree_entry {
4434         char id[SIZEOF_REV];
4435         mode_t mode;
4436         struct time time;               /* Date from the author ident. */
4437         const char *author;             /* Author of the commit. */
4438         char name[1];
4439 };
4441 static const char *
4442 tree_path(const struct line *line)
4444         return ((struct tree_entry *) line->data)->name;
4447 static int
4448 tree_compare_entry(const struct line *line1, const struct line *line2)
4450         if (line1->type != line2->type)
4451                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4452         return strcmp(tree_path(line1), tree_path(line2));
4455 static const enum sort_field tree_sort_fields[] = {
4456         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4457 };
4458 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4460 static int
4461 tree_compare(const void *l1, const void *l2)
4463         const struct line *line1 = (const struct line *) l1;
4464         const struct line *line2 = (const struct line *) l2;
4465         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4466         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4468         if (line1->type == LINE_TREE_HEAD)
4469                 return -1;
4470         if (line2->type == LINE_TREE_HEAD)
4471                 return 1;
4473         switch (get_sort_field(tree_sort_state)) {
4474         case ORDERBY_DATE:
4475                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4477         case ORDERBY_AUTHOR:
4478                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4480         case ORDERBY_NAME:
4481         default:
4482                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4483         }
4487 static struct line *
4488 tree_entry(struct view *view, enum line_type type, const char *path,
4489            const char *mode, const char *id)
4491         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4492         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4494         if (!entry || !line) {
4495                 free(entry);
4496                 return NULL;
4497         }
4499         strncpy(entry->name, path, strlen(path));
4500         if (mode)
4501                 entry->mode = strtoul(mode, NULL, 8);
4502         if (id)
4503                 string_copy_rev(entry->id, id);
4505         return line;
4508 static bool
4509 tree_read_date(struct view *view, char *text, bool *read_date)
4511         static const char *author_name;
4512         static struct time author_time;
4514         if (!text && *read_date) {
4515                 *read_date = FALSE;
4516                 return TRUE;
4518         } else if (!text) {
4519                 char *path = *opt_path ? opt_path : ".";
4520                 /* Find next entry to process */
4521                 const char *log_file[] = {
4522                         "git", "log", "--no-color", "--pretty=raw",
4523                                 "--cc", "--raw", view->id, "--", path, NULL
4524                 };
4525                 struct io io = {};
4527                 if (!view->lines) {
4528                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4529                         report("Tree is empty");
4530                         return TRUE;
4531                 }
4533                 if (!io_run_rd(&io, log_file, opt_cdup)) {
4534                         report("Failed to load tree data");
4535                         return TRUE;
4536                 }
4538                 io_done(view->pipe);
4539                 view->io = io;
4540                 *read_date = TRUE;
4541                 return FALSE;
4543         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4544                 parse_author_line(text + STRING_SIZE("author "),
4545                                   &author_name, &author_time);
4547         } else if (*text == ':') {
4548                 char *pos;
4549                 size_t annotated = 1;
4550                 size_t i;
4552                 pos = strchr(text, '\t');
4553                 if (!pos)
4554                         return TRUE;
4555                 text = pos + 1;
4556                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4557                         text += strlen(opt_path);
4558                 pos = strchr(text, '/');
4559                 if (pos)
4560                         *pos = 0;
4562                 for (i = 1; i < view->lines; i++) {
4563                         struct line *line = &view->line[i];
4564                         struct tree_entry *entry = line->data;
4566                         annotated += !!entry->author;
4567                         if (entry->author || strcmp(entry->name, text))
4568                                 continue;
4570                         entry->author = author_name;
4571                         entry->time = author_time;
4572                         line->dirty = 1;
4573                         break;
4574                 }
4576                 if (annotated == view->lines)
4577                         io_kill(view->pipe);
4578         }
4579         return TRUE;
4582 static bool
4583 tree_read(struct view *view, char *text)
4585         static bool read_date = FALSE;
4586         struct tree_entry *data;
4587         struct line *entry, *line;
4588         enum line_type type;
4589         size_t textlen = text ? strlen(text) : 0;
4590         char *path = text + SIZEOF_TREE_ATTR;
4592         if (read_date || !text)
4593                 return tree_read_date(view, text, &read_date);
4595         if (textlen <= SIZEOF_TREE_ATTR)
4596                 return FALSE;
4597         if (view->lines == 0 &&
4598             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4599                 return FALSE;
4601         /* Strip the path part ... */
4602         if (*opt_path) {
4603                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4604                 size_t striplen = strlen(opt_path);
4606                 if (pathlen > striplen)
4607                         memmove(path, path + striplen,
4608                                 pathlen - striplen + 1);
4610                 /* Insert "link" to parent directory. */
4611                 if (view->lines == 1 &&
4612                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4613                         return FALSE;
4614         }
4616         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4617         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4618         if (!entry)
4619                 return FALSE;
4620         data = entry->data;
4622         /* Skip "Directory ..." and ".." line. */
4623         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4624                 if (tree_compare_entry(line, entry) <= 0)
4625                         continue;
4627                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4629                 line->data = data;
4630                 line->type = type;
4631                 for (; line <= entry; line++)
4632                         line->dirty = line->cleareol = 1;
4633                 return TRUE;
4634         }
4636         if (tree_lineno > view->lineno) {
4637                 view->lineno = tree_lineno;
4638                 tree_lineno = 0;
4639         }
4641         return TRUE;
4644 static bool
4645 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4647         struct tree_entry *entry = line->data;
4649         if (line->type == LINE_TREE_HEAD) {
4650                 if (draw_text(view, line->type, "Directory path /", TRUE))
4651                         return TRUE;
4652         } else {
4653                 if (draw_mode(view, entry->mode))
4654                         return TRUE;
4656                 if (opt_author && draw_author(view, entry->author))
4657                         return TRUE;
4659                 if (opt_date && draw_date(view, &entry->time))
4660                         return TRUE;
4661         }
4662         if (draw_text(view, line->type, entry->name, TRUE))
4663                 return TRUE;
4664         return TRUE;
4667 static void
4668 open_blob_editor(const char *id)
4670         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4671         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4672         int fd = mkstemp(file);
4674         if (fd == -1)
4675                 report("Failed to create temporary file");
4676         else if (!io_run_append(blob_argv, fd))
4677                 report("Failed to save blob data to file");
4678         else
4679                 open_editor(file);
4680         if (fd != -1)
4681                 unlink(file);
4684 static enum request
4685 tree_request(struct view *view, enum request request, struct line *line)
4687         enum open_flags flags;
4688         struct tree_entry *entry = line->data;
4690         switch (request) {
4691         case REQ_VIEW_BLAME:
4692                 if (line->type != LINE_TREE_FILE) {
4693                         report("Blame only supported for files");
4694                         return REQ_NONE;
4695                 }
4697                 string_copy(opt_ref, view->vid);
4698                 return request;
4700         case REQ_EDIT:
4701                 if (line->type != LINE_TREE_FILE) {
4702                         report("Edit only supported for files");
4703                 } else if (!is_head_commit(view->vid)) {
4704                         open_blob_editor(entry->id);
4705                 } else {
4706                         open_editor(opt_file);
4707                 }
4708                 return REQ_NONE;
4710         case REQ_TOGGLE_SORT_FIELD:
4711         case REQ_TOGGLE_SORT_ORDER:
4712                 sort_view(view, request, &tree_sort_state, tree_compare);
4713                 return REQ_NONE;
4715         case REQ_PARENT:
4716                 if (!*opt_path) {
4717                         /* quit view if at top of tree */
4718                         return REQ_VIEW_CLOSE;
4719                 }
4720                 /* fake 'cd  ..' */
4721                 line = &view->line[1];
4722                 break;
4724         case REQ_ENTER:
4725                 break;
4727         default:
4728                 return request;
4729         }
4731         /* Cleanup the stack if the tree view is at a different tree. */
4732         while (!*opt_path && tree_stack)
4733                 pop_tree_stack_entry();
4735         switch (line->type) {
4736         case LINE_TREE_DIR:
4737                 /* Depending on whether it is a subdirectory or parent link
4738                  * mangle the path buffer. */
4739                 if (line == &view->line[1] && *opt_path) {
4740                         pop_tree_stack_entry();
4742                 } else {
4743                         const char *basename = tree_path(line);
4745                         push_tree_stack_entry(basename, view->lineno);
4746                 }
4748                 /* Trees and subtrees share the same ID, so they are not not
4749                  * unique like blobs. */
4750                 flags = OPEN_RELOAD;
4751                 request = REQ_VIEW_TREE;
4752                 break;
4754         case LINE_TREE_FILE:
4755                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4756                 request = REQ_VIEW_BLOB;
4757                 break;
4759         default:
4760                 return REQ_NONE;
4761         }
4763         open_view(view, request, flags);
4764         if (request == REQ_VIEW_TREE)
4765                 view->lineno = tree_lineno;
4767         return REQ_NONE;
4770 static bool
4771 tree_grep(struct view *view, struct line *line)
4773         struct tree_entry *entry = line->data;
4774         const char *text[] = {
4775                 entry->name,
4776                 opt_author ? entry->author : "",
4777                 mkdate(&entry->time, opt_date),
4778                 NULL
4779         };
4781         return grep_text(view, text);
4784 static void
4785 tree_select(struct view *view, struct line *line)
4787         struct tree_entry *entry = line->data;
4789         if (line->type == LINE_TREE_FILE) {
4790                 string_copy_rev(ref_blob, entry->id);
4791                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4793         } else if (line->type != LINE_TREE_DIR) {
4794                 return;
4795         }
4797         string_copy_rev(view->ref, entry->id);
4800 static bool
4801 tree_prepare(struct view *view)
4803         if (view->lines == 0 && opt_prefix[0]) {
4804                 char *pos = opt_prefix;
4806                 while (pos && *pos) {
4807                         char *end = strchr(pos, '/');
4809                         if (end)
4810                                 *end = 0;
4811                         push_tree_stack_entry(pos, 0);
4812                         pos = end;
4813                         if (end) {
4814                                 *end = '/';
4815                                 pos++;
4816                         }
4817                 }
4819         } else if (strcmp(view->vid, view->id)) {
4820                 opt_path[0] = 0;
4821         }
4823         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4826 static const char *tree_argv[SIZEOF_ARG] = {
4827         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4828 };
4830 static struct view_ops tree_ops = {
4831         "file",
4832         tree_argv,
4833         NULL,
4834         tree_read,
4835         tree_draw,
4836         tree_request,
4837         tree_grep,
4838         tree_select,
4839         tree_prepare,
4840 };
4842 static bool
4843 blob_read(struct view *view, char *line)
4845         if (!line)
4846                 return TRUE;
4847         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4850 static enum request
4851 blob_request(struct view *view, enum request request, struct line *line)
4853         switch (request) {
4854         case REQ_EDIT:
4855                 open_blob_editor(view->vid);
4856                 return REQ_NONE;
4857         default:
4858                 return pager_request(view, request, line);
4859         }
4862 static const char *blob_argv[SIZEOF_ARG] = {
4863         "git", "cat-file", "blob", "%(blob)", NULL
4864 };
4866 static struct view_ops blob_ops = {
4867         "line",
4868         blob_argv,
4869         NULL,
4870         blob_read,
4871         pager_draw,
4872         blob_request,
4873         pager_grep,
4874         pager_select,
4875 };
4877 /*
4878  * Blame backend
4879  *
4880  * Loading the blame view is a two phase job:
4881  *
4882  *  1. File content is read either using opt_file from the
4883  *     filesystem or using git-cat-file.
4884  *  2. Then blame information is incrementally added by
4885  *     reading output from git-blame.
4886  */
4888 struct blame_commit {
4889         char id[SIZEOF_REV];            /* SHA1 ID. */
4890         char title[128];                /* First line of the commit message. */
4891         const char *author;             /* Author of the commit. */
4892         struct time time;               /* Date from the author ident. */
4893         char filename[128];             /* Name of file. */
4894         bool has_previous;              /* Was a "previous" line detected. */
4895 };
4897 struct blame {
4898         struct blame_commit *commit;
4899         unsigned long lineno;
4900         char text[1];
4901 };
4903 static bool
4904 blame_open(struct view *view)
4906         char path[SIZEOF_STR];
4908         if (!view->prev && *opt_prefix) {
4909                 string_copy(path, opt_file);
4910                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4911                         return FALSE;
4912         }
4914         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4915                 const char *blame_cat_file_argv[] = {
4916                         "git", "cat-file", "blob", path, NULL
4917                 };
4919                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4920                     !io_run_rd(&view->io, blame_cat_file_argv, opt_cdup))
4921                         return FALSE;
4922         }
4924         setup_update(view, opt_file);
4925         string_format(view->ref, "%s ...", opt_file);
4927         return TRUE;
4930 static struct blame_commit *
4931 get_blame_commit(struct view *view, const char *id)
4933         size_t i;
4935         for (i = 0; i < view->lines; i++) {
4936                 struct blame *blame = view->line[i].data;
4938                 if (!blame->commit)
4939                         continue;
4941                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4942                         return blame->commit;
4943         }
4945         {
4946                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4948                 if (commit)
4949                         string_ncopy(commit->id, id, SIZEOF_REV);
4950                 return commit;
4951         }
4954 static bool
4955 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4957         const char *pos = *posref;
4959         *posref = NULL;
4960         pos = strchr(pos + 1, ' ');
4961         if (!pos || !isdigit(pos[1]))
4962                 return FALSE;
4963         *number = atoi(pos + 1);
4964         if (*number < min || *number > max)
4965                 return FALSE;
4967         *posref = pos;
4968         return TRUE;
4971 static struct blame_commit *
4972 parse_blame_commit(struct view *view, const char *text, int *blamed)
4974         struct blame_commit *commit;
4975         struct blame *blame;
4976         const char *pos = text + SIZEOF_REV - 2;
4977         size_t orig_lineno = 0;
4978         size_t lineno;
4979         size_t group;
4981         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4982                 return NULL;
4984         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4985             !parse_number(&pos, &lineno, 1, view->lines) ||
4986             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4987                 return NULL;
4989         commit = get_blame_commit(view, text);
4990         if (!commit)
4991                 return NULL;
4993         *blamed += group;
4994         while (group--) {
4995                 struct line *line = &view->line[lineno + group - 1];
4997                 blame = line->data;
4998                 blame->commit = commit;
4999                 blame->lineno = orig_lineno + group - 1;
5000                 line->dirty = 1;
5001         }
5003         return commit;
5006 static bool
5007 blame_read_file(struct view *view, const char *line, bool *read_file)
5009         if (!line) {
5010                 const char *blame_argv[] = {
5011                         "git", "blame", "--incremental",
5012                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5013                 };
5014                 struct io io = {};
5016                 if (view->lines == 0 && !view->prev)
5017                         die("No blame exist for %s", view->vid);
5019                 if (view->lines == 0 || !io_run_rd(&io, blame_argv, opt_cdup)) {
5020                         report("Failed to load blame data");
5021                         return TRUE;
5022                 }
5024                 io_done(view->pipe);
5025                 view->io = io;
5026                 *read_file = FALSE;
5027                 return FALSE;
5029         } else {
5030                 size_t linelen = strlen(line);
5031                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5033                 if (!blame)
5034                         return FALSE;
5036                 blame->commit = NULL;
5037                 strncpy(blame->text, line, linelen);
5038                 blame->text[linelen] = 0;
5039                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5040         }
5043 static bool
5044 match_blame_header(const char *name, char **line)
5046         size_t namelen = strlen(name);
5047         bool matched = !strncmp(name, *line, namelen);
5049         if (matched)
5050                 *line += namelen;
5052         return matched;
5055 static bool
5056 blame_read(struct view *view, char *line)
5058         static struct blame_commit *commit = NULL;
5059         static int blamed = 0;
5060         static bool read_file = TRUE;
5062         if (read_file)
5063                 return blame_read_file(view, line, &read_file);
5065         if (!line) {
5066                 /* Reset all! */
5067                 commit = NULL;
5068                 blamed = 0;
5069                 read_file = TRUE;
5070                 string_format(view->ref, "%s", view->vid);
5071                 if (view_is_displayed(view)) {
5072                         update_view_title(view);
5073                         redraw_view_from(view, 0);
5074                 }
5075                 return TRUE;
5076         }
5078         if (!commit) {
5079                 commit = parse_blame_commit(view, line, &blamed);
5080                 string_format(view->ref, "%s %2d%%", view->vid,
5081                               view->lines ? blamed * 100 / view->lines : 0);
5083         } else if (match_blame_header("author ", &line)) {
5084                 commit->author = get_author(line);
5086         } else if (match_blame_header("author-time ", &line)) {
5087                 parse_timesec(&commit->time, line);
5089         } else if (match_blame_header("author-tz ", &line)) {
5090                 parse_timezone(&commit->time, line);
5092         } else if (match_blame_header("summary ", &line)) {
5093                 string_ncopy(commit->title, line, strlen(line));
5095         } else if (match_blame_header("previous ", &line)) {
5096                 commit->has_previous = TRUE;
5098         } else if (match_blame_header("filename ", &line)) {
5099                 string_ncopy(commit->filename, line, strlen(line));
5100                 commit = NULL;
5101         }
5103         return TRUE;
5106 static bool
5107 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5109         struct blame *blame = line->data;
5110         struct time *time = NULL;
5111         const char *id = NULL, *author = NULL;
5112         char text[SIZEOF_STR];
5114         if (blame->commit && *blame->commit->filename) {
5115                 id = blame->commit->id;
5116                 author = blame->commit->author;
5117                 time = &blame->commit->time;
5118         }
5120         if (opt_date && draw_date(view, time))
5121                 return TRUE;
5123         if (opt_author && draw_author(view, author))
5124                 return TRUE;
5126         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5127                 return TRUE;
5129         if (draw_lineno(view, lineno))
5130                 return TRUE;
5132         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5133         draw_text(view, LINE_DEFAULT, text, TRUE);
5134         return TRUE;
5137 static bool
5138 check_blame_commit(struct blame *blame, bool check_null_id)
5140         if (!blame->commit)
5141                 report("Commit data not loaded yet");
5142         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5143                 report("No commit exist for the selected line");
5144         else
5145                 return TRUE;
5146         return FALSE;
5149 static void
5150 setup_blame_parent_line(struct view *view, struct blame *blame)
5152         const char *diff_tree_argv[] = {
5153                 "git", "diff-tree", "-U0", blame->commit->id,
5154                         "--", blame->commit->filename, NULL
5155         };
5156         struct io io = {};
5157         int parent_lineno = -1;
5158         int blamed_lineno = -1;
5159         char *line;
5161         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5162                 return;
5164         while ((line = io_get(&io, '\n', TRUE))) {
5165                 if (*line == '@') {
5166                         char *pos = strchr(line, '+');
5168                         parent_lineno = atoi(line + 4);
5169                         if (pos)
5170                                 blamed_lineno = atoi(pos + 1);
5172                 } else if (*line == '+' && parent_lineno != -1) {
5173                         if (blame->lineno == blamed_lineno - 1 &&
5174                             !strcmp(blame->text, line + 1)) {
5175                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5176                                 break;
5177                         }
5178                         blamed_lineno++;
5179                 }
5180         }
5182         io_done(&io);
5185 static enum request
5186 blame_request(struct view *view, enum request request, struct line *line)
5188         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5189         struct blame *blame = line->data;
5191         switch (request) {
5192         case REQ_VIEW_BLAME:
5193                 if (check_blame_commit(blame, TRUE)) {
5194                         string_copy(opt_ref, blame->commit->id);
5195                         string_copy(opt_file, blame->commit->filename);
5196                         if (blame->lineno)
5197                                 view->lineno = blame->lineno;
5198                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5199                 }
5200                 break;
5202         case REQ_PARENT:
5203                 if (check_blame_commit(blame, TRUE) &&
5204                     select_commit_parent(blame->commit->id, opt_ref,
5205                                          blame->commit->filename)) {
5206                         string_copy(opt_file, blame->commit->filename);
5207                         setup_blame_parent_line(view, blame);
5208                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5209                 }
5210                 break;
5212         case REQ_ENTER:
5213                 if (!check_blame_commit(blame, FALSE))
5214                         break;
5216                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5217                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5218                         break;
5220                 if (!strcmp(blame->commit->id, NULL_ID)) {
5221                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5222                         const char *diff_index_argv[] = {
5223                                 "git", "diff-index", "--root", "--patch-with-stat",
5224                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5225                         };
5227                         if (!blame->commit->has_previous) {
5228                                 diff_index_argv[1] = "diff";
5229                                 diff_index_argv[2] = "--no-color";
5230                                 diff_index_argv[6] = "--";
5231                                 diff_index_argv[7] = "/dev/null";
5232                         }
5234                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5235                                 report("Failed to allocate diff command");
5236                                 break;
5237                         }
5238                         flags |= OPEN_PREPARED;
5239                 }
5241                 open_view(view, REQ_VIEW_DIFF, flags);
5242                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5243                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5244                 break;
5246         default:
5247                 return request;
5248         }
5250         return REQ_NONE;
5253 static bool
5254 blame_grep(struct view *view, struct line *line)
5256         struct blame *blame = line->data;
5257         struct blame_commit *commit = blame->commit;
5258         const char *text[] = {
5259                 blame->text,
5260                 commit ? commit->title : "",
5261                 commit ? commit->id : "",
5262                 commit && opt_author ? commit->author : "",
5263                 commit ? mkdate(&commit->time, opt_date) : "",
5264                 NULL
5265         };
5267         return grep_text(view, text);
5270 static void
5271 blame_select(struct view *view, struct line *line)
5273         struct blame *blame = line->data;
5274         struct blame_commit *commit = blame->commit;
5276         if (!commit)
5277                 return;
5279         if (!strcmp(commit->id, NULL_ID))
5280                 string_ncopy(ref_commit, "HEAD", 4);
5281         else
5282                 string_copy_rev(ref_commit, commit->id);
5285 static struct view_ops blame_ops = {
5286         "line",
5287         NULL,
5288         blame_open,
5289         blame_read,
5290         blame_draw,
5291         blame_request,
5292         blame_grep,
5293         blame_select,
5294 };
5296 /*
5297  * Branch backend
5298  */
5300 struct branch {
5301         const char *author;             /* Author of the last commit. */
5302         struct time time;               /* Date of the last activity. */
5303         const struct ref *ref;          /* Name and commit ID information. */
5304 };
5306 static const struct ref branch_all;
5308 static const enum sort_field branch_sort_fields[] = {
5309         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5310 };
5311 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5313 static int
5314 branch_compare(const void *l1, const void *l2)
5316         const struct branch *branch1 = ((const struct line *) l1)->data;
5317         const struct branch *branch2 = ((const struct line *) l2)->data;
5319         switch (get_sort_field(branch_sort_state)) {
5320         case ORDERBY_DATE:
5321                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5323         case ORDERBY_AUTHOR:
5324                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5326         case ORDERBY_NAME:
5327         default:
5328                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5329         }
5332 static bool
5333 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5335         struct branch *branch = line->data;
5336         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5338         if (opt_date && draw_date(view, &branch->time))
5339                 return TRUE;
5341         if (opt_author && draw_author(view, branch->author))
5342                 return TRUE;
5344         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5345         return TRUE;
5348 static enum request
5349 branch_request(struct view *view, enum request request, struct line *line)
5351         struct branch *branch = line->data;
5353         switch (request) {
5354         case REQ_REFRESH:
5355                 load_refs();
5356                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5357                 return REQ_NONE;
5359         case REQ_TOGGLE_SORT_FIELD:
5360         case REQ_TOGGLE_SORT_ORDER:
5361                 sort_view(view, request, &branch_sort_state, branch_compare);
5362                 return REQ_NONE;
5364         case REQ_ENTER:
5365                 if (branch->ref == &branch_all) {
5366                         const char *all_branches_argv[] = {
5367                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5368                                       "--topo-order", "--all", NULL
5369                         };
5370                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5372                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5373                                 report("Failed to load view of all branches");
5374                                 return REQ_NONE;
5375                         }
5376                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5377                 } else {
5378                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5379                 }
5380                 return REQ_NONE;
5382         default:
5383                 return request;
5384         }
5387 static bool
5388 branch_read(struct view *view, char *line)
5390         static char id[SIZEOF_REV];
5391         struct branch *reference;
5392         size_t i;
5394         if (!line)
5395                 return TRUE;
5397         switch (get_line_type(line)) {
5398         case LINE_COMMIT:
5399                 string_copy_rev(id, line + STRING_SIZE("commit "));
5400                 return TRUE;
5402         case LINE_AUTHOR:
5403                 for (i = 0, reference = NULL; i < view->lines; i++) {
5404                         struct branch *branch = view->line[i].data;
5406                         if (strcmp(branch->ref->id, id))
5407                                 continue;
5409                         view->line[i].dirty = TRUE;
5410                         if (reference) {
5411                                 branch->author = reference->author;
5412                                 branch->time = reference->time;
5413                                 continue;
5414                         }
5416                         parse_author_line(line + STRING_SIZE("author "),
5417                                           &branch->author, &branch->time);
5418                         reference = branch;
5419                 }
5420                 return TRUE;
5422         default:
5423                 return TRUE;
5424         }
5428 static bool
5429 branch_open_visitor(void *data, const struct ref *ref)
5431         struct view *view = data;
5432         struct branch *branch;
5434         if (ref->tag || ref->ltag || ref->remote)
5435                 return TRUE;
5437         branch = calloc(1, sizeof(*branch));
5438         if (!branch)
5439                 return FALSE;
5441         branch->ref = ref;
5442         return !!add_line_data(view, branch, LINE_DEFAULT);
5445 static bool
5446 branch_open(struct view *view)
5448         const char *branch_log[] = {
5449                 "git", "log", "--no-color", "--pretty=raw",
5450                         "--simplify-by-decoration", "--all", NULL
5451         };
5453         if (!io_run_rd(&view->io, branch_log, NULL)) {
5454                 report("Failed to load branch data");
5455                 return TRUE;
5456         }
5458         setup_update(view, view->id);
5459         branch_open_visitor(view, &branch_all);
5460         foreach_ref(branch_open_visitor, view);
5461         view->p_restore = TRUE;
5463         return TRUE;
5466 static bool
5467 branch_grep(struct view *view, struct line *line)
5469         struct branch *branch = line->data;
5470         const char *text[] = {
5471                 branch->ref->name,
5472                 branch->author,
5473                 NULL
5474         };
5476         return grep_text(view, text);
5479 static void
5480 branch_select(struct view *view, struct line *line)
5482         struct branch *branch = line->data;
5484         string_copy_rev(view->ref, branch->ref->id);
5485         string_copy_rev(ref_commit, branch->ref->id);
5486         string_copy_rev(ref_head, branch->ref->id);
5487         string_copy_rev(ref_branch, branch->ref->name);
5490 static struct view_ops branch_ops = {
5491         "branch",
5492         NULL,
5493         branch_open,
5494         branch_read,
5495         branch_draw,
5496         branch_request,
5497         branch_grep,
5498         branch_select,
5499 };
5501 /*
5502  * Status backend
5503  */
5505 struct status {
5506         char status;
5507         struct {
5508                 mode_t mode;
5509                 char rev[SIZEOF_REV];
5510                 char name[SIZEOF_STR];
5511         } old;
5512         struct {
5513                 mode_t mode;
5514                 char rev[SIZEOF_REV];
5515                 char name[SIZEOF_STR];
5516         } new;
5517 };
5519 static char status_onbranch[SIZEOF_STR];
5520 static struct status stage_status;
5521 static enum line_type stage_line_type;
5522 static size_t stage_chunks;
5523 static int *stage_chunk;
5525 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5527 /* This should work even for the "On branch" line. */
5528 static inline bool
5529 status_has_none(struct view *view, struct line *line)
5531         return line < view->line + view->lines && !line[1].data;
5534 /* Get fields from the diff line:
5535  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5536  */
5537 static inline bool
5538 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5540         const char *old_mode = buf +  1;
5541         const char *new_mode = buf +  8;
5542         const char *old_rev  = buf + 15;
5543         const char *new_rev  = buf + 56;
5544         const char *status   = buf + 97;
5546         if (bufsize < 98 ||
5547             old_mode[-1] != ':' ||
5548             new_mode[-1] != ' ' ||
5549             old_rev[-1]  != ' ' ||
5550             new_rev[-1]  != ' ' ||
5551             status[-1]   != ' ')
5552                 return FALSE;
5554         file->status = *status;
5556         string_copy_rev(file->old.rev, old_rev);
5557         string_copy_rev(file->new.rev, new_rev);
5559         file->old.mode = strtoul(old_mode, NULL, 8);
5560         file->new.mode = strtoul(new_mode, NULL, 8);
5562         file->old.name[0] = file->new.name[0] = 0;
5564         return TRUE;
5567 static bool
5568 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5570         struct status *unmerged = NULL;
5571         char *buf;
5572         struct io io = {};
5574         if (!io_run(&io, argv, opt_cdup, IO_RD))
5575                 return FALSE;
5577         add_line_data(view, NULL, type);
5579         while ((buf = io_get(&io, 0, TRUE))) {
5580                 struct status *file = unmerged;
5582                 if (!file) {
5583                         file = calloc(1, sizeof(*file));
5584                         if (!file || !add_line_data(view, file, type))
5585                                 goto error_out;
5586                 }
5588                 /* Parse diff info part. */
5589                 if (status) {
5590                         file->status = status;
5591                         if (status == 'A')
5592                                 string_copy(file->old.rev, NULL_ID);
5594                 } else if (!file->status || file == unmerged) {
5595                         if (!status_get_diff(file, buf, strlen(buf)))
5596                                 goto error_out;
5598                         buf = io_get(&io, 0, TRUE);
5599                         if (!buf)
5600                                 break;
5602                         /* Collapse all modified entries that follow an
5603                          * associated unmerged entry. */
5604                         if (unmerged == file) {
5605                                 unmerged->status = 'U';
5606                                 unmerged = NULL;
5607                         } else if (file->status == 'U') {
5608                                 unmerged = file;
5609                         }
5610                 }
5612                 /* Grab the old name for rename/copy. */
5613                 if (!*file->old.name &&
5614                     (file->status == 'R' || file->status == 'C')) {
5615                         string_ncopy(file->old.name, buf, strlen(buf));
5617                         buf = io_get(&io, 0, TRUE);
5618                         if (!buf)
5619                                 break;
5620                 }
5622                 /* git-ls-files just delivers a NUL separated list of
5623                  * file names similar to the second half of the
5624                  * git-diff-* output. */
5625                 string_ncopy(file->new.name, buf, strlen(buf));
5626                 if (!*file->old.name)
5627                         string_copy(file->old.name, file->new.name);
5628                 file = NULL;
5629         }
5631         if (io_error(&io)) {
5632 error_out:
5633                 io_done(&io);
5634                 return FALSE;
5635         }
5637         if (!view->line[view->lines - 1].data)
5638                 add_line_data(view, NULL, LINE_STAT_NONE);
5640         io_done(&io);
5641         return TRUE;
5644 /* Don't show unmerged entries in the staged section. */
5645 static const char *status_diff_index_argv[] = {
5646         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5647                              "--cached", "-M", "HEAD", NULL
5648 };
5650 static const char *status_diff_files_argv[] = {
5651         "git", "diff-files", "-z", NULL
5652 };
5654 static const char *status_list_other_argv[] = {
5655         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5656 };
5658 static const char *status_list_no_head_argv[] = {
5659         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5660 };
5662 static const char *update_index_argv[] = {
5663         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5664 };
5666 /* Restore the previous line number to stay in the context or select a
5667  * line with something that can be updated. */
5668 static void
5669 status_restore(struct view *view)
5671         if (view->p_lineno >= view->lines)
5672                 view->p_lineno = view->lines - 1;
5673         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5674                 view->p_lineno++;
5675         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5676                 view->p_lineno--;
5678         /* If the above fails, always skip the "On branch" line. */
5679         if (view->p_lineno < view->lines)
5680                 view->lineno = view->p_lineno;
5681         else
5682                 view->lineno = 1;
5684         if (view->lineno < view->offset)
5685                 view->offset = view->lineno;
5686         else if (view->offset + view->height <= view->lineno)
5687                 view->offset = view->lineno - view->height + 1;
5689         view->p_restore = FALSE;
5692 static void
5693 status_update_onbranch(void)
5695         static const char *paths[][2] = {
5696                 { "rebase-apply/rebasing",      "Rebasing" },
5697                 { "rebase-apply/applying",      "Applying mailbox" },
5698                 { "rebase-apply/",              "Rebasing mailbox" },
5699                 { "rebase-merge/interactive",   "Interactive rebase" },
5700                 { "rebase-merge/",              "Rebase merge" },
5701                 { "MERGE_HEAD",                 "Merging" },
5702                 { "BISECT_LOG",                 "Bisecting" },
5703                 { "HEAD",                       "On branch" },
5704         };
5705         char buf[SIZEOF_STR];
5706         struct stat stat;
5707         int i;
5709         if (is_initial_commit()) {
5710                 string_copy(status_onbranch, "Initial commit");
5711                 return;
5712         }
5714         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5715                 char *head = opt_head;
5717                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5718                     lstat(buf, &stat) < 0)
5719                         continue;
5721                 if (!*opt_head) {
5722                         struct io io = {};
5724                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5725                             io_read_buf(&io, buf, sizeof(buf))) {
5726                                 head = buf;
5727                                 if (!prefixcmp(head, "refs/heads/"))
5728                                         head += STRING_SIZE("refs/heads/");
5729                         }
5730                 }
5732                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5733                         string_copy(status_onbranch, opt_head);
5734                 return;
5735         }
5737         string_copy(status_onbranch, "Not currently on any branch");
5740 /* First parse staged info using git-diff-index(1), then parse unstaged
5741  * info using git-diff-files(1), and finally untracked files using
5742  * git-ls-files(1). */
5743 static bool
5744 status_open(struct view *view)
5746         reset_view(view);
5748         add_line_data(view, NULL, LINE_STAT_HEAD);
5749         status_update_onbranch();
5751         io_run_bg(update_index_argv);
5753         if (is_initial_commit()) {
5754                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5755                         return FALSE;
5756         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5757                 return FALSE;
5758         }
5760         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5761             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5762                 return FALSE;
5764         /* Restore the exact position or use the specialized restore
5765          * mode? */
5766         if (!view->p_restore)
5767                 status_restore(view);
5768         return TRUE;
5771 static bool
5772 status_draw(struct view *view, struct line *line, unsigned int lineno)
5774         struct status *status = line->data;
5775         enum line_type type;
5776         const char *text;
5778         if (!status) {
5779                 switch (line->type) {
5780                 case LINE_STAT_STAGED:
5781                         type = LINE_STAT_SECTION;
5782                         text = "Changes to be committed:";
5783                         break;
5785                 case LINE_STAT_UNSTAGED:
5786                         type = LINE_STAT_SECTION;
5787                         text = "Changed but not updated:";
5788                         break;
5790                 case LINE_STAT_UNTRACKED:
5791                         type = LINE_STAT_SECTION;
5792                         text = "Untracked files:";
5793                         break;
5795                 case LINE_STAT_NONE:
5796                         type = LINE_DEFAULT;
5797                         text = "  (no files)";
5798                         break;
5800                 case LINE_STAT_HEAD:
5801                         type = LINE_STAT_HEAD;
5802                         text = status_onbranch;
5803                         break;
5805                 default:
5806                         return FALSE;
5807                 }
5808         } else {
5809                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5811                 buf[0] = status->status;
5812                 if (draw_text(view, line->type, buf, TRUE))
5813                         return TRUE;
5814                 type = LINE_DEFAULT;
5815                 text = status->new.name;
5816         }
5818         draw_text(view, type, text, TRUE);
5819         return TRUE;
5822 static enum request
5823 status_load_error(struct view *view, struct view *stage, const char *path)
5825         if (displayed_views() == 2 || display[current_view] != view)
5826                 maximize_view(view);
5827         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5828         return REQ_NONE;
5831 static enum request
5832 status_enter(struct view *view, struct line *line)
5834         struct status *status = line->data;
5835         const char *oldpath = status ? status->old.name : NULL;
5836         /* Diffs for unmerged entries are empty when passing the new
5837          * path, so leave it empty. */
5838         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5839         const char *info;
5840         enum open_flags split;
5841         struct view *stage = VIEW(REQ_VIEW_STAGE);
5843         if (line->type == LINE_STAT_NONE ||
5844             (!status && line[1].type == LINE_STAT_NONE)) {
5845                 report("No file to diff");
5846                 return REQ_NONE;
5847         }
5849         switch (line->type) {
5850         case LINE_STAT_STAGED:
5851                 if (is_initial_commit()) {
5852                         const char *no_head_diff_argv[] = {
5853                                 "git", "diff", "--no-color", "--patch-with-stat",
5854                                         "--", "/dev/null", newpath, NULL
5855                         };
5857                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5858                                 return status_load_error(view, stage, newpath);
5859                 } else {
5860                         const char *index_show_argv[] = {
5861                                 "git", "diff-index", "--root", "--patch-with-stat",
5862                                         "-C", "-M", "--cached", "HEAD", "--",
5863                                         oldpath, newpath, NULL
5864                         };
5866                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5867                                 return status_load_error(view, stage, newpath);
5868                 }
5870                 if (status)
5871                         info = "Staged changes to %s";
5872                 else
5873                         info = "Staged changes";
5874                 break;
5876         case LINE_STAT_UNSTAGED:
5877         {
5878                 const char *files_show_argv[] = {
5879                         "git", "diff-files", "--root", "--patch-with-stat",
5880                                 "-C", "-M", "--", oldpath, newpath, NULL
5881                 };
5883                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5884                         return status_load_error(view, stage, newpath);
5885                 if (status)
5886                         info = "Unstaged changes to %s";
5887                 else
5888                         info = "Unstaged changes";
5889                 break;
5890         }
5891         case LINE_STAT_UNTRACKED:
5892                 if (!newpath) {
5893                         report("No file to show");
5894                         return REQ_NONE;
5895                 }
5897                 if (!suffixcmp(status->new.name, -1, "/")) {
5898                         report("Cannot display a directory");
5899                         return REQ_NONE;
5900                 }
5902                 if (!prepare_update_file(stage, newpath))
5903                         return status_load_error(view, stage, newpath);
5904                 info = "Untracked file %s";
5905                 break;
5907         case LINE_STAT_HEAD:
5908                 return REQ_NONE;
5910         default:
5911                 die("line type %d not handled in switch", line->type);
5912         }
5914         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5915         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5916         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5917                 if (status) {
5918                         stage_status = *status;
5919                 } else {
5920                         memset(&stage_status, 0, sizeof(stage_status));
5921                 }
5923                 stage_line_type = line->type;
5924                 stage_chunks = 0;
5925                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5926         }
5928         return REQ_NONE;
5931 static bool
5932 status_exists(struct status *status, enum line_type type)
5934         struct view *view = VIEW(REQ_VIEW_STATUS);
5935         unsigned long lineno;
5937         for (lineno = 0; lineno < view->lines; lineno++) {
5938                 struct line *line = &view->line[lineno];
5939                 struct status *pos = line->data;
5941                 if (line->type != type)
5942                         continue;
5943                 if (!pos && (!status || !status->status) && line[1].data) {
5944                         select_view_line(view, lineno);
5945                         return TRUE;
5946                 }
5947                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5948                         select_view_line(view, lineno);
5949                         return TRUE;
5950                 }
5951         }
5953         return FALSE;
5957 static bool
5958 status_update_prepare(struct io *io, enum line_type type)
5960         const char *staged_argv[] = {
5961                 "git", "update-index", "-z", "--index-info", NULL
5962         };
5963         const char *others_argv[] = {
5964                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5965         };
5967         switch (type) {
5968         case LINE_STAT_STAGED:
5969                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5971         case LINE_STAT_UNSTAGED:
5972         case LINE_STAT_UNTRACKED:
5973                 return io_run(io, others_argv, opt_cdup, IO_WR);
5975         default:
5976                 die("line type %d not handled in switch", type);
5977                 return FALSE;
5978         }
5981 static bool
5982 status_update_write(struct io *io, struct status *status, enum line_type type)
5984         char buf[SIZEOF_STR];
5985         size_t bufsize = 0;
5987         switch (type) {
5988         case LINE_STAT_STAGED:
5989                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5990                                         status->old.mode,
5991                                         status->old.rev,
5992                                         status->old.name, 0))
5993                         return FALSE;
5994                 break;
5996         case LINE_STAT_UNSTAGED:
5997         case LINE_STAT_UNTRACKED:
5998                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5999                         return FALSE;
6000                 break;
6002         default:
6003                 die("line type %d not handled in switch", type);
6004         }
6006         return io_write(io, buf, bufsize);
6009 static bool
6010 status_update_file(struct status *status, enum line_type type)
6012         struct io io = {};
6013         bool result;
6015         if (!status_update_prepare(&io, type))
6016                 return FALSE;
6018         result = status_update_write(&io, status, type);
6019         return io_done(&io) && result;
6022 static bool
6023 status_update_files(struct view *view, struct line *line)
6025         char buf[sizeof(view->ref)];
6026         struct io io = {};
6027         bool result = TRUE;
6028         struct line *pos = view->line + view->lines;
6029         int files = 0;
6030         int file, done;
6031         int cursor_y = -1, cursor_x = -1;
6033         if (!status_update_prepare(&io, line->type))
6034                 return FALSE;
6036         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6037                 files++;
6039         string_copy(buf, view->ref);
6040         getsyx(cursor_y, cursor_x);
6041         for (file = 0, done = 5; result && file < files; line++, file++) {
6042                 int almost_done = file * 100 / files;
6044                 if (almost_done > done) {
6045                         done = almost_done;
6046                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6047                                       file, files, done);
6048                         update_view_title(view);
6049                         setsyx(cursor_y, cursor_x);
6050                         doupdate();
6051                 }
6052                 result = status_update_write(&io, line->data, line->type);
6053         }
6054         string_copy(view->ref, buf);
6056         return io_done(&io) && result;
6059 static bool
6060 status_update(struct view *view)
6062         struct line *line = &view->line[view->lineno];
6064         assert(view->lines);
6066         if (!line->data) {
6067                 /* This should work even for the "On branch" line. */
6068                 if (line < view->line + view->lines && !line[1].data) {
6069                         report("Nothing to update");
6070                         return FALSE;
6071                 }
6073                 if (!status_update_files(view, line + 1)) {
6074                         report("Failed to update file status");
6075                         return FALSE;
6076                 }
6078         } else if (!status_update_file(line->data, line->type)) {
6079                 report("Failed to update file status");
6080                 return FALSE;
6081         }
6083         return TRUE;
6086 static bool
6087 status_revert(struct status *status, enum line_type type, bool has_none)
6089         if (!status || type != LINE_STAT_UNSTAGED) {
6090                 if (type == LINE_STAT_STAGED) {
6091                         report("Cannot revert changes to staged files");
6092                 } else if (type == LINE_STAT_UNTRACKED) {
6093                         report("Cannot revert changes to untracked files");
6094                 } else if (has_none) {
6095                         report("Nothing to revert");
6096                 } else {
6097                         report("Cannot revert changes to multiple files");
6098                 }
6100         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6101                 char mode[10] = "100644";
6102                 const char *reset_argv[] = {
6103                         "git", "update-index", "--cacheinfo", mode,
6104                                 status->old.rev, status->old.name, NULL
6105                 };
6106                 const char *checkout_argv[] = {
6107                         "git", "checkout", "--", status->old.name, NULL
6108                 };
6110                 if (status->status == 'U') {
6111                         string_format(mode, "%5o", status->old.mode);
6113                         if (status->old.mode == 0 && status->new.mode == 0) {
6114                                 reset_argv[2] = "--force-remove";
6115                                 reset_argv[3] = status->old.name;
6116                                 reset_argv[4] = NULL;
6117                         }
6119                         if (!io_run_fg(reset_argv, opt_cdup))
6120                                 return FALSE;
6121                         if (status->old.mode == 0 && status->new.mode == 0)
6122                                 return TRUE;
6123                 }
6125                 return io_run_fg(checkout_argv, opt_cdup);
6126         }
6128         return FALSE;
6131 static enum request
6132 status_request(struct view *view, enum request request, struct line *line)
6134         struct status *status = line->data;
6136         switch (request) {
6137         case REQ_STATUS_UPDATE:
6138                 if (!status_update(view))
6139                         return REQ_NONE;
6140                 break;
6142         case REQ_STATUS_REVERT:
6143                 if (!status_revert(status, line->type, status_has_none(view, line)))
6144                         return REQ_NONE;
6145                 break;
6147         case REQ_STATUS_MERGE:
6148                 if (!status || status->status != 'U') {
6149                         report("Merging only possible for files with unmerged status ('U').");
6150                         return REQ_NONE;
6151                 }
6152                 open_mergetool(status->new.name);
6153                 break;
6155         case REQ_EDIT:
6156                 if (!status)
6157                         return request;
6158                 if (status->status == 'D') {
6159                         report("File has been deleted.");
6160                         return REQ_NONE;
6161                 }
6163                 open_editor(status->new.name);
6164                 break;
6166         case REQ_VIEW_BLAME:
6167                 if (status)
6168                         opt_ref[0] = 0;
6169                 return request;
6171         case REQ_ENTER:
6172                 /* After returning the status view has been split to
6173                  * show the stage view. No further reloading is
6174                  * necessary. */
6175                 return status_enter(view, line);
6177         case REQ_REFRESH:
6178                 /* Simply reload the view. */
6179                 break;
6181         default:
6182                 return request;
6183         }
6185         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6187         return REQ_NONE;
6190 static void
6191 status_select(struct view *view, struct line *line)
6193         struct status *status = line->data;
6194         char file[SIZEOF_STR] = "all files";
6195         const char *text;
6196         const char *key;
6198         if (status && !string_format(file, "'%s'", status->new.name))
6199                 return;
6201         if (!status && line[1].type == LINE_STAT_NONE)
6202                 line++;
6204         switch (line->type) {
6205         case LINE_STAT_STAGED:
6206                 text = "Press %s to unstage %s for commit";
6207                 break;
6209         case LINE_STAT_UNSTAGED:
6210                 text = "Press %s to stage %s for commit";
6211                 break;
6213         case LINE_STAT_UNTRACKED:
6214                 text = "Press %s to stage %s for addition";
6215                 break;
6217         case LINE_STAT_HEAD:
6218         case LINE_STAT_NONE:
6219                 text = "Nothing to update";
6220                 break;
6222         default:
6223                 die("line type %d not handled in switch", line->type);
6224         }
6226         if (status && status->status == 'U') {
6227                 text = "Press %s to resolve conflict in %s";
6228                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6230         } else {
6231                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6232         }
6234         string_format(view->ref, text, key, file);
6235         if (status)
6236                 string_copy(opt_file, status->new.name);
6239 static bool
6240 status_grep(struct view *view, struct line *line)
6242         struct status *status = line->data;
6244         if (status) {
6245                 const char buf[2] = { status->status, 0 };
6246                 const char *text[] = { status->new.name, buf, NULL };
6248                 return grep_text(view, text);
6249         }
6251         return FALSE;
6254 static struct view_ops status_ops = {
6255         "file",
6256         NULL,
6257         status_open,
6258         NULL,
6259         status_draw,
6260         status_request,
6261         status_grep,
6262         status_select,
6263 };
6266 static bool
6267 stage_diff_write(struct io *io, struct line *line, struct line *end)
6269         while (line < end) {
6270                 if (!io_write(io, line->data, strlen(line->data)) ||
6271                     !io_write(io, "\n", 1))
6272                         return FALSE;
6273                 line++;
6274                 if (line->type == LINE_DIFF_CHUNK ||
6275                     line->type == LINE_DIFF_HEADER)
6276                         break;
6277         }
6279         return TRUE;
6282 static struct line *
6283 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6285         for (; view->line < line; line--)
6286                 if (line->type == type)
6287                         return line;
6289         return NULL;
6292 static bool
6293 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6295         const char *apply_argv[SIZEOF_ARG] = {
6296                 "git", "apply", "--whitespace=nowarn", NULL
6297         };
6298         struct line *diff_hdr;
6299         struct io io = {};
6300         int argc = 3;
6302         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6303         if (!diff_hdr)
6304                 return FALSE;
6306         if (!revert)
6307                 apply_argv[argc++] = "--cached";
6308         if (revert || stage_line_type == LINE_STAT_STAGED)
6309                 apply_argv[argc++] = "-R";
6310         apply_argv[argc++] = "-";
6311         apply_argv[argc++] = NULL;
6312         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6313                 return FALSE;
6315         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6316             !stage_diff_write(&io, chunk, view->line + view->lines))
6317                 chunk = NULL;
6319         io_done(&io);
6320         io_run_bg(update_index_argv);
6322         return chunk ? TRUE : FALSE;
6325 static bool
6326 stage_update(struct view *view, struct line *line)
6328         struct line *chunk = NULL;
6330         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6331                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6333         if (chunk) {
6334                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6335                         report("Failed to apply chunk");
6336                         return FALSE;
6337                 }
6339         } else if (!stage_status.status) {
6340                 view = VIEW(REQ_VIEW_STATUS);
6342                 for (line = view->line; line < view->line + view->lines; line++)
6343                         if (line->type == stage_line_type)
6344                                 break;
6346                 if (!status_update_files(view, line + 1)) {
6347                         report("Failed to update files");
6348                         return FALSE;
6349                 }
6351         } else if (!status_update_file(&stage_status, stage_line_type)) {
6352                 report("Failed to update file");
6353                 return FALSE;
6354         }
6356         return TRUE;
6359 static bool
6360 stage_revert(struct view *view, struct line *line)
6362         struct line *chunk = NULL;
6364         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6365                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6367         if (chunk) {
6368                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6369                         return FALSE;
6371                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6372                         report("Failed to revert chunk");
6373                         return FALSE;
6374                 }
6375                 return TRUE;
6377         } else {
6378                 return status_revert(stage_status.status ? &stage_status : NULL,
6379                                      stage_line_type, FALSE);
6380         }
6384 static void
6385 stage_next(struct view *view, struct line *line)
6387         int i;
6389         if (!stage_chunks) {
6390                 for (line = view->line; line < view->line + view->lines; line++) {
6391                         if (line->type != LINE_DIFF_CHUNK)
6392                                 continue;
6394                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6395                                 report("Allocation failure");
6396                                 return;
6397                         }
6399                         stage_chunk[stage_chunks++] = line - view->line;
6400                 }
6401         }
6403         for (i = 0; i < stage_chunks; i++) {
6404                 if (stage_chunk[i] > view->lineno) {
6405                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6406                         report("Chunk %d of %d", i + 1, stage_chunks);
6407                         return;
6408                 }
6409         }
6411         report("No next chunk found");
6414 static enum request
6415 stage_request(struct view *view, enum request request, struct line *line)
6417         switch (request) {
6418         case REQ_STATUS_UPDATE:
6419                 if (!stage_update(view, line))
6420                         return REQ_NONE;
6421                 break;
6423         case REQ_STATUS_REVERT:
6424                 if (!stage_revert(view, line))
6425                         return REQ_NONE;
6426                 break;
6428         case REQ_STAGE_NEXT:
6429                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6430                         report("File is untracked; press %s to add",
6431                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6432                         return REQ_NONE;
6433                 }
6434                 stage_next(view, line);
6435                 return REQ_NONE;
6437         case REQ_EDIT:
6438                 if (!stage_status.new.name[0])
6439                         return request;
6440                 if (stage_status.status == 'D') {
6441                         report("File has been deleted.");
6442                         return REQ_NONE;
6443                 }
6445                 open_editor(stage_status.new.name);
6446                 break;
6448         case REQ_REFRESH:
6449                 /* Reload everything ... */
6450                 break;
6452         case REQ_VIEW_BLAME:
6453                 if (stage_status.new.name[0]) {
6454                         string_copy(opt_file, stage_status.new.name);
6455                         opt_ref[0] = 0;
6456                 }
6457                 return request;
6459         case REQ_ENTER:
6460                 return pager_request(view, request, line);
6462         default:
6463                 return request;
6464         }
6466         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6467         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6469         /* Check whether the staged entry still exists, and close the
6470          * stage view if it doesn't. */
6471         if (!status_exists(&stage_status, stage_line_type)) {
6472                 status_restore(VIEW(REQ_VIEW_STATUS));
6473                 return REQ_VIEW_CLOSE;
6474         }
6476         if (stage_line_type == LINE_STAT_UNTRACKED) {
6477                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6478                         report("Cannot display a directory");
6479                         return REQ_NONE;
6480                 }
6482                 if (!prepare_update_file(view, stage_status.new.name)) {
6483                         report("Failed to open file: %s", strerror(errno));
6484                         return REQ_NONE;
6485                 }
6486         }
6487         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6489         return REQ_NONE;
6492 static struct view_ops stage_ops = {
6493         "line",
6494         NULL,
6495         NULL,
6496         pager_read,
6497         pager_draw,
6498         stage_request,
6499         pager_grep,
6500         pager_select,
6501 };
6504 /*
6505  * Revision graph
6506  */
6508 struct commit {
6509         char id[SIZEOF_REV];            /* SHA1 ID. */
6510         char title[128];                /* First line of the commit message. */
6511         const char *author;             /* Author of the commit. */
6512         struct time time;               /* Date from the author ident. */
6513         struct ref_list *refs;          /* Repository references. */
6514         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6515         size_t graph_size;              /* The width of the graph array. */
6516         bool has_parents;               /* Rewritten --parents seen. */
6517 };
6519 /* Size of rev graph with no  "padding" columns */
6520 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6522 struct rev_graph {
6523         struct rev_graph *prev, *next, *parents;
6524         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6525         size_t size;
6526         struct commit *commit;
6527         size_t pos;
6528         unsigned int boundary:1;
6529 };
6531 /* Parents of the commit being visualized. */
6532 static struct rev_graph graph_parents[4];
6534 /* The current stack of revisions on the graph. */
6535 static struct rev_graph graph_stacks[4] = {
6536         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6537         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6538         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6539         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6540 };
6542 static inline bool
6543 graph_parent_is_merge(struct rev_graph *graph)
6545         return graph->parents->size > 1;
6548 static inline void
6549 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6551         struct commit *commit = graph->commit;
6553         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6554                 commit->graph[commit->graph_size++] = symbol;
6557 static void
6558 clear_rev_graph(struct rev_graph *graph)
6560         graph->boundary = 0;
6561         graph->size = graph->pos = 0;
6562         graph->commit = NULL;
6563         memset(graph->parents, 0, sizeof(*graph->parents));
6566 static void
6567 done_rev_graph(struct rev_graph *graph)
6569         if (graph_parent_is_merge(graph) &&
6570             graph->pos < graph->size - 1 &&
6571             graph->next->size == graph->size + graph->parents->size - 1) {
6572                 size_t i = graph->pos + graph->parents->size - 1;
6574                 graph->commit->graph_size = i * 2;
6575                 while (i < graph->next->size - 1) {
6576                         append_to_rev_graph(graph, ' ');
6577                         append_to_rev_graph(graph, '\\');
6578                         i++;
6579                 }
6580         }
6582         clear_rev_graph(graph);
6585 static void
6586 push_rev_graph(struct rev_graph *graph, const char *parent)
6588         int i;
6590         /* "Collapse" duplicate parents lines.
6591          *
6592          * FIXME: This needs to also update update the drawn graph but
6593          * for now it just serves as a method for pruning graph lines. */
6594         for (i = 0; i < graph->size; i++)
6595                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6596                         return;
6598         if (graph->size < SIZEOF_REVITEMS) {
6599                 string_copy_rev(graph->rev[graph->size++], parent);
6600         }
6603 static chtype
6604 get_rev_graph_symbol(struct rev_graph *graph)
6606         chtype symbol;
6608         if (graph->boundary)
6609                 symbol = REVGRAPH_BOUND;
6610         else if (graph->parents->size == 0)
6611                 symbol = REVGRAPH_INIT;
6612         else if (graph_parent_is_merge(graph))
6613                 symbol = REVGRAPH_MERGE;
6614         else if (graph->pos >= graph->size)
6615                 symbol = REVGRAPH_BRANCH;
6616         else
6617                 symbol = REVGRAPH_COMMIT;
6619         return symbol;
6622 static void
6623 draw_rev_graph(struct rev_graph *graph)
6625         struct rev_filler {
6626                 chtype separator, line;
6627         };
6628         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6629         static struct rev_filler fillers[] = {
6630                 { ' ',  '|' },
6631                 { '`',  '.' },
6632                 { '\'', ' ' },
6633                 { '/',  ' ' },
6634         };
6635         chtype symbol = get_rev_graph_symbol(graph);
6636         struct rev_filler *filler;
6637         size_t i;
6639         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6640         filler = &fillers[DEFAULT];
6642         for (i = 0; i < graph->pos; i++) {
6643                 append_to_rev_graph(graph, filler->line);
6644                 if (graph_parent_is_merge(graph->prev) &&
6645                     graph->prev->pos == i)
6646                         filler = &fillers[RSHARP];
6648                 append_to_rev_graph(graph, filler->separator);
6649         }
6651         /* Place the symbol for this revision. */
6652         append_to_rev_graph(graph, symbol);
6654         if (graph->prev->size > graph->size)
6655                 filler = &fillers[RDIAG];
6656         else
6657                 filler = &fillers[DEFAULT];
6659         i++;
6661         for (; i < graph->size; i++) {
6662                 append_to_rev_graph(graph, filler->separator);
6663                 append_to_rev_graph(graph, filler->line);
6664                 if (graph_parent_is_merge(graph->prev) &&
6665                     i < graph->prev->pos + graph->parents->size)
6666                         filler = &fillers[RSHARP];
6667                 if (graph->prev->size > graph->size)
6668                         filler = &fillers[LDIAG];
6669         }
6671         if (graph->prev->size > graph->size) {
6672                 append_to_rev_graph(graph, filler->separator);
6673                 if (filler->line != ' ')
6674                         append_to_rev_graph(graph, filler->line);
6675         }
6678 /* Prepare the next rev graph */
6679 static void
6680 prepare_rev_graph(struct rev_graph *graph)
6682         size_t i;
6684         /* First, traverse all lines of revisions up to the active one. */
6685         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6686                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6687                         break;
6689                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6690         }
6692         /* Interleave the new revision parent(s). */
6693         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6694                 push_rev_graph(graph->next, graph->parents->rev[i]);
6696         /* Lastly, put any remaining revisions. */
6697         for (i = graph->pos + 1; i < graph->size; i++)
6698                 push_rev_graph(graph->next, graph->rev[i]);
6701 static void
6702 update_rev_graph(struct view *view, struct rev_graph *graph)
6704         /* If this is the finalizing update ... */
6705         if (graph->commit)
6706                 prepare_rev_graph(graph);
6708         /* Graph visualization needs a one rev look-ahead,
6709          * so the first update doesn't visualize anything. */
6710         if (!graph->prev->commit)
6711                 return;
6713         if (view->lines > 2)
6714                 view->line[view->lines - 3].dirty = 1;
6715         if (view->lines > 1)
6716                 view->line[view->lines - 2].dirty = 1;
6717         draw_rev_graph(graph->prev);
6718         done_rev_graph(graph->prev->prev);
6722 /*
6723  * Main view backend
6724  */
6726 static const char *main_argv[SIZEOF_ARG] = {
6727         "git", "log", "--no-color", "--pretty=raw", "--parents",
6728                       "--topo-order", "%(head)", NULL
6729 };
6731 static bool
6732 main_draw(struct view *view, struct line *line, unsigned int lineno)
6734         struct commit *commit = line->data;
6736         if (!commit->author)
6737                 return FALSE;
6739         if (opt_date && draw_date(view, &commit->time))
6740                 return TRUE;
6742         if (opt_author && draw_author(view, commit->author))
6743                 return TRUE;
6745         if (opt_rev_graph && commit->graph_size &&
6746             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6747                 return TRUE;
6749         if (opt_show_refs && commit->refs) {
6750                 size_t i;
6752                 for (i = 0; i < commit->refs->size; i++) {
6753                         struct ref *ref = commit->refs->refs[i];
6754                         enum line_type type;
6756                         if (ref->head)
6757                                 type = LINE_MAIN_HEAD;
6758                         else if (ref->ltag)
6759                                 type = LINE_MAIN_LOCAL_TAG;
6760                         else if (ref->tag)
6761                                 type = LINE_MAIN_TAG;
6762                         else if (ref->tracked)
6763                                 type = LINE_MAIN_TRACKED;
6764                         else if (ref->remote)
6765                                 type = LINE_MAIN_REMOTE;
6766                         else
6767                                 type = LINE_MAIN_REF;
6769                         if (draw_text(view, type, "[", TRUE) ||
6770                             draw_text(view, type, ref->name, TRUE) ||
6771                             draw_text(view, type, "]", TRUE))
6772                                 return TRUE;
6774                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6775                                 return TRUE;
6776                 }
6777         }
6779         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6780         return TRUE;
6783 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6784 static bool
6785 main_read(struct view *view, char *line)
6787         static struct rev_graph *graph = graph_stacks;
6788         enum line_type type;
6789         struct commit *commit;
6791         if (!line) {
6792                 int i;
6794                 if (!view->lines && !view->prev)
6795                         die("No revisions match the given arguments.");
6796                 if (view->lines > 0) {
6797                         commit = view->line[view->lines - 1].data;
6798                         view->line[view->lines - 1].dirty = 1;
6799                         if (!commit->author) {
6800                                 view->lines--;
6801                                 free(commit);
6802                                 graph->commit = NULL;
6803                         }
6804                 }
6805                 update_rev_graph(view, graph);
6807                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6808                         clear_rev_graph(&graph_stacks[i]);
6809                 return TRUE;
6810         }
6812         type = get_line_type(line);
6813         if (type == LINE_COMMIT) {
6814                 commit = calloc(1, sizeof(struct commit));
6815                 if (!commit)
6816                         return FALSE;
6818                 line += STRING_SIZE("commit ");
6819                 if (*line == '-') {
6820                         graph->boundary = 1;
6821                         line++;
6822                 }
6824                 string_copy_rev(commit->id, line);
6825                 commit->refs = get_ref_list(commit->id);
6826                 graph->commit = commit;
6827                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6829                 while ((line = strchr(line, ' '))) {
6830                         line++;
6831                         push_rev_graph(graph->parents, line);
6832                         commit->has_parents = TRUE;
6833                 }
6834                 return TRUE;
6835         }
6837         if (!view->lines)
6838                 return TRUE;
6839         commit = view->line[view->lines - 1].data;
6841         switch (type) {
6842         case LINE_PARENT:
6843                 if (commit->has_parents)
6844                         break;
6845                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6846                 break;
6848         case LINE_AUTHOR:
6849                 parse_author_line(line + STRING_SIZE("author "),
6850                                   &commit->author, &commit->time);
6851                 update_rev_graph(view, graph);
6852                 graph = graph->next;
6853                 break;
6855         default:
6856                 /* Fill in the commit title if it has not already been set. */
6857                 if (commit->title[0])
6858                         break;
6860                 /* Require titles to start with a non-space character at the
6861                  * offset used by git log. */
6862                 if (strncmp(line, "    ", 4))
6863                         break;
6864                 line += 4;
6865                 /* Well, if the title starts with a whitespace character,
6866                  * try to be forgiving.  Otherwise we end up with no title. */
6867                 while (isspace(*line))
6868                         line++;
6869                 if (*line == '\0')
6870                         break;
6871                 /* FIXME: More graceful handling of titles; append "..." to
6872                  * shortened titles, etc. */
6874                 string_expand(commit->title, sizeof(commit->title), line, 1);
6875                 view->line[view->lines - 1].dirty = 1;
6876         }
6878         return TRUE;
6881 static enum request
6882 main_request(struct view *view, enum request request, struct line *line)
6884         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6886         switch (request) {
6887         case REQ_ENTER:
6888                 open_view(view, REQ_VIEW_DIFF, flags);
6889                 break;
6890         case REQ_REFRESH:
6891                 load_refs();
6892                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6893                 break;
6894         default:
6895                 return request;
6896         }
6898         return REQ_NONE;
6901 static bool
6902 grep_refs(struct ref_list *list, regex_t *regex)
6904         regmatch_t pmatch;
6905         size_t i;
6907         if (!opt_show_refs || !list)
6908                 return FALSE;
6910         for (i = 0; i < list->size; i++) {
6911                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6912                         return TRUE;
6913         }
6915         return FALSE;
6918 static bool
6919 main_grep(struct view *view, struct line *line)
6921         struct commit *commit = line->data;
6922         const char *text[] = {
6923                 commit->title,
6924                 opt_author ? commit->author : "",
6925                 mkdate(&commit->time, opt_date),
6926                 NULL
6927         };
6929         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6932 static void
6933 main_select(struct view *view, struct line *line)
6935         struct commit *commit = line->data;
6937         string_copy_rev(view->ref, commit->id);
6938         string_copy_rev(ref_commit, view->ref);
6941 static struct view_ops main_ops = {
6942         "commit",
6943         main_argv,
6944         NULL,
6945         main_read,
6946         main_draw,
6947         main_request,
6948         main_grep,
6949         main_select,
6950 };
6953 /*
6954  * Status management
6955  */
6957 /* Whether or not the curses interface has been initialized. */
6958 static bool cursed = FALSE;
6960 /* Terminal hacks and workarounds. */
6961 static bool use_scroll_redrawwin;
6962 static bool use_scroll_status_wclear;
6964 /* The status window is used for polling keystrokes. */
6965 static WINDOW *status_win;
6967 /* Reading from the prompt? */
6968 static bool input_mode = FALSE;
6970 static bool status_empty = FALSE;
6972 /* Update status and title window. */
6973 static void
6974 report(const char *msg, ...)
6976         struct view *view = display[current_view];
6978         if (input_mode)
6979                 return;
6981         if (!view) {
6982                 char buf[SIZEOF_STR];
6983                 va_list args;
6985                 va_start(args, msg);
6986                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6987                         buf[sizeof(buf) - 1] = 0;
6988                         buf[sizeof(buf) - 2] = '.';
6989                         buf[sizeof(buf) - 3] = '.';
6990                         buf[sizeof(buf) - 4] = '.';
6991                 }
6992                 va_end(args);
6993                 die("%s", buf);
6994         }
6996         if (!status_empty || *msg) {
6997                 va_list args;
6999                 va_start(args, msg);
7001                 wmove(status_win, 0, 0);
7002                 if (view->has_scrolled && use_scroll_status_wclear)
7003                         wclear(status_win);
7004                 if (*msg) {
7005                         vwprintw(status_win, msg, args);
7006                         status_empty = FALSE;
7007                 } else {
7008                         status_empty = TRUE;
7009                 }
7010                 wclrtoeol(status_win);
7011                 wnoutrefresh(status_win);
7013                 va_end(args);
7014         }
7016         update_view_title(view);
7019 static void
7020 init_display(void)
7022         const char *term;
7023         int x, y;
7025         /* Initialize the curses library */
7026         if (isatty(STDIN_FILENO)) {
7027                 cursed = !!initscr();
7028                 opt_tty = stdin;
7029         } else {
7030                 /* Leave stdin and stdout alone when acting as a pager. */
7031                 opt_tty = fopen("/dev/tty", "r+");
7032                 if (!opt_tty)
7033                         die("Failed to open /dev/tty");
7034                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7035         }
7037         if (!cursed)
7038                 die("Failed to initialize curses");
7040         nonl();         /* Disable conversion and detect newlines from input. */
7041         cbreak();       /* Take input chars one at a time, no wait for \n */
7042         noecho();       /* Don't echo input */
7043         leaveok(stdscr, FALSE);
7045         if (has_colors())
7046                 init_colors();
7048         getmaxyx(stdscr, y, x);
7049         status_win = newwin(1, 0, y - 1, 0);
7050         if (!status_win)
7051                 die("Failed to create status window");
7053         /* Enable keyboard mapping */
7054         keypad(status_win, TRUE);
7055         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7057         TABSIZE = opt_tab_size;
7059         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7060         if (term && !strcmp(term, "gnome-terminal")) {
7061                 /* In the gnome-terminal-emulator, the message from
7062                  * scrolling up one line when impossible followed by
7063                  * scrolling down one line causes corruption of the
7064                  * status line. This is fixed by calling wclear. */
7065                 use_scroll_status_wclear = TRUE;
7066                 use_scroll_redrawwin = FALSE;
7068         } else if (term && !strcmp(term, "xrvt-xpm")) {
7069                 /* No problems with full optimizations in xrvt-(unicode)
7070                  * and aterm. */
7071                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7073         } else {
7074                 /* When scrolling in (u)xterm the last line in the
7075                  * scrolling direction will update slowly. */
7076                 use_scroll_redrawwin = TRUE;
7077                 use_scroll_status_wclear = FALSE;
7078         }
7081 static int
7082 get_input(int prompt_position)
7084         struct view *view;
7085         int i, key, cursor_y, cursor_x;
7086         bool loading = FALSE;
7088         if (prompt_position)
7089                 input_mode = TRUE;
7091         while (TRUE) {
7092                 foreach_view (view, i) {
7093                         update_view(view);
7094                         if (view_is_displayed(view) && view->has_scrolled &&
7095                             use_scroll_redrawwin)
7096                                 redrawwin(view->win);
7097                         view->has_scrolled = FALSE;
7098                         if (view->pipe)
7099                                 loading = TRUE;
7100                 }
7102                 /* Update the cursor position. */
7103                 if (prompt_position) {
7104                         getbegyx(status_win, cursor_y, cursor_x);
7105                         cursor_x = prompt_position;
7106                 } else {
7107                         view = display[current_view];
7108                         getbegyx(view->win, cursor_y, cursor_x);
7109                         cursor_x = view->width - 1;
7110                         cursor_y += view->lineno - view->offset;
7111                 }
7112                 setsyx(cursor_y, cursor_x);
7114                 /* Refresh, accept single keystroke of input */
7115                 doupdate();
7116                 nodelay(status_win, loading);
7117                 key = wgetch(status_win);
7119                 /* wgetch() with nodelay() enabled returns ERR when
7120                  * there's no input. */
7121                 if (key == ERR) {
7123                 } else if (key == KEY_RESIZE) {
7124                         int height, width;
7126                         getmaxyx(stdscr, height, width);
7128                         wresize(status_win, 1, width);
7129                         mvwin(status_win, height - 1, 0);
7130                         wnoutrefresh(status_win);
7131                         resize_display();
7132                         redraw_display(TRUE);
7134                 } else {
7135                         input_mode = FALSE;
7136                         return key;
7137                 }
7138         }
7141 static char *
7142 prompt_input(const char *prompt, input_handler handler, void *data)
7144         enum input_status status = INPUT_OK;
7145         static char buf[SIZEOF_STR];
7146         size_t pos = 0;
7148         buf[pos] = 0;
7150         while (status == INPUT_OK || status == INPUT_SKIP) {
7151                 int key;
7153                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7154                 wclrtoeol(status_win);
7156                 key = get_input(pos + 1);
7157                 switch (key) {
7158                 case KEY_RETURN:
7159                 case KEY_ENTER:
7160                 case '\n':
7161                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7162                         break;
7164                 case KEY_BACKSPACE:
7165                         if (pos > 0)
7166                                 buf[--pos] = 0;
7167                         else
7168                                 status = INPUT_CANCEL;
7169                         break;
7171                 case KEY_ESC:
7172                         status = INPUT_CANCEL;
7173                         break;
7175                 default:
7176                         if (pos >= sizeof(buf)) {
7177                                 report("Input string too long");
7178                                 return NULL;
7179                         }
7181                         status = handler(data, buf, key);
7182                         if (status == INPUT_OK)
7183                                 buf[pos++] = (char) key;
7184                 }
7185         }
7187         /* Clear the status window */
7188         status_empty = FALSE;
7189         report("");
7191         if (status == INPUT_CANCEL)
7192                 return NULL;
7194         buf[pos++] = 0;
7196         return buf;
7199 static enum input_status
7200 prompt_yesno_handler(void *data, char *buf, int c)
7202         if (c == 'y' || c == 'Y')
7203                 return INPUT_STOP;
7204         if (c == 'n' || c == 'N')
7205                 return INPUT_CANCEL;
7206         return INPUT_SKIP;
7209 static bool
7210 prompt_yesno(const char *prompt)
7212         char prompt2[SIZEOF_STR];
7214         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7215                 return FALSE;
7217         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7220 static enum input_status
7221 read_prompt_handler(void *data, char *buf, int c)
7223         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7226 static char *
7227 read_prompt(const char *prompt)
7229         return prompt_input(prompt, read_prompt_handler, NULL);
7232 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7234         enum input_status status = INPUT_OK;
7235         int size = 0;
7237         while (items[size].text)
7238                 size++;
7240         while (status == INPUT_OK) {
7241                 const struct menu_item *item = &items[*selected];
7242                 int key;
7243                 int i;
7245                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7246                           prompt, *selected + 1, size);
7247                 if (item->hotkey)
7248                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7249                 wprintw(status_win, "%s", item->text);
7250                 wclrtoeol(status_win);
7252                 key = get_input(COLS - 1);
7253                 switch (key) {
7254                 case KEY_RETURN:
7255                 case KEY_ENTER:
7256                 case '\n':
7257                         status = INPUT_STOP;
7258                         break;
7260                 case KEY_LEFT:
7261                 case KEY_UP:
7262                         *selected = *selected - 1;
7263                         if (*selected < 0)
7264                                 *selected = size - 1;
7265                         break;
7267                 case KEY_RIGHT:
7268                 case KEY_DOWN:
7269                         *selected = (*selected + 1) % size;
7270                         break;
7272                 case KEY_ESC:
7273                         status = INPUT_CANCEL;
7274                         break;
7276                 default:
7277                         for (i = 0; items[i].text; i++)
7278                                 if (items[i].hotkey == key) {
7279                                         *selected = i;
7280                                         status = INPUT_STOP;
7281                                         break;
7282                                 }
7283                 }
7284         }
7286         /* Clear the status window */
7287         status_empty = FALSE;
7288         report("");
7290         return status != INPUT_CANCEL;
7293 /*
7294  * Repository properties
7295  */
7297 static struct ref **refs = NULL;
7298 static size_t refs_size = 0;
7299 static struct ref *refs_head = NULL;
7301 static struct ref_list **ref_lists = NULL;
7302 static size_t ref_lists_size = 0;
7304 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7305 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7306 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7308 static int
7309 compare_refs(const void *ref1_, const void *ref2_)
7311         const struct ref *ref1 = *(const struct ref **)ref1_;
7312         const struct ref *ref2 = *(const struct ref **)ref2_;
7314         if (ref1->tag != ref2->tag)
7315                 return ref2->tag - ref1->tag;
7316         if (ref1->ltag != ref2->ltag)
7317                 return ref2->ltag - ref2->ltag;
7318         if (ref1->head != ref2->head)
7319                 return ref2->head - ref1->head;
7320         if (ref1->tracked != ref2->tracked)
7321                 return ref2->tracked - ref1->tracked;
7322         if (ref1->remote != ref2->remote)
7323                 return ref2->remote - ref1->remote;
7324         return strcmp(ref1->name, ref2->name);
7327 static void
7328 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7330         size_t i;
7332         for (i = 0; i < refs_size; i++)
7333                 if (!visitor(data, refs[i]))
7334                         break;
7337 static struct ref *
7338 get_ref_head()
7340         return refs_head;
7343 static struct ref_list *
7344 get_ref_list(const char *id)
7346         struct ref_list *list;
7347         size_t i;
7349         for (i = 0; i < ref_lists_size; i++)
7350                 if (!strcmp(id, ref_lists[i]->id))
7351                         return ref_lists[i];
7353         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7354                 return NULL;
7355         list = calloc(1, sizeof(*list));
7356         if (!list)
7357                 return NULL;
7359         for (i = 0; i < refs_size; i++) {
7360                 if (!strcmp(id, refs[i]->id) &&
7361                     realloc_refs_list(&list->refs, list->size, 1))
7362                         list->refs[list->size++] = refs[i];
7363         }
7365         if (!list->refs) {
7366                 free(list);
7367                 return NULL;
7368         }
7370         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7371         ref_lists[ref_lists_size++] = list;
7372         return list;
7375 static int
7376 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7378         struct ref *ref = NULL;
7379         bool tag = FALSE;
7380         bool ltag = FALSE;
7381         bool remote = FALSE;
7382         bool tracked = FALSE;
7383         bool head = FALSE;
7384         int from = 0, to = refs_size - 1;
7386         if (!prefixcmp(name, "refs/tags/")) {
7387                 if (!suffixcmp(name, namelen, "^{}")) {
7388                         namelen -= 3;
7389                         name[namelen] = 0;
7390                 } else {
7391                         ltag = TRUE;
7392                 }
7394                 tag = TRUE;
7395                 namelen -= STRING_SIZE("refs/tags/");
7396                 name    += STRING_SIZE("refs/tags/");
7398         } else if (!prefixcmp(name, "refs/remotes/")) {
7399                 remote = TRUE;
7400                 namelen -= STRING_SIZE("refs/remotes/");
7401                 name    += STRING_SIZE("refs/remotes/");
7402                 tracked  = !strcmp(opt_remote, name);
7404         } else if (!prefixcmp(name, "refs/heads/")) {
7405                 namelen -= STRING_SIZE("refs/heads/");
7406                 name    += STRING_SIZE("refs/heads/");
7407                 if (!strncmp(opt_head, name, namelen))
7408                         return OK;
7410         } else if (!strcmp(name, "HEAD")) {
7411                 head     = TRUE;
7412                 if (*opt_head) {
7413                         namelen  = strlen(opt_head);
7414                         name     = opt_head;
7415                 }
7416         }
7418         /* If we are reloading or it's an annotated tag, replace the
7419          * previous SHA1 with the resolved commit id; relies on the fact
7420          * git-ls-remote lists the commit id of an annotated tag right
7421          * before the commit id it points to. */
7422         while (from <= to) {
7423                 size_t pos = (to + from) / 2;
7424                 int cmp = strcmp(name, refs[pos]->name);
7426                 if (!cmp) {
7427                         ref = refs[pos];
7428                         break;
7429                 }
7431                 if (cmp < 0)
7432                         to = pos - 1;
7433                 else
7434                         from = pos + 1;
7435         }
7437         if (!ref) {
7438                 if (!realloc_refs(&refs, refs_size, 1))
7439                         return ERR;
7440                 ref = calloc(1, sizeof(*ref) + namelen);
7441                 if (!ref)
7442                         return ERR;
7443                 memmove(refs + from + 1, refs + from,
7444                         (refs_size - from) * sizeof(*refs));
7445                 refs[from] = ref;
7446                 strncpy(ref->name, name, namelen);
7447                 refs_size++;
7448         }
7450         ref->head = head;
7451         ref->tag = tag;
7452         ref->ltag = ltag;
7453         ref->remote = remote;
7454         ref->tracked = tracked;
7455         string_copy_rev(ref->id, id);
7457         if (head)
7458                 refs_head = ref;
7459         return OK;
7462 static int
7463 load_refs(void)
7465         const char *head_argv[] = {
7466                 "git", "symbolic-ref", "HEAD", NULL
7467         };
7468         static const char *ls_remote_argv[SIZEOF_ARG] = {
7469                 "git", "ls-remote", opt_git_dir, NULL
7470         };
7471         static bool init = FALSE;
7472         size_t i;
7474         if (!init) {
7475                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7476                         die("TIG_LS_REMOTE contains too many arguments");
7477                 init = TRUE;
7478         }
7480         if (!*opt_git_dir)
7481                 return OK;
7483         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7484             !prefixcmp(opt_head, "refs/heads/")) {
7485                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7487                 memmove(opt_head, offset, strlen(offset) + 1);
7488         }
7490         refs_head = NULL;
7491         for (i = 0; i < refs_size; i++)
7492                 refs[i]->id[0] = 0;
7494         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7495                 return ERR;
7497         /* Update the ref lists to reflect changes. */
7498         for (i = 0; i < ref_lists_size; i++) {
7499                 struct ref_list *list = ref_lists[i];
7500                 size_t old, new;
7502                 for (old = new = 0; old < list->size; old++)
7503                         if (!strcmp(list->id, list->refs[old]->id))
7504                                 list->refs[new++] = list->refs[old];
7505                 list->size = new;
7506         }
7508         return OK;
7511 static void
7512 set_remote_branch(const char *name, const char *value, size_t valuelen)
7514         if (!strcmp(name, ".remote")) {
7515                 string_ncopy(opt_remote, value, valuelen);
7517         } else if (*opt_remote && !strcmp(name, ".merge")) {
7518                 size_t from = strlen(opt_remote);
7520                 if (!prefixcmp(value, "refs/heads/"))
7521                         value += STRING_SIZE("refs/heads/");
7523                 if (!string_format_from(opt_remote, &from, "/%s", value))
7524                         opt_remote[0] = 0;
7525         }
7528 static void
7529 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7531         const char *argv[SIZEOF_ARG] = { name, "=" };
7532         int argc = 1 + (cmd == option_set_command);
7533         int error = ERR;
7535         if (!argv_from_string(argv, &argc, value))
7536                 config_msg = "Too many option arguments";
7537         else
7538                 error = cmd(argc, argv);
7540         if (error == ERR)
7541                 warn("Option 'tig.%s': %s", name, config_msg);
7544 static bool
7545 set_environment_variable(const char *name, const char *value)
7547         size_t len = strlen(name) + 1 + strlen(value) + 1;
7548         char *env = malloc(len);
7550         if (env &&
7551             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7552             putenv(env) == 0)
7553                 return TRUE;
7554         free(env);
7555         return FALSE;
7558 static void
7559 set_work_tree(const char *value)
7561         char cwd[SIZEOF_STR];
7563         if (!getcwd(cwd, sizeof(cwd)))
7564                 die("Failed to get cwd path: %s", strerror(errno));
7565         if (chdir(opt_git_dir) < 0)
7566                 die("Failed to chdir(%s): %s", strerror(errno));
7567         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7568                 die("Failed to get git path: %s", strerror(errno));
7569         if (chdir(cwd) < 0)
7570                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7571         if (chdir(value) < 0)
7572                 die("Failed to chdir(%s): %s", value, strerror(errno));
7573         if (!getcwd(cwd, sizeof(cwd)))
7574                 die("Failed to get cwd path: %s", strerror(errno));
7575         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7576                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7577         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7578                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7579         opt_is_inside_work_tree = TRUE;
7582 static int
7583 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7585         if (!strcmp(name, "i18n.commitencoding"))
7586                 string_ncopy(opt_encoding, value, valuelen);
7588         else if (!strcmp(name, "core.editor"))
7589                 string_ncopy(opt_editor, value, valuelen);
7591         else if (!strcmp(name, "core.worktree"))
7592                 set_work_tree(value);
7594         else if (!prefixcmp(name, "tig.color."))
7595                 set_repo_config_option(name + 10, value, option_color_command);
7597         else if (!prefixcmp(name, "tig.bind."))
7598                 set_repo_config_option(name + 9, value, option_bind_command);
7600         else if (!prefixcmp(name, "tig."))
7601                 set_repo_config_option(name + 4, value, option_set_command);
7603         else if (*opt_head && !prefixcmp(name, "branch.") &&
7604                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7605                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7607         return OK;
7610 static int
7611 load_git_config(void)
7613         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7615         return io_run_load(config_list_argv, "=", read_repo_config_option);
7618 static int
7619 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7621         if (!opt_git_dir[0]) {
7622                 string_ncopy(opt_git_dir, name, namelen);
7624         } else if (opt_is_inside_work_tree == -1) {
7625                 /* This can be 3 different values depending on the
7626                  * version of git being used. If git-rev-parse does not
7627                  * understand --is-inside-work-tree it will simply echo
7628                  * the option else either "true" or "false" is printed.
7629                  * Default to true for the unknown case. */
7630                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7632         } else if (*name == '.') {
7633                 string_ncopy(opt_cdup, name, namelen);
7635         } else {
7636                 string_ncopy(opt_prefix, name, namelen);
7637         }
7639         return OK;
7642 static int
7643 load_repo_info(void)
7645         const char *rev_parse_argv[] = {
7646                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7647                         "--show-cdup", "--show-prefix", NULL
7648         };
7650         return io_run_load(rev_parse_argv, "=", read_repo_info);
7654 /*
7655  * Main
7656  */
7658 static const char usage[] =
7659 "tig " TIG_VERSION " (" __DATE__ ")\n"
7660 "\n"
7661 "Usage: tig        [options] [revs] [--] [paths]\n"
7662 "   or: tig show   [options] [revs] [--] [paths]\n"
7663 "   or: tig blame  [rev] path\n"
7664 "   or: tig status\n"
7665 "   or: tig <      [git command output]\n"
7666 "\n"
7667 "Options:\n"
7668 "  -v, --version   Show version and exit\n"
7669 "  -h, --help      Show help message and exit";
7671 static void __NORETURN
7672 quit(int sig)
7674         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7675         if (cursed)
7676                 endwin();
7677         exit(0);
7680 static void __NORETURN
7681 die(const char *err, ...)
7683         va_list args;
7685         endwin();
7687         va_start(args, err);
7688         fputs("tig: ", stderr);
7689         vfprintf(stderr, err, args);
7690         fputs("\n", stderr);
7691         va_end(args);
7693         exit(1);
7696 static void
7697 warn(const char *msg, ...)
7699         va_list args;
7701         va_start(args, msg);
7702         fputs("tig warning: ", stderr);
7703         vfprintf(stderr, msg, args);
7704         fputs("\n", stderr);
7705         va_end(args);
7708 static enum request
7709 parse_options(int argc, const char *argv[])
7711         enum request request = REQ_VIEW_MAIN;
7712         const char *subcommand;
7713         bool seen_dashdash = FALSE;
7714         /* XXX: This is vulnerable to the user overriding options
7715          * required for the main view parser. */
7716         const char *custom_argv[SIZEOF_ARG] = {
7717                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7718                         "--topo-order", NULL
7719         };
7720         int i, j = 6;
7722         if (!isatty(STDIN_FILENO)) {
7723                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7724                 return REQ_VIEW_PAGER;
7725         }
7727         if (argc <= 1)
7728                 return REQ_NONE;
7730         subcommand = argv[1];
7731         if (!strcmp(subcommand, "status")) {
7732                 if (argc > 2)
7733                         warn("ignoring arguments after `%s'", subcommand);
7734                 return REQ_VIEW_STATUS;
7736         } else if (!strcmp(subcommand, "blame")) {
7737                 if (argc <= 2 || argc > 4)
7738                         die("invalid number of options to blame\n\n%s", usage);
7740                 i = 2;
7741                 if (argc == 4) {
7742                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7743                         i++;
7744                 }
7746                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7747                 return REQ_VIEW_BLAME;
7749         } else if (!strcmp(subcommand, "show")) {
7750                 request = REQ_VIEW_DIFF;
7752         } else {
7753                 subcommand = NULL;
7754         }
7756         if (subcommand) {
7757                 custom_argv[1] = subcommand;
7758                 j = 2;
7759         }
7761         for (i = 1 + !!subcommand; i < argc; i++) {
7762                 const char *opt = argv[i];
7764                 if (seen_dashdash || !strcmp(opt, "--")) {
7765                         seen_dashdash = TRUE;
7767                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7768                         printf("tig version %s\n", TIG_VERSION);
7769                         quit(0);
7771                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7772                         printf("%s\n", usage);
7773                         quit(0);
7774                 }
7776                 custom_argv[j++] = opt;
7777                 if (j >= ARRAY_SIZE(custom_argv))
7778                         die("command too long");
7779         }
7781         if (!prepare_update(VIEW(request), custom_argv, NULL))
7782                 die("Failed to format arguments");
7784         return request;
7787 int
7788 main(int argc, const char *argv[])
7790         const char *codeset = "UTF-8";
7791         enum request request = parse_options(argc, argv);
7792         struct view *view;
7793         size_t i;
7795         signal(SIGINT, quit);
7796         signal(SIGPIPE, SIG_IGN);
7798         if (setlocale(LC_ALL, "")) {
7799                 codeset = nl_langinfo(CODESET);
7800         }
7802         if (load_repo_info() == ERR)
7803                 die("Failed to load repo info.");
7805         if (load_options() == ERR)
7806                 die("Failed to load user config.");
7808         if (load_git_config() == ERR)
7809                 die("Failed to load repo config.");
7811         /* Require a git repository unless when running in pager mode. */
7812         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7813                 die("Not a git repository");
7815         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7816                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7817                 if (opt_iconv_in == ICONV_NONE)
7818                         die("Failed to initialize character set conversion");
7819         }
7821         if (codeset && strcmp(codeset, "UTF-8")) {
7822                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7823                 if (opt_iconv_out == ICONV_NONE)
7824                         die("Failed to initialize character set conversion");
7825         }
7827         if (load_refs() == ERR)
7828                 die("Failed to load refs.");
7830         foreach_view (view, i)
7831                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7832                         die("Too many arguments in the `%s` environment variable",
7833                             view->cmd_env);
7835         init_display();
7837         if (request != REQ_NONE)
7838                 open_view(NULL, request, OPEN_PREPARED);
7839         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7841         while (view_driver(display[current_view], request)) {
7842                 int key = get_input(0);
7844                 view = display[current_view];
7845                 request = get_keybinding(view->keymap, key);
7847                 /* Some low-level request handling. This keeps access to
7848                  * status_win restricted. */
7849                 switch (request) {
7850                 case REQ_NONE:
7851                         report("Unknown key, press %s for help",
7852                                get_key(view->keymap, REQ_VIEW_HELP));
7853                         break;
7854                 case REQ_PROMPT:
7855                 {
7856                         char *cmd = read_prompt(":");
7858                         if (cmd && isdigit(*cmd)) {
7859                                 int lineno = view->lineno + 1;
7861                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7862                                         select_view_line(view, lineno - 1);
7863                                         report("");
7864                                 } else {
7865                                         report("Unable to parse '%s' as a line number", cmd);
7866                                 }
7868                         } else if (cmd) {
7869                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7870                                 const char *argv[SIZEOF_ARG] = { "git" };
7871                                 int argc = 1;
7873                                 /* When running random commands, initially show the
7874                                  * command in the title. However, it maybe later be
7875                                  * overwritten if a commit line is selected. */
7876                                 string_ncopy(next->ref, cmd, strlen(cmd));
7878                                 if (!argv_from_string(argv, &argc, cmd)) {
7879                                         report("Too many arguments");
7880                                 } else if (!prepare_update(next, argv, NULL)) {
7881                                         report("Failed to format command");
7882                                 } else {
7883                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7884                                 }
7885                         }
7887                         request = REQ_NONE;
7888                         break;
7889                 }
7890                 case REQ_SEARCH:
7891                 case REQ_SEARCH_BACK:
7892                 {
7893                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7894                         char *search = read_prompt(prompt);
7896                         if (search)
7897                                 string_ncopy(opt_search, search, strlen(search));
7898                         else if (*opt_search)
7899                                 request = request == REQ_SEARCH ?
7900                                         REQ_FIND_NEXT :
7901                                         REQ_FIND_PREV;
7902                         else
7903                                 request = REQ_NONE;
7904                         break;
7905                 }
7906                 default:
7907                         break;
7908                 }
7909         }
7911         quit(0);
7913         return 0;