Code

Introduce io_prepare as a fix to plug memory leaks related to argv formatting
[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]);
690 static void
691 argv_copy(const char *dst[], const char *src[])
693         int argc;
695         for (argc = 0; src[argc]; argc++)
696                 dst[argc] = src[argc];
700 /*
701  * Executing external commands.
702  */
704 enum io_type {
705         IO_FD,                  /* File descriptor based IO. */
706         IO_BG,                  /* Execute command in the background. */
707         IO_FG,                  /* Execute command with same std{in,out,err}. */
708         IO_RD,                  /* Read only fork+exec IO. */
709         IO_WR,                  /* Write only fork+exec IO. */
710         IO_AP,                  /* Append fork+exec output to file. */
711 };
713 struct io {
714         enum io_type type;      /* The requested type of pipe. */
715         const char *dir;        /* Directory from which to execute. */
716         pid_t pid;              /* PID of spawned process. */
717         int pipe;               /* Pipe end for reading or writing. */
718         int error;              /* Error status. */
719         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
720         char *buf;              /* Read buffer. */
721         size_t bufalloc;        /* Allocated buffer size. */
722         size_t bufsize;         /* Buffer content size. */
723         char *bufpos;           /* Current buffer position. */
724         unsigned int eof:1;     /* Has end of file been reached. */
725 };
727 static void
728 io_reset(struct io *io)
730         io->pipe = -1;
731         io->pid = 0;
732         io->buf = io->bufpos = NULL;
733         io->bufalloc = io->bufsize = 0;
734         io->error = 0;
735         io->eof = 0;
738 static void
739 io_init(struct io *io, const char *dir, enum io_type type)
741         io_reset(io);
742         io->type = type;
743         io->dir = dir;
746 static void
747 io_prepare(struct io *io, const char *dir, enum io_type type, const char *argv[])
749         io_init(io, dir, type);
750         argv_copy(io->argv, argv);
753 static bool
754 io_format(struct io *io, const char *dir, enum io_type type,
755           const char *argv[], enum format_flags flags)
757         io_init(io, dir, type);
758         return format_argv(io->argv, argv, flags);
761 static bool
762 io_open(struct io *io, const char *fmt, ...)
764         char name[SIZEOF_STR] = "";
765         bool fits;
766         va_list args;
768         io_init(io, NULL, IO_FD);
770         va_start(args, fmt);
771         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
772         va_end(args);
774         if (!fits) {
775                 io->error = ENAMETOOLONG;
776                 return FALSE;
777         }
778         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
779         if (io->pipe == -1)
780                 io->error = errno;
781         return io->pipe != -1;
784 static bool
785 io_kill(struct io *io)
787         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
790 static bool
791 io_done(struct io *io)
793         pid_t pid = io->pid;
795         if (io->pipe != -1)
796                 close(io->pipe);
797         free(io->buf);
798         io_reset(io);
800         while (pid > 0) {
801                 int status;
802                 pid_t waiting = waitpid(pid, &status, 0);
804                 if (waiting < 0) {
805                         if (errno == EINTR)
806                                 continue;
807                         io->error = errno;
808                         return FALSE;
809                 }
811                 return waiting == pid &&
812                        !WIFSIGNALED(status) &&
813                        WIFEXITED(status) &&
814                        !WEXITSTATUS(status);
815         }
817         return TRUE;
820 static bool
821 io_start(struct io *io)
823         int pipefds[2] = { -1, -1 };
825         if (io->type == IO_FD)
826                 return TRUE;
828         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
829                 io->error = errno;
830                 return FALSE;
831         } else if (io->type == IO_AP) {
832                 pipefds[1] = io->pipe;
833         }
835         if ((io->pid = fork())) {
836                 if (io->pid == -1)
837                         io->error = errno;
838                 if (pipefds[!(io->type == IO_WR)] != -1)
839                         close(pipefds[!(io->type == IO_WR)]);
840                 if (io->pid != -1) {
841                         io->pipe = pipefds[!!(io->type == IO_WR)];
842                         return TRUE;
843                 }
845         } else {
846                 if (io->type != IO_FG) {
847                         int devnull = open("/dev/null", O_RDWR);
848                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
849                         int writefd = (io->type == IO_RD || io->type == IO_AP)
850                                                         ? pipefds[1] : devnull;
852                         dup2(readfd,  STDIN_FILENO);
853                         dup2(writefd, STDOUT_FILENO);
854                         dup2(devnull, STDERR_FILENO);
856                         close(devnull);
857                         if (pipefds[0] != -1)
858                                 close(pipefds[0]);
859                         if (pipefds[1] != -1)
860                                 close(pipefds[1]);
861                 }
863                 if (io->dir && *io->dir && chdir(io->dir) == -1)
864                         exit(errno);
866                 execvp(io->argv[0], (char *const*) io->argv);
867                 exit(errno);
868         }
870         if (pipefds[!!(io->type == IO_WR)] != -1)
871                 close(pipefds[!!(io->type == IO_WR)]);
872         return FALSE;
875 static bool
876 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
878         io_prepare(io, dir, type, argv);
879         return io_start(io);
882 static int
883 io_complete(struct io *io)
885         return io_start(io) && io_done(io);
888 static int
889 io_run_bg(const char **argv)
891         struct io io = {};
893         io_prepare(&io, NULL, IO_BG, argv);
894         return io_complete(&io);
897 static bool
898 io_run_fg(const char **argv, const char *dir)
900         struct io io = {};
902         io_prepare(&io, dir, IO_FG, argv);
903         return io_complete(&io);
906 static bool
907 io_run_append(const char **argv, int fd)
909         struct io io = {};
911         io_prepare(&io, NULL, IO_AP, argv);
912         io.pipe = fd;
913         return io_complete(&io);
916 static bool
917 io_run_rd(struct io *io, const char **argv, const char *dir)
919         return io_format(io, dir, IO_RD, argv, FORMAT_NONE) && io_start(io);
922 static bool
923 io_eof(struct io *io)
925         return io->eof;
928 static int
929 io_error(struct io *io)
931         return io->error;
934 static char *
935 io_strerror(struct io *io)
937         return strerror(io->error);
940 static bool
941 io_can_read(struct io *io)
943         struct timeval tv = { 0, 500 };
944         fd_set fds;
946         FD_ZERO(&fds);
947         FD_SET(io->pipe, &fds);
949         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
952 static ssize_t
953 io_read(struct io *io, void *buf, size_t bufsize)
955         do {
956                 ssize_t readsize = read(io->pipe, buf, bufsize);
958                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
959                         continue;
960                 else if (readsize == -1)
961                         io->error = errno;
962                 else if (readsize == 0)
963                         io->eof = 1;
964                 return readsize;
965         } while (1);
968 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
970 static char *
971 io_get(struct io *io, int c, bool can_read)
973         char *eol;
974         ssize_t readsize;
976         while (TRUE) {
977                 if (io->bufsize > 0) {
978                         eol = memchr(io->bufpos, c, io->bufsize);
979                         if (eol) {
980                                 char *line = io->bufpos;
982                                 *eol = 0;
983                                 io->bufpos = eol + 1;
984                                 io->bufsize -= io->bufpos - line;
985                                 return line;
986                         }
987                 }
989                 if (io_eof(io)) {
990                         if (io->bufsize) {
991                                 io->bufpos[io->bufsize] = 0;
992                                 io->bufsize = 0;
993                                 return io->bufpos;
994                         }
995                         return NULL;
996                 }
998                 if (!can_read)
999                         return NULL;
1001                 if (io->bufsize > 0 && io->bufpos > io->buf)
1002                         memmove(io->buf, io->bufpos, io->bufsize);
1004                 if (io->bufalloc == io->bufsize) {
1005                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
1006                                 return NULL;
1007                         io->bufalloc += BUFSIZ;
1008                 }
1010                 io->bufpos = io->buf;
1011                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1012                 if (io_error(io))
1013                         return NULL;
1014                 io->bufsize += readsize;
1015         }
1018 static bool
1019 io_write(struct io *io, const void *buf, size_t bufsize)
1021         size_t written = 0;
1023         while (!io_error(io) && written < bufsize) {
1024                 ssize_t size;
1026                 size = write(io->pipe, buf + written, bufsize - written);
1027                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1028                         continue;
1029                 else if (size == -1)
1030                         io->error = errno;
1031                 else
1032                         written += size;
1033         }
1035         return written == bufsize;
1038 static bool
1039 io_read_buf(struct io *io, char buf[], size_t bufsize)
1041         char *result = io_get(io, '\n', TRUE);
1043         if (result) {
1044                 result = chomp_string(result);
1045                 string_ncopy_do(buf, bufsize, result, strlen(result));
1046         }
1048         return io_done(io) && result;
1051 static bool
1052 io_run_buf(const char **argv, char buf[], size_t bufsize)
1054         struct io io = {};
1056         io_prepare(&io, NULL, IO_RD, argv);
1057         return io_start(&io) && io_read_buf(&io, buf, bufsize);
1060 static int
1061 io_load(struct io *io, const char *separators,
1062         int (*read_property)(char *, size_t, char *, size_t))
1064         char *name;
1065         int state = OK;
1067         if (!io_start(io))
1068                 return ERR;
1070         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1071                 char *value;
1072                 size_t namelen;
1073                 size_t valuelen;
1075                 name = chomp_string(name);
1076                 namelen = strcspn(name, separators);
1078                 if (name[namelen]) {
1079                         name[namelen] = 0;
1080                         value = chomp_string(name + namelen + 1);
1081                         valuelen = strlen(value);
1083                 } else {
1084                         value = "";
1085                         valuelen = 0;
1086                 }
1088                 state = read_property(name, namelen, value, valuelen);
1089         }
1091         if (state != ERR && io_error(io))
1092                 state = ERR;
1093         io_done(io);
1095         return state;
1098 static int
1099 io_run_load(const char **argv, const char *separators,
1100             int (*read_property)(char *, size_t, char *, size_t))
1102         struct io io = {};
1104         io_prepare(&io, NULL, IO_RD, argv);
1105         return io_load(&io, separators, read_property);
1109 /*
1110  * User requests
1111  */
1113 #define REQ_INFO \
1114         /* XXX: Keep the view request first and in sync with views[]. */ \
1115         REQ_GROUP("View switching") \
1116         REQ_(VIEW_MAIN,         "Show main view"), \
1117         REQ_(VIEW_DIFF,         "Show diff view"), \
1118         REQ_(VIEW_LOG,          "Show log view"), \
1119         REQ_(VIEW_TREE,         "Show tree view"), \
1120         REQ_(VIEW_BLOB,         "Show blob view"), \
1121         REQ_(VIEW_BLAME,        "Show blame view"), \
1122         REQ_(VIEW_BRANCH,       "Show branch view"), \
1123         REQ_(VIEW_HELP,         "Show help page"), \
1124         REQ_(VIEW_PAGER,        "Show pager view"), \
1125         REQ_(VIEW_STATUS,       "Show status view"), \
1126         REQ_(VIEW_STAGE,        "Show stage view"), \
1127         \
1128         REQ_GROUP("View manipulation") \
1129         REQ_(ENTER,             "Enter current line and scroll"), \
1130         REQ_(NEXT,              "Move to next"), \
1131         REQ_(PREVIOUS,          "Move to previous"), \
1132         REQ_(PARENT,            "Move to parent"), \
1133         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1134         REQ_(REFRESH,           "Reload and refresh"), \
1135         REQ_(MAXIMIZE,          "Maximize the current view"), \
1136         REQ_(VIEW_CLOSE,        "Close the current view"), \
1137         REQ_(QUIT,              "Close all views and quit"), \
1138         \
1139         REQ_GROUP("View specific requests") \
1140         REQ_(STATUS_UPDATE,     "Update file status"), \
1141         REQ_(STATUS_REVERT,     "Revert file changes"), \
1142         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1143         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1144         \
1145         REQ_GROUP("Cursor navigation") \
1146         REQ_(MOVE_UP,           "Move cursor one line up"), \
1147         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1148         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1149         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1150         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1151         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1152         \
1153         REQ_GROUP("Scrolling") \
1154         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1155         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1156         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1157         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1158         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1159         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1160         \
1161         REQ_GROUP("Searching") \
1162         REQ_(SEARCH,            "Search the view"), \
1163         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1164         REQ_(FIND_NEXT,         "Find next search match"), \
1165         REQ_(FIND_PREV,         "Find previous search match"), \
1166         \
1167         REQ_GROUP("Option manipulation") \
1168         REQ_(OPTIONS,           "Open option menu"), \
1169         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1170         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1171         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1172         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1173         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1174         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1175         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1176         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1177         \
1178         REQ_GROUP("Misc") \
1179         REQ_(PROMPT,            "Bring up the prompt"), \
1180         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1181         REQ_(SHOW_VERSION,      "Show version information"), \
1182         REQ_(STOP_LOADING,      "Stop all loading views"), \
1183         REQ_(EDIT,              "Open in editor"), \
1184         REQ_(NONE,              "Do nothing")
1187 /* User action requests. */
1188 enum request {
1189 #define REQ_GROUP(help)
1190 #define REQ_(req, help) REQ_##req
1192         /* Offset all requests to avoid conflicts with ncurses getch values. */
1193         REQ_UNKNOWN = KEY_MAX + 1,
1194         REQ_OFFSET,
1195         REQ_INFO
1197 #undef  REQ_GROUP
1198 #undef  REQ_
1199 };
1201 struct request_info {
1202         enum request request;
1203         const char *name;
1204         int namelen;
1205         const char *help;
1206 };
1208 static const struct request_info req_info[] = {
1209 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1210 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1211         REQ_INFO
1212 #undef  REQ_GROUP
1213 #undef  REQ_
1214 };
1216 static enum request
1217 get_request(const char *name)
1219         int namelen = strlen(name);
1220         int i;
1222         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1223                 if (enum_equals(req_info[i], name, namelen))
1224                         return req_info[i].request;
1226         return REQ_UNKNOWN;
1230 /*
1231  * Options
1232  */
1234 /* Option and state variables. */
1235 static enum date opt_date               = DATE_DEFAULT;
1236 static enum author opt_author           = AUTHOR_DEFAULT;
1237 static bool opt_line_number             = FALSE;
1238 static bool opt_line_graphics           = TRUE;
1239 static bool opt_rev_graph               = FALSE;
1240 static bool opt_show_refs               = TRUE;
1241 static int opt_num_interval             = 5;
1242 static double opt_hscroll               = 0.50;
1243 static double opt_scale_split_view      = 2.0 / 3.0;
1244 static int opt_tab_size                 = 8;
1245 static int opt_author_cols              = AUTHOR_COLS;
1246 static char opt_path[SIZEOF_STR]        = "";
1247 static char opt_file[SIZEOF_STR]        = "";
1248 static char opt_ref[SIZEOF_REF]         = "";
1249 static char opt_head[SIZEOF_REF]        = "";
1250 static char opt_remote[SIZEOF_REF]      = "";
1251 static char opt_encoding[20]            = "UTF-8";
1252 static iconv_t opt_iconv_in             = ICONV_NONE;
1253 static iconv_t opt_iconv_out            = ICONV_NONE;
1254 static char opt_search[SIZEOF_STR]      = "";
1255 static char opt_cdup[SIZEOF_STR]        = "";
1256 static char opt_prefix[SIZEOF_STR]      = "";
1257 static char opt_git_dir[SIZEOF_STR]     = "";
1258 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1259 static char opt_editor[SIZEOF_STR]      = "";
1260 static FILE *opt_tty                    = NULL;
1262 #define is_initial_commit()     (!get_ref_head())
1263 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1266 /*
1267  * Line-oriented content detection.
1268  */
1270 #define LINE_INFO \
1271 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1272 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1273 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1274 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1275 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1276 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1277 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1278 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1279 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1280 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1281 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1282 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1283 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1284 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1285 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1286 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1287 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1288 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1289 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1290 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1291 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1292 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1293 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1294 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1295 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1296 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1297 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1298 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1299 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1300 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1301 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1302 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1303 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1304 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1305 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1306 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1307 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1308 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1309 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1310 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1311 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1312 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1313 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1314 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1315 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1316 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1317 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1318 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1319 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1320 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1321 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1322 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1323 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1324 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1325 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1326 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1327 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1328 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1329 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1331 enum line_type {
1332 #define LINE(type, line, fg, bg, attr) \
1333         LINE_##type
1334         LINE_INFO,
1335         LINE_NONE
1336 #undef  LINE
1337 };
1339 struct line_info {
1340         const char *name;       /* Option name. */
1341         int namelen;            /* Size of option name. */
1342         const char *line;       /* The start of line to match. */
1343         int linelen;            /* Size of string to match. */
1344         int fg, bg, attr;       /* Color and text attributes for the lines. */
1345 };
1347 static struct line_info line_info[] = {
1348 #define LINE(type, line, fg, bg, attr) \
1349         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1350         LINE_INFO
1351 #undef  LINE
1352 };
1354 static enum line_type
1355 get_line_type(const char *line)
1357         int linelen = strlen(line);
1358         enum line_type type;
1360         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1361                 /* Case insensitive search matches Signed-off-by lines better. */
1362                 if (linelen >= line_info[type].linelen &&
1363                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1364                         return type;
1366         return LINE_DEFAULT;
1369 static inline int
1370 get_line_attr(enum line_type type)
1372         assert(type < ARRAY_SIZE(line_info));
1373         return COLOR_PAIR(type) | line_info[type].attr;
1376 static struct line_info *
1377 get_line_info(const char *name)
1379         size_t namelen = strlen(name);
1380         enum line_type type;
1382         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1383                 if (enum_equals(line_info[type], name, namelen))
1384                         return &line_info[type];
1386         return NULL;
1389 static void
1390 init_colors(void)
1392         int default_bg = line_info[LINE_DEFAULT].bg;
1393         int default_fg = line_info[LINE_DEFAULT].fg;
1394         enum line_type type;
1396         start_color();
1398         if (assume_default_colors(default_fg, default_bg) == ERR) {
1399                 default_bg = COLOR_BLACK;
1400                 default_fg = COLOR_WHITE;
1401         }
1403         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1404                 struct line_info *info = &line_info[type];
1405                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1406                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1408                 init_pair(type, fg, bg);
1409         }
1412 struct line {
1413         enum line_type type;
1415         /* State flags */
1416         unsigned int selected:1;
1417         unsigned int dirty:1;
1418         unsigned int cleareol:1;
1419         unsigned int other:16;
1421         void *data;             /* User data */
1422 };
1425 /*
1426  * Keys
1427  */
1429 struct keybinding {
1430         int alias;
1431         enum request request;
1432 };
1434 static struct keybinding default_keybindings[] = {
1435         /* View switching */
1436         { 'm',          REQ_VIEW_MAIN },
1437         { 'd',          REQ_VIEW_DIFF },
1438         { 'l',          REQ_VIEW_LOG },
1439         { 't',          REQ_VIEW_TREE },
1440         { 'f',          REQ_VIEW_BLOB },
1441         { 'B',          REQ_VIEW_BLAME },
1442         { 'H',          REQ_VIEW_BRANCH },
1443         { 'p',          REQ_VIEW_PAGER },
1444         { 'h',          REQ_VIEW_HELP },
1445         { 'S',          REQ_VIEW_STATUS },
1446         { 'c',          REQ_VIEW_STAGE },
1448         /* View manipulation */
1449         { 'q',          REQ_VIEW_CLOSE },
1450         { KEY_TAB,      REQ_VIEW_NEXT },
1451         { KEY_RETURN,   REQ_ENTER },
1452         { KEY_UP,       REQ_PREVIOUS },
1453         { KEY_DOWN,     REQ_NEXT },
1454         { 'R',          REQ_REFRESH },
1455         { KEY_F(5),     REQ_REFRESH },
1456         { 'O',          REQ_MAXIMIZE },
1458         /* Cursor navigation */
1459         { 'k',          REQ_MOVE_UP },
1460         { 'j',          REQ_MOVE_DOWN },
1461         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1462         { KEY_END,      REQ_MOVE_LAST_LINE },
1463         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1464         { ' ',          REQ_MOVE_PAGE_DOWN },
1465         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1466         { 'b',          REQ_MOVE_PAGE_UP },
1467         { '-',          REQ_MOVE_PAGE_UP },
1469         /* Scrolling */
1470         { KEY_LEFT,     REQ_SCROLL_LEFT },
1471         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1472         { KEY_IC,       REQ_SCROLL_LINE_UP },
1473         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1474         { 'w',          REQ_SCROLL_PAGE_UP },
1475         { 's',          REQ_SCROLL_PAGE_DOWN },
1477         /* Searching */
1478         { '/',          REQ_SEARCH },
1479         { '?',          REQ_SEARCH_BACK },
1480         { 'n',          REQ_FIND_NEXT },
1481         { 'N',          REQ_FIND_PREV },
1483         /* Misc */
1484         { 'Q',          REQ_QUIT },
1485         { 'z',          REQ_STOP_LOADING },
1486         { 'v',          REQ_SHOW_VERSION },
1487         { 'r',          REQ_SCREEN_REDRAW },
1488         { 'o',          REQ_OPTIONS },
1489         { '.',          REQ_TOGGLE_LINENO },
1490         { 'D',          REQ_TOGGLE_DATE },
1491         { 'A',          REQ_TOGGLE_AUTHOR },
1492         { 'g',          REQ_TOGGLE_REV_GRAPH },
1493         { 'F',          REQ_TOGGLE_REFS },
1494         { 'I',          REQ_TOGGLE_SORT_ORDER },
1495         { 'i',          REQ_TOGGLE_SORT_FIELD },
1496         { ':',          REQ_PROMPT },
1497         { 'u',          REQ_STATUS_UPDATE },
1498         { '!',          REQ_STATUS_REVERT },
1499         { 'M',          REQ_STATUS_MERGE },
1500         { '@',          REQ_STAGE_NEXT },
1501         { ',',          REQ_PARENT },
1502         { 'e',          REQ_EDIT },
1503 };
1505 #define KEYMAP_INFO \
1506         KEYMAP_(GENERIC), \
1507         KEYMAP_(MAIN), \
1508         KEYMAP_(DIFF), \
1509         KEYMAP_(LOG), \
1510         KEYMAP_(TREE), \
1511         KEYMAP_(BLOB), \
1512         KEYMAP_(BLAME), \
1513         KEYMAP_(BRANCH), \
1514         KEYMAP_(PAGER), \
1515         KEYMAP_(HELP), \
1516         KEYMAP_(STATUS), \
1517         KEYMAP_(STAGE)
1519 enum keymap {
1520 #define KEYMAP_(name) KEYMAP_##name
1521         KEYMAP_INFO
1522 #undef  KEYMAP_
1523 };
1525 static const struct enum_map keymap_table[] = {
1526 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1527         KEYMAP_INFO
1528 #undef  KEYMAP_
1529 };
1531 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1533 struct keybinding_table {
1534         struct keybinding *data;
1535         size_t size;
1536 };
1538 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1540 static void
1541 add_keybinding(enum keymap keymap, enum request request, int key)
1543         struct keybinding_table *table = &keybindings[keymap];
1544         size_t i;
1546         for (i = 0; i < keybindings[keymap].size; i++) {
1547                 if (keybindings[keymap].data[i].alias == key) {
1548                         keybindings[keymap].data[i].request = request;
1549                         return;
1550                 }
1551         }
1553         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1554         if (!table->data)
1555                 die("Failed to allocate keybinding");
1556         table->data[table->size].alias = key;
1557         table->data[table->size++].request = request;
1559         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1560                 int i;
1562                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1563                         if (default_keybindings[i].alias == key)
1564                                 default_keybindings[i].request = REQ_NONE;
1565         }
1568 /* Looks for a key binding first in the given map, then in the generic map, and
1569  * lastly in the default keybindings. */
1570 static enum request
1571 get_keybinding(enum keymap keymap, int key)
1573         size_t i;
1575         for (i = 0; i < keybindings[keymap].size; i++)
1576                 if (keybindings[keymap].data[i].alias == key)
1577                         return keybindings[keymap].data[i].request;
1579         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1580                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1581                         return keybindings[KEYMAP_GENERIC].data[i].request;
1583         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1584                 if (default_keybindings[i].alias == key)
1585                         return default_keybindings[i].request;
1587         return (enum request) key;
1591 struct key {
1592         const char *name;
1593         int value;
1594 };
1596 static const struct key key_table[] = {
1597         { "Enter",      KEY_RETURN },
1598         { "Space",      ' ' },
1599         { "Backspace",  KEY_BACKSPACE },
1600         { "Tab",        KEY_TAB },
1601         { "Escape",     KEY_ESC },
1602         { "Left",       KEY_LEFT },
1603         { "Right",      KEY_RIGHT },
1604         { "Up",         KEY_UP },
1605         { "Down",       KEY_DOWN },
1606         { "Insert",     KEY_IC },
1607         { "Delete",     KEY_DC },
1608         { "Hash",       '#' },
1609         { "Home",       KEY_HOME },
1610         { "End",        KEY_END },
1611         { "PageUp",     KEY_PPAGE },
1612         { "PageDown",   KEY_NPAGE },
1613         { "F1",         KEY_F(1) },
1614         { "F2",         KEY_F(2) },
1615         { "F3",         KEY_F(3) },
1616         { "F4",         KEY_F(4) },
1617         { "F5",         KEY_F(5) },
1618         { "F6",         KEY_F(6) },
1619         { "F7",         KEY_F(7) },
1620         { "F8",         KEY_F(8) },
1621         { "F9",         KEY_F(9) },
1622         { "F10",        KEY_F(10) },
1623         { "F11",        KEY_F(11) },
1624         { "F12",        KEY_F(12) },
1625 };
1627 static int
1628 get_key_value(const char *name)
1630         int i;
1632         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1633                 if (!strcasecmp(key_table[i].name, name))
1634                         return key_table[i].value;
1636         if (strlen(name) == 1 && isprint(*name))
1637                 return (int) *name;
1639         return ERR;
1642 static const char *
1643 get_key_name(int key_value)
1645         static char key_char[] = "'X'";
1646         const char *seq = NULL;
1647         int key;
1649         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1650                 if (key_table[key].value == key_value)
1651                         seq = key_table[key].name;
1653         if (seq == NULL &&
1654             key_value < 127 &&
1655             isprint(key_value)) {
1656                 key_char[1] = (char) key_value;
1657                 seq = key_char;
1658         }
1660         return seq ? seq : "(no key)";
1663 static bool
1664 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1666         const char *sep = *pos > 0 ? ", " : "";
1667         const char *keyname = get_key_name(keybinding->alias);
1669         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1672 static bool
1673 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1674                            enum keymap keymap, bool all)
1676         int i;
1678         for (i = 0; i < keybindings[keymap].size; i++) {
1679                 if (keybindings[keymap].data[i].request == request) {
1680                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1681                                 return FALSE;
1682                         if (!all)
1683                                 break;
1684                 }
1685         }
1687         return TRUE;
1690 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1692 static const char *
1693 get_keys(enum keymap keymap, enum request request, bool all)
1695         static char buf[BUFSIZ];
1696         size_t pos = 0;
1697         int i;
1699         buf[pos] = 0;
1701         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1702                 return "Too many keybindings!";
1703         if (pos > 0 && !all)
1704                 return buf;
1706         if (keymap != KEYMAP_GENERIC) {
1707                 /* Only the generic keymap includes the default keybindings when
1708                  * listing all keys. */
1709                 if (all)
1710                         return buf;
1712                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1713                         return "Too many keybindings!";
1714                 if (pos)
1715                         return buf;
1716         }
1718         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1719                 if (default_keybindings[i].request == request) {
1720                         if (!append_key(buf, &pos, &default_keybindings[i]))
1721                                 return "Too many keybindings!";
1722                         if (!all)
1723                                 return buf;
1724                 }
1725         }
1727         return buf;
1730 struct run_request {
1731         enum keymap keymap;
1732         int key;
1733         const char *argv[SIZEOF_ARG];
1734 };
1736 static struct run_request *run_request;
1737 static size_t run_requests;
1739 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1741 static enum request
1742 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1744         struct run_request *req;
1746         if (argc >= ARRAY_SIZE(req->argv) - 1)
1747                 return REQ_NONE;
1749         if (!realloc_run_requests(&run_request, run_requests, 1))
1750                 return REQ_NONE;
1752         req = &run_request[run_requests];
1753         req->keymap = keymap;
1754         req->key = key;
1755         req->argv[0] = NULL;
1757         if (!format_argv(req->argv, argv, FORMAT_NONE))
1758                 return REQ_NONE;
1760         return REQ_NONE + ++run_requests;
1763 static struct run_request *
1764 get_run_request(enum request request)
1766         if (request <= REQ_NONE)
1767                 return NULL;
1768         return &run_request[request - REQ_NONE - 1];
1771 static void
1772 add_builtin_run_requests(void)
1774         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1775         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1776         const char *commit[] = { "git", "commit", NULL };
1777         const char *gc[] = { "git", "gc", NULL };
1778         struct {
1779                 enum keymap keymap;
1780                 int key;
1781                 int argc;
1782                 const char **argv;
1783         } reqs[] = {
1784                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1785                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1786                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1787                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1788         };
1789         int i;
1791         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1792                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1794                 if (req != reqs[i].key)
1795                         continue;
1796                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1797                 if (req != REQ_NONE)
1798                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1799         }
1802 /*
1803  * User config file handling.
1804  */
1806 static int   config_lineno;
1807 static bool  config_errors;
1808 static const char *config_msg;
1810 static const struct enum_map color_map[] = {
1811 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1812         COLOR_MAP(DEFAULT),
1813         COLOR_MAP(BLACK),
1814         COLOR_MAP(BLUE),
1815         COLOR_MAP(CYAN),
1816         COLOR_MAP(GREEN),
1817         COLOR_MAP(MAGENTA),
1818         COLOR_MAP(RED),
1819         COLOR_MAP(WHITE),
1820         COLOR_MAP(YELLOW),
1821 };
1823 static const struct enum_map attr_map[] = {
1824 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1825         ATTR_MAP(NORMAL),
1826         ATTR_MAP(BLINK),
1827         ATTR_MAP(BOLD),
1828         ATTR_MAP(DIM),
1829         ATTR_MAP(REVERSE),
1830         ATTR_MAP(STANDOUT),
1831         ATTR_MAP(UNDERLINE),
1832 };
1834 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1836 static int parse_step(double *opt, const char *arg)
1838         *opt = atoi(arg);
1839         if (!strchr(arg, '%'))
1840                 return OK;
1842         /* "Shift down" so 100% and 1 does not conflict. */
1843         *opt = (*opt - 1) / 100;
1844         if (*opt >= 1.0) {
1845                 *opt = 0.99;
1846                 config_msg = "Step value larger than 100%";
1847                 return ERR;
1848         }
1849         if (*opt < 0.0) {
1850                 *opt = 1;
1851                 config_msg = "Invalid step value";
1852                 return ERR;
1853         }
1854         return OK;
1857 static int
1858 parse_int(int *opt, const char *arg, int min, int max)
1860         int value = atoi(arg);
1862         if (min <= value && value <= max) {
1863                 *opt = value;
1864                 return OK;
1865         }
1867         config_msg = "Integer value out of bound";
1868         return ERR;
1871 static bool
1872 set_color(int *color, const char *name)
1874         if (map_enum(color, color_map, name))
1875                 return TRUE;
1876         if (!prefixcmp(name, "color"))
1877                 return parse_int(color, name + 5, 0, 255) == OK;
1878         return FALSE;
1881 /* Wants: object fgcolor bgcolor [attribute] */
1882 static int
1883 option_color_command(int argc, const char *argv[])
1885         struct line_info *info;
1887         if (argc < 3) {
1888                 config_msg = "Wrong number of arguments given to color command";
1889                 return ERR;
1890         }
1892         info = get_line_info(argv[0]);
1893         if (!info) {
1894                 static const struct enum_map obsolete[] = {
1895                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1896                         ENUM_MAP("main-date",   LINE_DATE),
1897                         ENUM_MAP("main-author", LINE_AUTHOR),
1898                 };
1899                 int index;
1901                 if (!map_enum(&index, obsolete, argv[0])) {
1902                         config_msg = "Unknown color name";
1903                         return ERR;
1904                 }
1905                 info = &line_info[index];
1906         }
1908         if (!set_color(&info->fg, argv[1]) ||
1909             !set_color(&info->bg, argv[2])) {
1910                 config_msg = "Unknown color";
1911                 return ERR;
1912         }
1914         info->attr = 0;
1915         while (argc-- > 3) {
1916                 int attr;
1918                 if (!set_attribute(&attr, argv[argc])) {
1919                         config_msg = "Unknown attribute";
1920                         return ERR;
1921                 }
1922                 info->attr |= attr;
1923         }
1925         return OK;
1928 static int parse_bool(bool *opt, const char *arg)
1930         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1931                 ? TRUE : FALSE;
1932         return OK;
1935 static int parse_enum_do(unsigned int *opt, const char *arg,
1936                          const struct enum_map *map, size_t map_size)
1938         bool is_true;
1940         assert(map_size > 1);
1942         if (map_enum_do(map, map_size, (int *) opt, arg))
1943                 return OK;
1945         if (parse_bool(&is_true, arg) != OK)
1946                 return ERR;
1948         *opt = is_true ? map[1].value : map[0].value;
1949         return OK;
1952 #define parse_enum(opt, arg, map) \
1953         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1955 static int
1956 parse_string(char *opt, const char *arg, size_t optsize)
1958         int arglen = strlen(arg);
1960         switch (arg[0]) {
1961         case '\"':
1962         case '\'':
1963                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1964                         config_msg = "Unmatched quotation";
1965                         return ERR;
1966                 }
1967                 arg += 1; arglen -= 2;
1968         default:
1969                 string_ncopy_do(opt, optsize, arg, arglen);
1970                 return OK;
1971         }
1974 /* Wants: name = value */
1975 static int
1976 option_set_command(int argc, const char *argv[])
1978         if (argc != 3) {
1979                 config_msg = "Wrong number of arguments given to set command";
1980                 return ERR;
1981         }
1983         if (strcmp(argv[1], "=")) {
1984                 config_msg = "No value assigned";
1985                 return ERR;
1986         }
1988         if (!strcmp(argv[0], "show-author"))
1989                 return parse_enum(&opt_author, argv[2], author_map);
1991         if (!strcmp(argv[0], "show-date"))
1992                 return parse_enum(&opt_date, argv[2], date_map);
1994         if (!strcmp(argv[0], "show-rev-graph"))
1995                 return parse_bool(&opt_rev_graph, argv[2]);
1997         if (!strcmp(argv[0], "show-refs"))
1998                 return parse_bool(&opt_show_refs, argv[2]);
2000         if (!strcmp(argv[0], "show-line-numbers"))
2001                 return parse_bool(&opt_line_number, argv[2]);
2003         if (!strcmp(argv[0], "line-graphics"))
2004                 return parse_bool(&opt_line_graphics, argv[2]);
2006         if (!strcmp(argv[0], "line-number-interval"))
2007                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2009         if (!strcmp(argv[0], "author-width"))
2010                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2012         if (!strcmp(argv[0], "horizontal-scroll"))
2013                 return parse_step(&opt_hscroll, argv[2]);
2015         if (!strcmp(argv[0], "split-view-height"))
2016                 return parse_step(&opt_scale_split_view, argv[2]);
2018         if (!strcmp(argv[0], "tab-size"))
2019                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2021         if (!strcmp(argv[0], "commit-encoding"))
2022                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2024         config_msg = "Unknown variable name";
2025         return ERR;
2028 /* Wants: mode request key */
2029 static int
2030 option_bind_command(int argc, const char *argv[])
2032         enum request request;
2033         int keymap = -1;
2034         int key;
2036         if (argc < 3) {
2037                 config_msg = "Wrong number of arguments given to bind command";
2038                 return ERR;
2039         }
2041         if (!set_keymap(&keymap, argv[0])) {
2042                 config_msg = "Unknown key map";
2043                 return ERR;
2044         }
2046         key = get_key_value(argv[1]);
2047         if (key == ERR) {
2048                 config_msg = "Unknown key";
2049                 return ERR;
2050         }
2052         request = get_request(argv[2]);
2053         if (request == REQ_UNKNOWN) {
2054                 static const struct enum_map obsolete[] = {
2055                         ENUM_MAP("cherry-pick",         REQ_NONE),
2056                         ENUM_MAP("screen-resize",       REQ_NONE),
2057                         ENUM_MAP("tree-parent",         REQ_PARENT),
2058                 };
2059                 int alias;
2061                 if (map_enum(&alias, obsolete, argv[2])) {
2062                         if (alias != REQ_NONE)
2063                                 add_keybinding(keymap, alias, key);
2064                         config_msg = "Obsolete request name";
2065                         return ERR;
2066                 }
2067         }
2068         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2069                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2070         if (request == REQ_UNKNOWN) {
2071                 config_msg = "Unknown request name";
2072                 return ERR;
2073         }
2075         add_keybinding(keymap, request, key);
2077         return OK;
2080 static int
2081 set_option(const char *opt, char *value)
2083         const char *argv[SIZEOF_ARG];
2084         int argc = 0;
2086         if (!argv_from_string(argv, &argc, value)) {
2087                 config_msg = "Too many option arguments";
2088                 return ERR;
2089         }
2091         if (!strcmp(opt, "color"))
2092                 return option_color_command(argc, argv);
2094         if (!strcmp(opt, "set"))
2095                 return option_set_command(argc, argv);
2097         if (!strcmp(opt, "bind"))
2098                 return option_bind_command(argc, argv);
2100         config_msg = "Unknown option command";
2101         return ERR;
2104 static int
2105 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2107         int status = OK;
2109         config_lineno++;
2110         config_msg = "Internal error";
2112         /* Check for comment markers, since read_properties() will
2113          * only ensure opt and value are split at first " \t". */
2114         optlen = strcspn(opt, "#");
2115         if (optlen == 0)
2116                 return OK;
2118         if (opt[optlen] != 0) {
2119                 config_msg = "No option value";
2120                 status = ERR;
2122         }  else {
2123                 /* Look for comment endings in the value. */
2124                 size_t len = strcspn(value, "#");
2126                 if (len < valuelen) {
2127                         valuelen = len;
2128                         value[valuelen] = 0;
2129                 }
2131                 status = set_option(opt, value);
2132         }
2134         if (status == ERR) {
2135                 warn("Error on line %d, near '%.*s': %s",
2136                      config_lineno, (int) optlen, opt, config_msg);
2137                 config_errors = TRUE;
2138         }
2140         /* Always keep going if errors are encountered. */
2141         return OK;
2144 static void
2145 load_option_file(const char *path)
2147         struct io io = {};
2149         /* It's OK that the file doesn't exist. */
2150         if (!io_open(&io, "%s", path))
2151                 return;
2153         config_lineno = 0;
2154         config_errors = FALSE;
2156         if (io_load(&io, " \t", read_option) == ERR ||
2157             config_errors == TRUE)
2158                 warn("Errors while loading %s.", path);
2161 static int
2162 load_options(void)
2164         const char *home = getenv("HOME");
2165         const char *tigrc_user = getenv("TIGRC_USER");
2166         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2167         char buf[SIZEOF_STR];
2169         if (!tigrc_system)
2170                 tigrc_system = SYSCONFDIR "/tigrc";
2171         load_option_file(tigrc_system);
2173         if (!tigrc_user) {
2174                 if (!home || !string_format(buf, "%s/.tigrc", home))
2175                         return ERR;
2176                 tigrc_user = buf;
2177         }
2178         load_option_file(tigrc_user);
2180         /* Add _after_ loading config files to avoid adding run requests
2181          * that conflict with keybindings. */
2182         add_builtin_run_requests();
2184         return OK;
2188 /*
2189  * The viewer
2190  */
2192 struct view;
2193 struct view_ops;
2195 /* The display array of active views and the index of the current view. */
2196 static struct view *display[2];
2197 static unsigned int current_view;
2199 #define foreach_displayed_view(view, i) \
2200         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2202 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2204 /* Current head and commit ID */
2205 static char ref_blob[SIZEOF_REF]        = "";
2206 static char ref_commit[SIZEOF_REF]      = "HEAD";
2207 static char ref_head[SIZEOF_REF]        = "HEAD";
2208 static char ref_branch[SIZEOF_REF]      = "";
2210 enum view_type {
2211         VIEW_MAIN,
2212         VIEW_DIFF,
2213         VIEW_LOG,
2214         VIEW_TREE,
2215         VIEW_BLOB,
2216         VIEW_BLAME,
2217         VIEW_BRANCH,
2218         VIEW_HELP,
2219         VIEW_PAGER,
2220         VIEW_STATUS,
2221         VIEW_STAGE,
2222 };
2224 struct view {
2225         enum view_type type;    /* View type */
2226         const char *name;       /* View name */
2227         const char *cmd_env;    /* Command line set via environment */
2228         const char *id;         /* Points to either of ref_{head,commit,blob} */
2230         struct view_ops *ops;   /* View operations */
2232         enum keymap keymap;     /* What keymap does this view have */
2233         bool git_dir;           /* Whether the view requires a git directory. */
2235         char ref[SIZEOF_REF];   /* Hovered commit reference */
2236         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2238         int height, width;      /* The width and height of the main window */
2239         WINDOW *win;            /* The main window */
2240         WINDOW *title;          /* The title window living below the main window */
2242         /* Navigation */
2243         unsigned long offset;   /* Offset of the window top */
2244         unsigned long yoffset;  /* Offset from the window side. */
2245         unsigned long lineno;   /* Current line number */
2246         unsigned long p_offset; /* Previous offset of the window top */
2247         unsigned long p_yoffset;/* Previous offset from the window side */
2248         unsigned long p_lineno; /* Previous current line number */
2249         bool p_restore;         /* Should the previous position be restored. */
2251         /* Searching */
2252         char grep[SIZEOF_STR];  /* Search string */
2253         regex_t *regex;         /* Pre-compiled regexp */
2255         /* If non-NULL, points to the view that opened this view. If this view
2256          * is closed tig will switch back to the parent view. */
2257         struct view *parent;
2258         struct view *prev;
2260         /* Buffering */
2261         size_t lines;           /* Total number of lines */
2262         struct line *line;      /* Line index */
2263         unsigned int digits;    /* Number of digits in the lines member. */
2265         /* Drawing */
2266         struct line *curline;   /* Line currently being drawn. */
2267         enum line_type curtype; /* Attribute currently used for drawing. */
2268         unsigned long col;      /* Column when drawing. */
2269         bool has_scrolled;      /* View was scrolled. */
2271         /* Loading */
2272         struct io io;
2273         struct io *pipe;
2274         time_t start_time;
2275         time_t update_secs;
2276 };
2278 struct view_ops {
2279         /* What type of content being displayed. Used in the title bar. */
2280         const char *type;
2281         /* Default command arguments. */
2282         const char **argv;
2283         /* Open and reads in all view content. */
2284         bool (*open)(struct view *view);
2285         /* Read one line; updates view->line. */
2286         bool (*read)(struct view *view, char *data);
2287         /* Draw one line; @lineno must be < view->height. */
2288         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2289         /* Depending on view handle a special requests. */
2290         enum request (*request)(struct view *view, enum request request, struct line *line);
2291         /* Search for regexp in a line. */
2292         bool (*grep)(struct view *view, struct line *line);
2293         /* Select line */
2294         void (*select)(struct view *view, struct line *line);
2295         /* Prepare view for loading */
2296         bool (*prepare)(struct view *view);
2297 };
2299 static struct view_ops blame_ops;
2300 static struct view_ops blob_ops;
2301 static struct view_ops diff_ops;
2302 static struct view_ops help_ops;
2303 static struct view_ops log_ops;
2304 static struct view_ops main_ops;
2305 static struct view_ops pager_ops;
2306 static struct view_ops stage_ops;
2307 static struct view_ops status_ops;
2308 static struct view_ops tree_ops;
2309 static struct view_ops branch_ops;
2311 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2312         { type, name, #env, ref, ops, map, git }
2314 #define VIEW_(id, name, ops, git, ref) \
2315         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2317 static struct view views[] = {
2318         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2319         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2320         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2321         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2322         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2323         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2324         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2325         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2326         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2327         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2328         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2329 };
2331 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2333 #define foreach_view(view, i) \
2334         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2336 #define view_is_displayed(view) \
2337         (view == display[0] || view == display[1])
2339 static enum request
2340 view_request(struct view *view, enum request request)
2342         if (!view || !view->lines)
2343                 return request;
2344         return view->ops->request(view, request, &view->line[view->lineno]);
2348 /*
2349  * View drawing.
2350  */
2352 static inline void
2353 set_view_attr(struct view *view, enum line_type type)
2355         if (!view->curline->selected && view->curtype != type) {
2356                 (void) wattrset(view->win, get_line_attr(type));
2357                 wchgat(view->win, -1, 0, type, NULL);
2358                 view->curtype = type;
2359         }
2362 static int
2363 draw_chars(struct view *view, enum line_type type, const char *string,
2364            int max_len, bool use_tilde)
2366         static char out_buffer[BUFSIZ * 2];
2367         int len = 0;
2368         int col = 0;
2369         int trimmed = FALSE;
2370         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2372         if (max_len <= 0)
2373                 return 0;
2375         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2377         set_view_attr(view, type);
2378         if (len > 0) {
2379                 if (opt_iconv_out != ICONV_NONE) {
2380                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2381                         size_t inlen = len + 1;
2383                         char *outbuf = out_buffer;
2384                         size_t outlen = sizeof(out_buffer);
2386                         size_t ret;
2388                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2389                         if (ret != (size_t) -1) {
2390                                 string = out_buffer;
2391                                 len = sizeof(out_buffer) - outlen;
2392                         }
2393                 }
2395                 waddnstr(view->win, string, len);
2396         }
2397         if (trimmed && use_tilde) {
2398                 set_view_attr(view, LINE_DELIMITER);
2399                 waddch(view->win, '~');
2400                 col++;
2401         }
2403         return col;
2406 static int
2407 draw_space(struct view *view, enum line_type type, int max, int spaces)
2409         static char space[] = "                    ";
2410         int col = 0;
2412         spaces = MIN(max, spaces);
2414         while (spaces > 0) {
2415                 int len = MIN(spaces, sizeof(space) - 1);
2417                 col += draw_chars(view, type, space, len, FALSE);
2418                 spaces -= len;
2419         }
2421         return col;
2424 static bool
2425 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2427         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2428         return view->width + view->yoffset <= view->col;
2431 static bool
2432 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2434         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2435         int max = view->width + view->yoffset - view->col;
2436         int i;
2438         if (max < size)
2439                 size = max;
2441         set_view_attr(view, type);
2442         /* Using waddch() instead of waddnstr() ensures that
2443          * they'll be rendered correctly for the cursor line. */
2444         for (i = skip; i < size; i++)
2445                 waddch(view->win, graphic[i]);
2447         view->col += size;
2448         if (size < max && skip <= size)
2449                 waddch(view->win, ' ');
2450         view->col++;
2452         return view->width + view->yoffset <= view->col;
2455 static bool
2456 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2458         int max = MIN(view->width + view->yoffset - view->col, len);
2459         int col;
2461         if (text)
2462                 col = draw_chars(view, type, text, max - 1, trim);
2463         else
2464                 col = draw_space(view, type, max - 1, max - 1);
2466         view->col += col;
2467         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2468         return view->width + view->yoffset <= view->col;
2471 static bool
2472 draw_date(struct view *view, struct time *time)
2474         const char *date = mkdate(time, opt_date);
2475         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2477         return draw_field(view, LINE_DATE, date, cols, FALSE);
2480 static bool
2481 draw_author(struct view *view, const char *author)
2483         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2484         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2486         if (abbreviate && author)
2487                 author = get_author_initials(author);
2489         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2492 static bool
2493 draw_mode(struct view *view, mode_t mode)
2495         const char *str;
2497         if (S_ISDIR(mode))
2498                 str = "drwxr-xr-x";
2499         else if (S_ISLNK(mode))
2500                 str = "lrwxrwxrwx";
2501         else if (S_ISGITLINK(mode))
2502                 str = "m---------";
2503         else if (S_ISREG(mode) && mode & S_IXUSR)
2504                 str = "-rwxr-xr-x";
2505         else if (S_ISREG(mode))
2506                 str = "-rw-r--r--";
2507         else
2508                 str = "----------";
2510         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2513 static bool
2514 draw_lineno(struct view *view, unsigned int lineno)
2516         char number[10];
2517         int digits3 = view->digits < 3 ? 3 : view->digits;
2518         int max = MIN(view->width + view->yoffset - view->col, digits3);
2519         char *text = NULL;
2520         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2522         lineno += view->offset + 1;
2523         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2524                 static char fmt[] = "%1ld";
2526                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2527                 if (string_format(number, fmt, lineno))
2528                         text = number;
2529         }
2530         if (text)
2531                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2532         else
2533                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2534         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2537 static bool
2538 draw_view_line(struct view *view, unsigned int lineno)
2540         struct line *line;
2541         bool selected = (view->offset + lineno == view->lineno);
2543         assert(view_is_displayed(view));
2545         if (view->offset + lineno >= view->lines)
2546                 return FALSE;
2548         line = &view->line[view->offset + lineno];
2550         wmove(view->win, lineno, 0);
2551         if (line->cleareol)
2552                 wclrtoeol(view->win);
2553         view->col = 0;
2554         view->curline = line;
2555         view->curtype = LINE_NONE;
2556         line->selected = FALSE;
2557         line->dirty = line->cleareol = 0;
2559         if (selected) {
2560                 set_view_attr(view, LINE_CURSOR);
2561                 line->selected = TRUE;
2562                 view->ops->select(view, line);
2563         }
2565         return view->ops->draw(view, line, lineno);
2568 static void
2569 redraw_view_dirty(struct view *view)
2571         bool dirty = FALSE;
2572         int lineno;
2574         for (lineno = 0; lineno < view->height; lineno++) {
2575                 if (view->offset + lineno >= view->lines)
2576                         break;
2577                 if (!view->line[view->offset + lineno].dirty)
2578                         continue;
2579                 dirty = TRUE;
2580                 if (!draw_view_line(view, lineno))
2581                         break;
2582         }
2584         if (!dirty)
2585                 return;
2586         wnoutrefresh(view->win);
2589 static void
2590 redraw_view_from(struct view *view, int lineno)
2592         assert(0 <= lineno && lineno < view->height);
2594         for (; lineno < view->height; lineno++) {
2595                 if (!draw_view_line(view, lineno))
2596                         break;
2597         }
2599         wnoutrefresh(view->win);
2602 static void
2603 redraw_view(struct view *view)
2605         werase(view->win);
2606         redraw_view_from(view, 0);
2610 static void
2611 update_view_title(struct view *view)
2613         char buf[SIZEOF_STR];
2614         char state[SIZEOF_STR];
2615         size_t bufpos = 0, statelen = 0;
2617         assert(view_is_displayed(view));
2619         if (view->type != VIEW_STATUS && view->lines) {
2620                 unsigned int view_lines = view->offset + view->height;
2621                 unsigned int lines = view->lines
2622                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2623                                    : 0;
2625                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2626                                    view->ops->type,
2627                                    view->lineno + 1,
2628                                    view->lines,
2629                                    lines);
2631         }
2633         if (view->pipe) {
2634                 time_t secs = time(NULL) - view->start_time;
2636                 /* Three git seconds are a long time ... */
2637                 if (secs > 2)
2638                         string_format_from(state, &statelen, " loading %lds", secs);
2639         }
2641         string_format_from(buf, &bufpos, "[%s]", view->name);
2642         if (*view->ref && bufpos < view->width) {
2643                 size_t refsize = strlen(view->ref);
2644                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2646                 if (minsize < view->width)
2647                         refsize = view->width - minsize + 7;
2648                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2649         }
2651         if (statelen && bufpos < view->width) {
2652                 string_format_from(buf, &bufpos, "%s", state);
2653         }
2655         if (view == display[current_view])
2656                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2657         else
2658                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2660         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2661         wclrtoeol(view->title);
2662         wnoutrefresh(view->title);
2665 static int
2666 apply_step(double step, int value)
2668         if (step >= 1)
2669                 return (int) step;
2670         value *= step + 0.01;
2671         return value ? value : 1;
2674 static void
2675 resize_display(void)
2677         int offset, i;
2678         struct view *base = display[0];
2679         struct view *view = display[1] ? display[1] : display[0];
2681         /* Setup window dimensions */
2683         getmaxyx(stdscr, base->height, base->width);
2685         /* Make room for the status window. */
2686         base->height -= 1;
2688         if (view != base) {
2689                 /* Horizontal split. */
2690                 view->width   = base->width;
2691                 view->height  = apply_step(opt_scale_split_view, base->height);
2692                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2693                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2694                 base->height -= view->height;
2696                 /* Make room for the title bar. */
2697                 view->height -= 1;
2698         }
2700         /* Make room for the title bar. */
2701         base->height -= 1;
2703         offset = 0;
2705         foreach_displayed_view (view, i) {
2706                 if (!view->win) {
2707                         view->win = newwin(view->height, 0, offset, 0);
2708                         if (!view->win)
2709                                 die("Failed to create %s view", view->name);
2711                         scrollok(view->win, FALSE);
2713                         view->title = newwin(1, 0, offset + view->height, 0);
2714                         if (!view->title)
2715                                 die("Failed to create title window");
2717                 } else {
2718                         wresize(view->win, view->height, view->width);
2719                         mvwin(view->win,   offset, 0);
2720                         mvwin(view->title, offset + view->height, 0);
2721                 }
2723                 offset += view->height + 1;
2724         }
2727 static void
2728 redraw_display(bool clear)
2730         struct view *view;
2731         int i;
2733         foreach_displayed_view (view, i) {
2734                 if (clear)
2735                         wclear(view->win);
2736                 redraw_view(view);
2737                 update_view_title(view);
2738         }
2742 /*
2743  * Option management
2744  */
2746 static void
2747 toggle_enum_option_do(unsigned int *opt, const char *help,
2748                       const struct enum_map *map, size_t size)
2750         *opt = (*opt + 1) % size;
2751         redraw_display(FALSE);
2752         report("Displaying %s %s", enum_name(map[*opt]), help);
2755 #define toggle_enum_option(opt, help, map) \
2756         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2758 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2759 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2761 static void
2762 toggle_view_option(bool *option, const char *help)
2764         *option = !*option;
2765         redraw_display(FALSE);
2766         report("%sabling %s", *option ? "En" : "Dis", help);
2769 static void
2770 open_option_menu(void)
2772         const struct menu_item menu[] = {
2773                 { '.', "line numbers", &opt_line_number },
2774                 { 'D', "date display", &opt_date },
2775                 { 'A', "author display", &opt_author },
2776                 { 'g', "revision graph display", &opt_rev_graph },
2777                 { 'F', "reference display", &opt_show_refs },
2778                 { 0 }
2779         };
2780         int selected = 0;
2782         if (prompt_menu("Toggle option", menu, &selected)) {
2783                 if (menu[selected].data == &opt_date)
2784                         toggle_date();
2785                 else if (menu[selected].data == &opt_author)
2786                         toggle_author();
2787                 else
2788                         toggle_view_option(menu[selected].data, menu[selected].text);
2789         }
2792 static void
2793 maximize_view(struct view *view)
2795         memset(display, 0, sizeof(display));
2796         current_view = 0;
2797         display[current_view] = view;
2798         resize_display();
2799         redraw_display(FALSE);
2800         report("");
2804 /*
2805  * Navigation
2806  */
2808 static bool
2809 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2811         if (lineno >= view->lines)
2812                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2814         if (offset > lineno || offset + view->height <= lineno) {
2815                 unsigned long half = view->height / 2;
2817                 if (lineno > half)
2818                         offset = lineno - half;
2819                 else
2820                         offset = 0;
2821         }
2823         if (offset != view->offset || lineno != view->lineno) {
2824                 view->offset = offset;
2825                 view->lineno = lineno;
2826                 return TRUE;
2827         }
2829         return FALSE;
2832 /* Scrolling backend */
2833 static void
2834 do_scroll_view(struct view *view, int lines)
2836         bool redraw_current_line = FALSE;
2838         /* The rendering expects the new offset. */
2839         view->offset += lines;
2841         assert(0 <= view->offset && view->offset < view->lines);
2842         assert(lines);
2844         /* Move current line into the view. */
2845         if (view->lineno < view->offset) {
2846                 view->lineno = view->offset;
2847                 redraw_current_line = TRUE;
2848         } else if (view->lineno >= view->offset + view->height) {
2849                 view->lineno = view->offset + view->height - 1;
2850                 redraw_current_line = TRUE;
2851         }
2853         assert(view->offset <= view->lineno && view->lineno < view->lines);
2855         /* Redraw the whole screen if scrolling is pointless. */
2856         if (view->height < ABS(lines)) {
2857                 redraw_view(view);
2859         } else {
2860                 int line = lines > 0 ? view->height - lines : 0;
2861                 int end = line + ABS(lines);
2863                 scrollok(view->win, TRUE);
2864                 wscrl(view->win, lines);
2865                 scrollok(view->win, FALSE);
2867                 while (line < end && draw_view_line(view, line))
2868                         line++;
2870                 if (redraw_current_line)
2871                         draw_view_line(view, view->lineno - view->offset);
2872                 wnoutrefresh(view->win);
2873         }
2875         view->has_scrolled = TRUE;
2876         report("");
2879 /* Scroll frontend */
2880 static void
2881 scroll_view(struct view *view, enum request request)
2883         int lines = 1;
2885         assert(view_is_displayed(view));
2887         switch (request) {
2888         case REQ_SCROLL_LEFT:
2889                 if (view->yoffset == 0) {
2890                         report("Cannot scroll beyond the first column");
2891                         return;
2892                 }
2893                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2894                         view->yoffset = 0;
2895                 else
2896                         view->yoffset -= apply_step(opt_hscroll, view->width);
2897                 redraw_view_from(view, 0);
2898                 report("");
2899                 return;
2900         case REQ_SCROLL_RIGHT:
2901                 view->yoffset += apply_step(opt_hscroll, view->width);
2902                 redraw_view(view);
2903                 report("");
2904                 return;
2905         case REQ_SCROLL_PAGE_DOWN:
2906                 lines = view->height;
2907         case REQ_SCROLL_LINE_DOWN:
2908                 if (view->offset + lines > view->lines)
2909                         lines = view->lines - view->offset;
2911                 if (lines == 0 || view->offset + view->height >= view->lines) {
2912                         report("Cannot scroll beyond the last line");
2913                         return;
2914                 }
2915                 break;
2917         case REQ_SCROLL_PAGE_UP:
2918                 lines = view->height;
2919         case REQ_SCROLL_LINE_UP:
2920                 if (lines > view->offset)
2921                         lines = view->offset;
2923                 if (lines == 0) {
2924                         report("Cannot scroll beyond the first line");
2925                         return;
2926                 }
2928                 lines = -lines;
2929                 break;
2931         default:
2932                 die("request %d not handled in switch", request);
2933         }
2935         do_scroll_view(view, lines);
2938 /* Cursor moving */
2939 static void
2940 move_view(struct view *view, enum request request)
2942         int scroll_steps = 0;
2943         int steps;
2945         switch (request) {
2946         case REQ_MOVE_FIRST_LINE:
2947                 steps = -view->lineno;
2948                 break;
2950         case REQ_MOVE_LAST_LINE:
2951                 steps = view->lines - view->lineno - 1;
2952                 break;
2954         case REQ_MOVE_PAGE_UP:
2955                 steps = view->height > view->lineno
2956                       ? -view->lineno : -view->height;
2957                 break;
2959         case REQ_MOVE_PAGE_DOWN:
2960                 steps = view->lineno + view->height >= view->lines
2961                       ? view->lines - view->lineno - 1 : view->height;
2962                 break;
2964         case REQ_MOVE_UP:
2965                 steps = -1;
2966                 break;
2968         case REQ_MOVE_DOWN:
2969                 steps = 1;
2970                 break;
2972         default:
2973                 die("request %d not handled in switch", request);
2974         }
2976         if (steps <= 0 && view->lineno == 0) {
2977                 report("Cannot move beyond the first line");
2978                 return;
2980         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2981                 report("Cannot move beyond the last line");
2982                 return;
2983         }
2985         /* Move the current line */
2986         view->lineno += steps;
2987         assert(0 <= view->lineno && view->lineno < view->lines);
2989         /* Check whether the view needs to be scrolled */
2990         if (view->lineno < view->offset ||
2991             view->lineno >= view->offset + view->height) {
2992                 scroll_steps = steps;
2993                 if (steps < 0 && -steps > view->offset) {
2994                         scroll_steps = -view->offset;
2996                 } else if (steps > 0) {
2997                         if (view->lineno == view->lines - 1 &&
2998                             view->lines > view->height) {
2999                                 scroll_steps = view->lines - view->offset - 1;
3000                                 if (scroll_steps >= view->height)
3001                                         scroll_steps -= view->height - 1;
3002                         }
3003                 }
3004         }
3006         if (!view_is_displayed(view)) {
3007                 view->offset += scroll_steps;
3008                 assert(0 <= view->offset && view->offset < view->lines);
3009                 view->ops->select(view, &view->line[view->lineno]);
3010                 return;
3011         }
3013         /* Repaint the old "current" line if we be scrolling */
3014         if (ABS(steps) < view->height)
3015                 draw_view_line(view, view->lineno - steps - view->offset);
3017         if (scroll_steps) {
3018                 do_scroll_view(view, scroll_steps);
3019                 return;
3020         }
3022         /* Draw the current line */
3023         draw_view_line(view, view->lineno - view->offset);
3025         wnoutrefresh(view->win);
3026         report("");
3030 /*
3031  * Searching
3032  */
3034 static void search_view(struct view *view, enum request request);
3036 static bool
3037 grep_text(struct view *view, const char *text[])
3039         regmatch_t pmatch;
3040         size_t i;
3042         for (i = 0; text[i]; i++)
3043                 if (*text[i] &&
3044                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3045                         return TRUE;
3046         return FALSE;
3049 static void
3050 select_view_line(struct view *view, unsigned long lineno)
3052         unsigned long old_lineno = view->lineno;
3053         unsigned long old_offset = view->offset;
3055         if (goto_view_line(view, view->offset, lineno)) {
3056                 if (view_is_displayed(view)) {
3057                         if (old_offset != view->offset) {
3058                                 redraw_view(view);
3059                         } else {
3060                                 draw_view_line(view, old_lineno - view->offset);
3061                                 draw_view_line(view, view->lineno - view->offset);
3062                                 wnoutrefresh(view->win);
3063                         }
3064                 } else {
3065                         view->ops->select(view, &view->line[view->lineno]);
3066                 }
3067         }
3070 static void
3071 find_next(struct view *view, enum request request)
3073         unsigned long lineno = view->lineno;
3074         int direction;
3076         if (!*view->grep) {
3077                 if (!*opt_search)
3078                         report("No previous search");
3079                 else
3080                         search_view(view, request);
3081                 return;
3082         }
3084         switch (request) {
3085         case REQ_SEARCH:
3086         case REQ_FIND_NEXT:
3087                 direction = 1;
3088                 break;
3090         case REQ_SEARCH_BACK:
3091         case REQ_FIND_PREV:
3092                 direction = -1;
3093                 break;
3095         default:
3096                 return;
3097         }
3099         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3100                 lineno += direction;
3102         /* Note, lineno is unsigned long so will wrap around in which case it
3103          * will become bigger than view->lines. */
3104         for (; lineno < view->lines; lineno += direction) {
3105                 if (view->ops->grep(view, &view->line[lineno])) {
3106                         select_view_line(view, lineno);
3107                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3108                         return;
3109                 }
3110         }
3112         report("No match found for '%s'", view->grep);
3115 static void
3116 search_view(struct view *view, enum request request)
3118         int regex_err;
3120         if (view->regex) {
3121                 regfree(view->regex);
3122                 *view->grep = 0;
3123         } else {
3124                 view->regex = calloc(1, sizeof(*view->regex));
3125                 if (!view->regex)
3126                         return;
3127         }
3129         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3130         if (regex_err != 0) {
3131                 char buf[SIZEOF_STR] = "unknown error";
3133                 regerror(regex_err, view->regex, buf, sizeof(buf));
3134                 report("Search failed: %s", buf);
3135                 return;
3136         }
3138         string_copy(view->grep, opt_search);
3140         find_next(view, request);
3143 /*
3144  * Incremental updating
3145  */
3147 static void
3148 reset_view(struct view *view)
3150         int i;
3152         for (i = 0; i < view->lines; i++)
3153                 free(view->line[i].data);
3154         free(view->line);
3156         view->p_offset = view->offset;
3157         view->p_yoffset = view->yoffset;
3158         view->p_lineno = view->lineno;
3160         view->line = NULL;
3161         view->offset = 0;
3162         view->yoffset = 0;
3163         view->lines  = 0;
3164         view->lineno = 0;
3165         view->vid[0] = 0;
3166         view->update_secs = 0;
3169 static const char *
3170 format_arg(const char *name)
3172         static struct {
3173                 const char *name;
3174                 size_t namelen;
3175                 const char *value;
3176                 const char *value_if_empty;
3177         } vars[] = {
3178 #define FORMAT_VAR(name, value, value_if_empty) \
3179         { name, STRING_SIZE(name), value, value_if_empty }
3180                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3181                 FORMAT_VAR("%(file)",           opt_file,       ""),
3182                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3183                 FORMAT_VAR("%(head)",           ref_head,       ""),
3184                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3185                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3186                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3187         };
3188         int i;
3190         for (i = 0; i < ARRAY_SIZE(vars); i++)
3191                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3192                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3194         report("Unknown replacement: `%s`", name);
3195         return NULL;
3198 static bool
3199 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3201         char buf[SIZEOF_STR];
3202         int argc;
3203         bool noreplace = flags == FORMAT_NONE;
3205         argv_free(dst_argv);
3207         for (argc = 0; src_argv[argc]; argc++) {
3208                 const char *arg = src_argv[argc];
3209                 size_t bufpos = 0;
3211                 while (arg) {
3212                         char *next = strstr(arg, "%(");
3213                         int len = next - arg;
3214                         const char *value;
3216                         if (!next || noreplace) {
3217                                 len = strlen(arg);
3218                                 value = "";
3220                         } else {
3221                                 value = format_arg(next);
3223                                 if (!value) {
3224                                         return FALSE;
3225                                 }
3226                         }
3228                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3229                                 return FALSE;
3231                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3232                 }
3234                 dst_argv[argc] = strdup(buf);
3235                 if (!dst_argv[argc])
3236                         break;
3237         }
3239         dst_argv[argc] = NULL;
3241         return src_argv[argc] == NULL;
3244 static bool
3245 restore_view_position(struct view *view)
3247         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3248                 return FALSE;
3250         /* Changing the view position cancels the restoring. */
3251         /* FIXME: Changing back to the first line is not detected. */
3252         if (view->offset != 0 || view->lineno != 0) {
3253                 view->p_restore = FALSE;
3254                 return FALSE;
3255         }
3257         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3258             view_is_displayed(view))
3259                 werase(view->win);
3261         view->yoffset = view->p_yoffset;
3262         view->p_restore = FALSE;
3264         return TRUE;
3267 static void
3268 end_update(struct view *view, bool force)
3270         if (!view->pipe)
3271                 return;
3272         while (!view->ops->read(view, NULL))
3273                 if (!force)
3274                         return;
3275         if (force)
3276                 io_kill(view->pipe);
3277         io_done(view->pipe);
3278         view->pipe = NULL;
3281 static void
3282 setup_update(struct view *view, const char *vid)
3284         reset_view(view);
3285         string_copy_rev(view->vid, vid);
3286         view->pipe = &view->io;
3287         view->start_time = time(NULL);
3290 static bool
3291 prepare_update(struct view *view, const char *argv[], const char *dir)
3293         if (view->pipe)
3294                 end_update(view, TRUE);
3295         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3298 static bool
3299 prepare_update_file(struct view *view, const char *name)
3301         if (view->pipe)
3302                 end_update(view, TRUE);
3303         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3306 static bool
3307 begin_update(struct view *view, bool refresh)
3309         if (view->pipe)
3310                 end_update(view, TRUE);
3312         if (!refresh) {
3313                 if (view->ops->prepare) {
3314                         if (!view->ops->prepare(view))
3315                                 return FALSE;
3316                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3317                         return FALSE;
3318                 }
3320                 /* Put the current ref_* value to the view title ref
3321                  * member. This is needed by the blob view. Most other
3322                  * views sets it automatically after loading because the
3323                  * first line is a commit line. */
3324                 string_copy_rev(view->ref, view->id);
3325         }
3327         if (!io_start(&view->io))
3328                 return FALSE;
3330         setup_update(view, view->id);
3332         return TRUE;
3335 static bool
3336 update_view(struct view *view)
3338         char out_buffer[BUFSIZ * 2];
3339         char *line;
3340         /* Clear the view and redraw everything since the tree sorting
3341          * might have rearranged things. */
3342         bool redraw = view->lines == 0;
3343         bool can_read = TRUE;
3345         if (!view->pipe)
3346                 return TRUE;
3348         if (!io_can_read(view->pipe)) {
3349                 if (view->lines == 0 && view_is_displayed(view)) {
3350                         time_t secs = time(NULL) - view->start_time;
3352                         if (secs > 1 && secs > view->update_secs) {
3353                                 if (view->update_secs == 0)
3354                                         redraw_view(view);
3355                                 update_view_title(view);
3356                                 view->update_secs = secs;
3357                         }
3358                 }
3359                 return TRUE;
3360         }
3362         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3363                 if (opt_iconv_in != ICONV_NONE) {
3364                         ICONV_CONST char *inbuf = line;
3365                         size_t inlen = strlen(line) + 1;
3367                         char *outbuf = out_buffer;
3368                         size_t outlen = sizeof(out_buffer);
3370                         size_t ret;
3372                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3373                         if (ret != (size_t) -1)
3374                                 line = out_buffer;
3375                 }
3377                 if (!view->ops->read(view, line)) {
3378                         report("Allocation failure");
3379                         end_update(view, TRUE);
3380                         return FALSE;
3381                 }
3382         }
3384         {
3385                 unsigned long lines = view->lines;
3386                 int digits;
3388                 for (digits = 0; lines; digits++)
3389                         lines /= 10;
3391                 /* Keep the displayed view in sync with line number scaling. */
3392                 if (digits != view->digits) {
3393                         view->digits = digits;
3394                         if (opt_line_number || view->type == VIEW_BLAME)
3395                                 redraw = TRUE;
3396                 }
3397         }
3399         if (io_error(view->pipe)) {
3400                 report("Failed to read: %s", io_strerror(view->pipe));
3401                 end_update(view, TRUE);
3403         } else if (io_eof(view->pipe)) {
3404                 if (view_is_displayed(view))
3405                         report("");
3406                 end_update(view, FALSE);
3407         }
3409         if (restore_view_position(view))
3410                 redraw = TRUE;
3412         if (!view_is_displayed(view))
3413                 return TRUE;
3415         if (redraw)
3416                 redraw_view_from(view, 0);
3417         else
3418                 redraw_view_dirty(view);
3420         /* Update the title _after_ the redraw so that if the redraw picks up a
3421          * commit reference in view->ref it'll be available here. */
3422         update_view_title(view);
3423         return TRUE;
3426 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3428 static struct line *
3429 add_line_data(struct view *view, void *data, enum line_type type)
3431         struct line *line;
3433         if (!realloc_lines(&view->line, view->lines, 1))
3434                 return NULL;
3436         line = &view->line[view->lines++];
3437         memset(line, 0, sizeof(*line));
3438         line->type = type;
3439         line->data = data;
3440         line->dirty = 1;
3442         return line;
3445 static struct line *
3446 add_line_text(struct view *view, const char *text, enum line_type type)
3448         char *data = text ? strdup(text) : NULL;
3450         return data ? add_line_data(view, data, type) : NULL;
3453 static struct line *
3454 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3456         char buf[SIZEOF_STR];
3457         va_list args;
3459         va_start(args, fmt);
3460         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3461                 buf[0] = 0;
3462         va_end(args);
3464         return buf[0] ? add_line_text(view, buf, type) : NULL;
3467 /*
3468  * View opening
3469  */
3471 enum open_flags {
3472         OPEN_DEFAULT = 0,       /* Use default view switching. */
3473         OPEN_SPLIT = 1,         /* Split current view. */
3474         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3475         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3476         OPEN_PREPARED = 32,     /* Open already prepared command. */
3477 };
3479 static void
3480 open_view(struct view *prev, enum request request, enum open_flags flags)
3482         bool split = !!(flags & OPEN_SPLIT);
3483         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3484         bool nomaximize = !!(flags & OPEN_REFRESH);
3485         struct view *view = VIEW(request);
3486         int nviews = displayed_views();
3487         struct view *base_view = display[0];
3489         if (view == prev && nviews == 1 && !reload) {
3490                 report("Already in %s view", view->name);
3491                 return;
3492         }
3494         if (view->git_dir && !opt_git_dir[0]) {
3495                 report("The %s view is disabled in pager view", view->name);
3496                 return;
3497         }
3499         if (split) {
3500                 display[1] = view;
3501                 current_view = 1;
3502                 view->parent = prev;
3503         } else if (!nomaximize) {
3504                 /* Maximize the current view. */
3505                 memset(display, 0, sizeof(display));
3506                 current_view = 0;
3507                 display[current_view] = view;
3508         }
3510         /* No prev signals that this is the first loaded view. */
3511         if (prev && view != prev) {
3512                 view->prev = prev;
3513         }
3515         /* Resize the view when switching between split- and full-screen,
3516          * or when switching between two different full-screen views. */
3517         if (nviews != displayed_views() ||
3518             (nviews == 1 && base_view != display[0]))
3519                 resize_display();
3521         if (view->ops->open) {
3522                 if (view->pipe)
3523                         end_update(view, TRUE);
3524                 if (!view->ops->open(view)) {
3525                         report("Failed to load %s view", view->name);
3526                         return;
3527                 }
3528                 restore_view_position(view);
3530         } else if ((reload || strcmp(view->vid, view->id)) &&
3531                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3532                 report("Failed to load %s view", view->name);
3533                 return;
3534         }
3536         if (split && prev->lineno - prev->offset >= prev->height) {
3537                 /* Take the title line into account. */
3538                 int lines = prev->lineno - prev->offset - prev->height + 1;
3540                 /* Scroll the view that was split if the current line is
3541                  * outside the new limited view. */
3542                 do_scroll_view(prev, lines);
3543         }
3545         if (prev && view != prev && split && view_is_displayed(prev)) {
3546                 /* "Blur" the previous view. */
3547                 update_view_title(prev);
3548         }
3550         if (view->pipe && view->lines == 0) {
3551                 /* Clear the old view and let the incremental updating refill
3552                  * the screen. */
3553                 werase(view->win);
3554                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3555                 report("");
3556         } else if (view_is_displayed(view)) {
3557                 redraw_view(view);
3558                 report("");
3559         }
3562 static void
3563 open_external_viewer(const char *argv[], const char *dir)
3565         def_prog_mode();           /* save current tty modes */
3566         endwin();                  /* restore original tty modes */
3567         io_run_fg(argv, dir);
3568         fprintf(stderr, "Press Enter to continue");
3569         getc(opt_tty);
3570         reset_prog_mode();
3571         redraw_display(TRUE);
3574 static void
3575 open_mergetool(const char *file)
3577         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3579         open_external_viewer(mergetool_argv, opt_cdup);
3582 static void
3583 open_editor(const char *file)
3585         const char *editor_argv[] = { "vi", file, NULL };
3586         const char *editor;
3588         editor = getenv("GIT_EDITOR");
3589         if (!editor && *opt_editor)
3590                 editor = opt_editor;
3591         if (!editor)
3592                 editor = getenv("VISUAL");
3593         if (!editor)
3594                 editor = getenv("EDITOR");
3595         if (!editor)
3596                 editor = "vi";
3598         editor_argv[0] = editor;
3599         open_external_viewer(editor_argv, opt_cdup);
3602 static void
3603 open_run_request(enum request request)
3605         struct run_request *req = get_run_request(request);
3606         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3608         if (!req) {
3609                 report("Unknown run request");
3610                 return;
3611         }
3613         if (format_argv(argv, req->argv, FORMAT_ALL))
3614                 open_external_viewer(argv, NULL);
3615         argv_free(argv);
3618 /*
3619  * User request switch noodle
3620  */
3622 static int
3623 view_driver(struct view *view, enum request request)
3625         int i;
3627         if (request == REQ_NONE)
3628                 return TRUE;
3630         if (request > REQ_NONE) {
3631                 open_run_request(request);
3632                 view_request(view, REQ_REFRESH);
3633                 return TRUE;
3634         }
3636         request = view_request(view, request);
3637         if (request == REQ_NONE)
3638                 return TRUE;
3640         switch (request) {
3641         case REQ_MOVE_UP:
3642         case REQ_MOVE_DOWN:
3643         case REQ_MOVE_PAGE_UP:
3644         case REQ_MOVE_PAGE_DOWN:
3645         case REQ_MOVE_FIRST_LINE:
3646         case REQ_MOVE_LAST_LINE:
3647                 move_view(view, request);
3648                 break;
3650         case REQ_SCROLL_LEFT:
3651         case REQ_SCROLL_RIGHT:
3652         case REQ_SCROLL_LINE_DOWN:
3653         case REQ_SCROLL_LINE_UP:
3654         case REQ_SCROLL_PAGE_DOWN:
3655         case REQ_SCROLL_PAGE_UP:
3656                 scroll_view(view, request);
3657                 break;
3659         case REQ_VIEW_BLAME:
3660                 if (!opt_file[0]) {
3661                         report("No file chosen, press %s to open tree view",
3662                                get_key(view->keymap, REQ_VIEW_TREE));
3663                         break;
3664                 }
3665                 open_view(view, request, OPEN_DEFAULT);
3666                 break;
3668         case REQ_VIEW_BLOB:
3669                 if (!ref_blob[0]) {
3670                         report("No file chosen, press %s to open tree view",
3671                                get_key(view->keymap, REQ_VIEW_TREE));
3672                         break;
3673                 }
3674                 open_view(view, request, OPEN_DEFAULT);
3675                 break;
3677         case REQ_VIEW_PAGER:
3678                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3679                         report("No pager content, press %s to run command from prompt",
3680                                get_key(view->keymap, REQ_PROMPT));
3681                         break;
3682                 }
3683                 open_view(view, request, OPEN_DEFAULT);
3684                 break;
3686         case REQ_VIEW_STAGE:
3687                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3688                         report("No stage content, press %s to open the status view and choose file",
3689                                get_key(view->keymap, REQ_VIEW_STATUS));
3690                         break;
3691                 }
3692                 open_view(view, request, OPEN_DEFAULT);
3693                 break;
3695         case REQ_VIEW_STATUS:
3696                 if (opt_is_inside_work_tree == FALSE) {
3697                         report("The status view requires a working tree");
3698                         break;
3699                 }
3700                 open_view(view, request, OPEN_DEFAULT);
3701                 break;
3703         case REQ_VIEW_MAIN:
3704         case REQ_VIEW_DIFF:
3705         case REQ_VIEW_LOG:
3706         case REQ_VIEW_TREE:
3707         case REQ_VIEW_HELP:
3708         case REQ_VIEW_BRANCH:
3709                 open_view(view, request, OPEN_DEFAULT);
3710                 break;
3712         case REQ_NEXT:
3713         case REQ_PREVIOUS:
3714                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3716                 if (view->parent) {
3717                         int line;
3719                         view = view->parent;
3720                         line = view->lineno;
3721                         move_view(view, request);
3722                         if (view_is_displayed(view))
3723                                 update_view_title(view);
3724                         if (line != view->lineno)
3725                                 view_request(view, REQ_ENTER);
3726                 } else {
3727                         move_view(view, request);
3728                 }
3729                 break;
3731         case REQ_VIEW_NEXT:
3732         {
3733                 int nviews = displayed_views();
3734                 int next_view = (current_view + 1) % nviews;
3736                 if (next_view == current_view) {
3737                         report("Only one view is displayed");
3738                         break;
3739                 }
3741                 current_view = next_view;
3742                 /* Blur out the title of the previous view. */
3743                 update_view_title(view);
3744                 report("");
3745                 break;
3746         }
3747         case REQ_REFRESH:
3748                 report("Refreshing is not yet supported for the %s view", view->name);
3749                 break;
3751         case REQ_MAXIMIZE:
3752                 if (displayed_views() == 2)
3753                         maximize_view(view);
3754                 break;
3756         case REQ_OPTIONS:
3757                 open_option_menu();
3758                 break;
3760         case REQ_TOGGLE_LINENO:
3761                 toggle_view_option(&opt_line_number, "line numbers");
3762                 break;
3764         case REQ_TOGGLE_DATE:
3765                 toggle_date();
3766                 break;
3768         case REQ_TOGGLE_AUTHOR:
3769                 toggle_author();
3770                 break;
3772         case REQ_TOGGLE_REV_GRAPH:
3773                 toggle_view_option(&opt_rev_graph, "revision graph display");
3774                 break;
3776         case REQ_TOGGLE_REFS:
3777                 toggle_view_option(&opt_show_refs, "reference display");
3778                 break;
3780         case REQ_TOGGLE_SORT_FIELD:
3781         case REQ_TOGGLE_SORT_ORDER:
3782                 report("Sorting is not yet supported for the %s view", view->name);
3783                 break;
3785         case REQ_SEARCH:
3786         case REQ_SEARCH_BACK:
3787                 search_view(view, request);
3788                 break;
3790         case REQ_FIND_NEXT:
3791         case REQ_FIND_PREV:
3792                 find_next(view, request);
3793                 break;
3795         case REQ_STOP_LOADING:
3796                 foreach_view(view, i) {
3797                         if (view->pipe)
3798                                 report("Stopped loading the %s view", view->name),
3799                         end_update(view, TRUE);
3800                 }
3801                 break;
3803         case REQ_SHOW_VERSION:
3804                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3805                 return TRUE;
3807         case REQ_SCREEN_REDRAW:
3808                 redraw_display(TRUE);
3809                 break;
3811         case REQ_EDIT:
3812                 report("Nothing to edit");
3813                 break;
3815         case REQ_ENTER:
3816                 report("Nothing to enter");
3817                 break;
3819         case REQ_VIEW_CLOSE:
3820                 /* XXX: Mark closed views by letting view->prev point to the
3821                  * view itself. Parents to closed view should never be
3822                  * followed. */
3823                 if (view->prev && view->prev != view) {
3824                         maximize_view(view->prev);
3825                         view->prev = view;
3826                         break;
3827                 }
3828                 /* Fall-through */
3829         case REQ_QUIT:
3830                 return FALSE;
3832         default:
3833                 report("Unknown key, press %s for help",
3834                        get_key(view->keymap, REQ_VIEW_HELP));
3835                 return TRUE;
3836         }
3838         return TRUE;
3842 /*
3843  * View backend utilities
3844  */
3846 enum sort_field {
3847         ORDERBY_NAME,
3848         ORDERBY_DATE,
3849         ORDERBY_AUTHOR,
3850 };
3852 struct sort_state {
3853         const enum sort_field *fields;
3854         size_t size, current;
3855         bool reverse;
3856 };
3858 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3859 #define get_sort_field(state) ((state).fields[(state).current])
3860 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3862 static void
3863 sort_view(struct view *view, enum request request, struct sort_state *state,
3864           int (*compare)(const void *, const void *))
3866         switch (request) {
3867         case REQ_TOGGLE_SORT_FIELD:
3868                 state->current = (state->current + 1) % state->size;
3869                 break;
3871         case REQ_TOGGLE_SORT_ORDER:
3872                 state->reverse = !state->reverse;
3873                 break;
3874         default:
3875                 die("Not a sort request");
3876         }
3878         qsort(view->line, view->lines, sizeof(*view->line), compare);
3879         redraw_view(view);
3882 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3884 /* Small author cache to reduce memory consumption. It uses binary
3885  * search to lookup or find place to position new entries. No entries
3886  * are ever freed. */
3887 static const char *
3888 get_author(const char *name)
3890         static const char **authors;
3891         static size_t authors_size;
3892         int from = 0, to = authors_size - 1;
3894         while (from <= to) {
3895                 size_t pos = (to + from) / 2;
3896                 int cmp = strcmp(name, authors[pos]);
3898                 if (!cmp)
3899                         return authors[pos];
3901                 if (cmp < 0)
3902                         to = pos - 1;
3903                 else
3904                         from = pos + 1;
3905         }
3907         if (!realloc_authors(&authors, authors_size, 1))
3908                 return NULL;
3909         name = strdup(name);
3910         if (!name)
3911                 return NULL;
3913         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3914         authors[from] = name;
3915         authors_size++;
3917         return name;
3920 static void
3921 parse_timesec(struct time *time, const char *sec)
3923         time->sec = (time_t) atol(sec);
3926 static void
3927 parse_timezone(struct time *time, const char *zone)
3929         long tz;
3931         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3932         tz += ('0' - zone[2]) * 60 * 60;
3933         tz += ('0' - zone[3]) * 60 * 10;
3934         tz += ('0' - zone[4]) * 60;
3936         if (zone[0] == '-')
3937                 tz = -tz;
3939         time->tz = tz;
3940         time->sec -= tz;
3943 /* Parse author lines where the name may be empty:
3944  *      author  <email@address.tld> 1138474660 +0100
3945  */
3946 static void
3947 parse_author_line(char *ident, const char **author, struct time *time)
3949         char *nameend = strchr(ident, '<');
3950         char *emailend = strchr(ident, '>');
3952         if (nameend && emailend)
3953                 *nameend = *emailend = 0;
3954         ident = chomp_string(ident);
3955         if (!*ident) {
3956                 if (nameend)
3957                         ident = chomp_string(nameend + 1);
3958                 if (!*ident)
3959                         ident = "Unknown";
3960         }
3962         *author = get_author(ident);
3964         /* Parse epoch and timezone */
3965         if (emailend && emailend[1] == ' ') {
3966                 char *secs = emailend + 2;
3967                 char *zone = strchr(secs, ' ');
3969                 parse_timesec(time, secs);
3971                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3972                         parse_timezone(time, zone + 1);
3973         }
3976 static bool
3977 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3979         char rev[SIZEOF_REV];
3980         const char *revlist_argv[] = {
3981                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3982         };
3983         struct menu_item *items;
3984         char text[SIZEOF_STR];
3985         bool ok = TRUE;
3986         int i;
3988         items = calloc(*parents + 1, sizeof(*items));
3989         if (!items)
3990                 return FALSE;
3992         for (i = 0; i < *parents; i++) {
3993                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3994                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3995                     !(items[i].text = strdup(text))) {
3996                         ok = FALSE;
3997                         break;
3998                 }
3999         }
4001         if (ok) {
4002                 *parents = 0;
4003                 ok = prompt_menu("Select parent", items, parents);
4004         }
4005         for (i = 0; items[i].text; i++)
4006                 free((char *) items[i].text);
4007         free(items);
4008         return ok;
4011 static bool
4012 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4014         char buf[SIZEOF_STR * 4];
4015         const char *revlist_argv[] = {
4016                 "git", "log", "--no-color", "-1",
4017                         "--pretty=format:%P", id, "--", path, NULL
4018         };
4019         int parents;
4021         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4022             (parents = strlen(buf) / 40) < 0) {
4023                 report("Failed to get parent information");
4024                 return FALSE;
4026         } else if (parents == 0) {
4027                 if (path)
4028                         report("Path '%s' does not exist in the parent", path);
4029                 else
4030                         report("The selected commit has no parents");
4031                 return FALSE;
4032         }
4034         if (parents == 1)
4035                 parents = 0;
4036         else if (!open_commit_parent_menu(buf, &parents))
4037                 return FALSE;
4039         string_copy_rev(rev, &buf[41 * parents]);
4040         return TRUE;
4043 /*
4044  * Pager backend
4045  */
4047 static bool
4048 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4050         char text[SIZEOF_STR];
4052         if (opt_line_number && draw_lineno(view, lineno))
4053                 return TRUE;
4055         string_expand(text, sizeof(text), line->data, opt_tab_size);
4056         draw_text(view, line->type, text, TRUE);
4057         return TRUE;
4060 static bool
4061 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4063         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4064         char ref[SIZEOF_STR];
4066         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4067                 return TRUE;
4069         /* This is the only fatal call, since it can "corrupt" the buffer. */
4070         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4071                 return FALSE;
4073         return TRUE;
4076 static void
4077 add_pager_refs(struct view *view, struct line *line)
4079         char buf[SIZEOF_STR];
4080         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4081         struct ref_list *list;
4082         size_t bufpos = 0, i;
4083         const char *sep = "Refs: ";
4084         bool is_tag = FALSE;
4086         assert(line->type == LINE_COMMIT);
4088         list = get_ref_list(commit_id);
4089         if (!list) {
4090                 if (view->type == VIEW_DIFF)
4091                         goto try_add_describe_ref;
4092                 return;
4093         }
4095         for (i = 0; i < list->size; i++) {
4096                 struct ref *ref = list->refs[i];
4097                 const char *fmt = ref->tag    ? "%s[%s]" :
4098                                   ref->remote ? "%s<%s>" : "%s%s";
4100                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4101                         return;
4102                 sep = ", ";
4103                 if (ref->tag)
4104                         is_tag = TRUE;
4105         }
4107         if (!is_tag && view->type == VIEW_DIFF) {
4108 try_add_describe_ref:
4109                 /* Add <tag>-g<commit_id> "fake" reference. */
4110                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4111                         return;
4112         }
4114         if (bufpos == 0)
4115                 return;
4117         add_line_text(view, buf, LINE_PP_REFS);
4120 static bool
4121 pager_read(struct view *view, char *data)
4123         struct line *line;
4125         if (!data)
4126                 return TRUE;
4128         line = add_line_text(view, data, get_line_type(data));
4129         if (!line)
4130                 return FALSE;
4132         if (line->type == LINE_COMMIT &&
4133             (view->type == VIEW_DIFF ||
4134              view->type == VIEW_LOG))
4135                 add_pager_refs(view, line);
4137         return TRUE;
4140 static enum request
4141 pager_request(struct view *view, enum request request, struct line *line)
4143         int split = 0;
4145         if (request != REQ_ENTER)
4146                 return request;
4148         if (line->type == LINE_COMMIT &&
4149            (view->type == VIEW_LOG ||
4150             view->type == VIEW_PAGER)) {
4151                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4152                 split = 1;
4153         }
4155         /* Always scroll the view even if it was split. That way
4156          * you can use Enter to scroll through the log view and
4157          * split open each commit diff. */
4158         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4160         /* FIXME: A minor workaround. Scrolling the view will call report("")
4161          * but if we are scrolling a non-current view this won't properly
4162          * update the view title. */
4163         if (split)
4164                 update_view_title(view);
4166         return REQ_NONE;
4169 static bool
4170 pager_grep(struct view *view, struct line *line)
4172         const char *text[] = { line->data, NULL };
4174         return grep_text(view, text);
4177 static void
4178 pager_select(struct view *view, struct line *line)
4180         if (line->type == LINE_COMMIT) {
4181                 char *text = (char *)line->data + STRING_SIZE("commit ");
4183                 if (view->type != VIEW_PAGER)
4184                         string_copy_rev(view->ref, text);
4185                 string_copy_rev(ref_commit, text);
4186         }
4189 static struct view_ops pager_ops = {
4190         "line",
4191         NULL,
4192         NULL,
4193         pager_read,
4194         pager_draw,
4195         pager_request,
4196         pager_grep,
4197         pager_select,
4198 };
4200 static const char *log_argv[SIZEOF_ARG] = {
4201         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4202 };
4204 static enum request
4205 log_request(struct view *view, enum request request, struct line *line)
4207         switch (request) {
4208         case REQ_REFRESH:
4209                 load_refs();
4210                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4211                 return REQ_NONE;
4212         default:
4213                 return pager_request(view, request, line);
4214         }
4217 static struct view_ops log_ops = {
4218         "line",
4219         log_argv,
4220         NULL,
4221         pager_read,
4222         pager_draw,
4223         log_request,
4224         pager_grep,
4225         pager_select,
4226 };
4228 static const char *diff_argv[SIZEOF_ARG] = {
4229         "git", "show", "--pretty=fuller", "--no-color", "--root",
4230                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4231 };
4233 static struct view_ops diff_ops = {
4234         "line",
4235         diff_argv,
4236         NULL,
4237         pager_read,
4238         pager_draw,
4239         pager_request,
4240         pager_grep,
4241         pager_select,
4242 };
4244 /*
4245  * Help backend
4246  */
4248 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4250 static bool
4251 help_open_keymap_title(struct view *view, enum keymap keymap)
4253         struct line *line;
4255         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4256                                help_keymap_hidden[keymap] ? '+' : '-',
4257                                enum_name(keymap_table[keymap]));
4258         if (line)
4259                 line->other = keymap;
4261         return help_keymap_hidden[keymap];
4264 static void
4265 help_open_keymap(struct view *view, enum keymap keymap)
4267         const char *group = NULL;
4268         char buf[SIZEOF_STR];
4269         size_t bufpos;
4270         bool add_title = TRUE;
4271         int i;
4273         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4274                 const char *key = NULL;
4276                 if (req_info[i].request == REQ_NONE)
4277                         continue;
4279                 if (!req_info[i].request) {
4280                         group = req_info[i].help;
4281                         continue;
4282                 }
4284                 key = get_keys(keymap, req_info[i].request, TRUE);
4285                 if (!key || !*key)
4286                         continue;
4288                 if (add_title && help_open_keymap_title(view, keymap))
4289                         return;
4290                 add_title = FALSE;
4292                 if (group) {
4293                         add_line_text(view, group, LINE_HELP_GROUP);
4294                         group = NULL;
4295                 }
4297                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4298                                 enum_name(req_info[i]), req_info[i].help);
4299         }
4301         group = "External commands:";
4303         for (i = 0; i < run_requests; i++) {
4304                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4305                 const char *key;
4306                 int argc;
4308                 if (!req || req->keymap != keymap)
4309                         continue;
4311                 key = get_key_name(req->key);
4312                 if (!*key)
4313                         key = "(no key defined)";
4315                 if (add_title && help_open_keymap_title(view, keymap))
4316                         return;
4317                 if (group) {
4318                         add_line_text(view, group, LINE_HELP_GROUP);
4319                         group = NULL;
4320                 }
4322                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4323                         if (!string_format_from(buf, &bufpos, "%s%s",
4324                                                 argc ? " " : "", req->argv[argc]))
4325                                 return;
4327                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4328         }
4331 static bool
4332 help_open(struct view *view)
4334         enum keymap keymap;
4336         reset_view(view);
4337         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4338         add_line_text(view, "", LINE_DEFAULT);
4340         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4341                 help_open_keymap(view, keymap);
4343         return TRUE;
4346 static enum request
4347 help_request(struct view *view, enum request request, struct line *line)
4349         switch (request) {
4350         case REQ_ENTER:
4351                 if (line->type == LINE_HELP_KEYMAP) {
4352                         help_keymap_hidden[line->other] =
4353                                 !help_keymap_hidden[line->other];
4354                         view->p_restore = TRUE;
4355                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4356                 }
4358                 return REQ_NONE;
4359         default:
4360                 return pager_request(view, request, line);
4361         }
4364 static struct view_ops help_ops = {
4365         "line",
4366         NULL,
4367         help_open,
4368         NULL,
4369         pager_draw,
4370         help_request,
4371         pager_grep,
4372         pager_select,
4373 };
4376 /*
4377  * Tree backend
4378  */
4380 struct tree_stack_entry {
4381         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4382         unsigned long lineno;           /* Line number to restore */
4383         char *name;                     /* Position of name in opt_path */
4384 };
4386 /* The top of the path stack. */
4387 static struct tree_stack_entry *tree_stack = NULL;
4388 unsigned long tree_lineno = 0;
4390 static void
4391 pop_tree_stack_entry(void)
4393         struct tree_stack_entry *entry = tree_stack;
4395         tree_lineno = entry->lineno;
4396         entry->name[0] = 0;
4397         tree_stack = entry->prev;
4398         free(entry);
4401 static void
4402 push_tree_stack_entry(const char *name, unsigned long lineno)
4404         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4405         size_t pathlen = strlen(opt_path);
4407         if (!entry)
4408                 return;
4410         entry->prev = tree_stack;
4411         entry->name = opt_path + pathlen;
4412         tree_stack = entry;
4414         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4415                 pop_tree_stack_entry();
4416                 return;
4417         }
4419         /* Move the current line to the first tree entry. */
4420         tree_lineno = 1;
4421         entry->lineno = lineno;
4424 /* Parse output from git-ls-tree(1):
4425  *
4426  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4427  */
4429 #define SIZEOF_TREE_ATTR \
4430         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4432 #define SIZEOF_TREE_MODE \
4433         STRING_SIZE("100644 ")
4435 #define TREE_ID_OFFSET \
4436         STRING_SIZE("100644 blob ")
4438 struct tree_entry {
4439         char id[SIZEOF_REV];
4440         mode_t mode;
4441         struct time time;               /* Date from the author ident. */
4442         const char *author;             /* Author of the commit. */
4443         char name[1];
4444 };
4446 static const char *
4447 tree_path(const struct line *line)
4449         return ((struct tree_entry *) line->data)->name;
4452 static int
4453 tree_compare_entry(const struct line *line1, const struct line *line2)
4455         if (line1->type != line2->type)
4456                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4457         return strcmp(tree_path(line1), tree_path(line2));
4460 static const enum sort_field tree_sort_fields[] = {
4461         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4462 };
4463 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4465 static int
4466 tree_compare(const void *l1, const void *l2)
4468         const struct line *line1 = (const struct line *) l1;
4469         const struct line *line2 = (const struct line *) l2;
4470         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4471         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4473         if (line1->type == LINE_TREE_HEAD)
4474                 return -1;
4475         if (line2->type == LINE_TREE_HEAD)
4476                 return 1;
4478         switch (get_sort_field(tree_sort_state)) {
4479         case ORDERBY_DATE:
4480                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4482         case ORDERBY_AUTHOR:
4483                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4485         case ORDERBY_NAME:
4486         default:
4487                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4488         }
4492 static struct line *
4493 tree_entry(struct view *view, enum line_type type, const char *path,
4494            const char *mode, const char *id)
4496         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4497         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4499         if (!entry || !line) {
4500                 free(entry);
4501                 return NULL;
4502         }
4504         strncpy(entry->name, path, strlen(path));
4505         if (mode)
4506                 entry->mode = strtoul(mode, NULL, 8);
4507         if (id)
4508                 string_copy_rev(entry->id, id);
4510         return line;
4513 static bool
4514 tree_read_date(struct view *view, char *text, bool *read_date)
4516         static const char *author_name;
4517         static struct time author_time;
4519         if (!text && *read_date) {
4520                 *read_date = FALSE;
4521                 return TRUE;
4523         } else if (!text) {
4524                 char *path = *opt_path ? opt_path : ".";
4525                 /* Find next entry to process */
4526                 const char *log_file[] = {
4527                         "git", "log", "--no-color", "--pretty=raw",
4528                                 "--cc", "--raw", view->id, "--", path, NULL
4529                 };
4530                 struct io io = {};
4532                 if (!view->lines) {
4533                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4534                         report("Tree is empty");
4535                         return TRUE;
4536                 }
4538                 if (!io_run_rd(&io, log_file, opt_cdup)) {
4539                         report("Failed to load tree data");
4540                         return TRUE;
4541                 }
4543                 io_done(view->pipe);
4544                 view->io = io;
4545                 *read_date = TRUE;
4546                 return FALSE;
4548         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4549                 parse_author_line(text + STRING_SIZE("author "),
4550                                   &author_name, &author_time);
4552         } else if (*text == ':') {
4553                 char *pos;
4554                 size_t annotated = 1;
4555                 size_t i;
4557                 pos = strchr(text, '\t');
4558                 if (!pos)
4559                         return TRUE;
4560                 text = pos + 1;
4561                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4562                         text += strlen(opt_path);
4563                 pos = strchr(text, '/');
4564                 if (pos)
4565                         *pos = 0;
4567                 for (i = 1; i < view->lines; i++) {
4568                         struct line *line = &view->line[i];
4569                         struct tree_entry *entry = line->data;
4571                         annotated += !!entry->author;
4572                         if (entry->author || strcmp(entry->name, text))
4573                                 continue;
4575                         entry->author = author_name;
4576                         entry->time = author_time;
4577                         line->dirty = 1;
4578                         break;
4579                 }
4581                 if (annotated == view->lines)
4582                         io_kill(view->pipe);
4583         }
4584         return TRUE;
4587 static bool
4588 tree_read(struct view *view, char *text)
4590         static bool read_date = FALSE;
4591         struct tree_entry *data;
4592         struct line *entry, *line;
4593         enum line_type type;
4594         size_t textlen = text ? strlen(text) : 0;
4595         char *path = text + SIZEOF_TREE_ATTR;
4597         if (read_date || !text)
4598                 return tree_read_date(view, text, &read_date);
4600         if (textlen <= SIZEOF_TREE_ATTR)
4601                 return FALSE;
4602         if (view->lines == 0 &&
4603             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4604                 return FALSE;
4606         /* Strip the path part ... */
4607         if (*opt_path) {
4608                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4609                 size_t striplen = strlen(opt_path);
4611                 if (pathlen > striplen)
4612                         memmove(path, path + striplen,
4613                                 pathlen - striplen + 1);
4615                 /* Insert "link" to parent directory. */
4616                 if (view->lines == 1 &&
4617                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4618                         return FALSE;
4619         }
4621         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4622         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4623         if (!entry)
4624                 return FALSE;
4625         data = entry->data;
4627         /* Skip "Directory ..." and ".." line. */
4628         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4629                 if (tree_compare_entry(line, entry) <= 0)
4630                         continue;
4632                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4634                 line->data = data;
4635                 line->type = type;
4636                 for (; line <= entry; line++)
4637                         line->dirty = line->cleareol = 1;
4638                 return TRUE;
4639         }
4641         if (tree_lineno > view->lineno) {
4642                 view->lineno = tree_lineno;
4643                 tree_lineno = 0;
4644         }
4646         return TRUE;
4649 static bool
4650 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4652         struct tree_entry *entry = line->data;
4654         if (line->type == LINE_TREE_HEAD) {
4655                 if (draw_text(view, line->type, "Directory path /", TRUE))
4656                         return TRUE;
4657         } else {
4658                 if (draw_mode(view, entry->mode))
4659                         return TRUE;
4661                 if (opt_author && draw_author(view, entry->author))
4662                         return TRUE;
4664                 if (opt_date && draw_date(view, &entry->time))
4665                         return TRUE;
4666         }
4667         if (draw_text(view, line->type, entry->name, TRUE))
4668                 return TRUE;
4669         return TRUE;
4672 static void
4673 open_blob_editor(const char *id)
4675         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4676         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4677         int fd = mkstemp(file);
4679         if (fd == -1)
4680                 report("Failed to create temporary file");
4681         else if (!io_run_append(blob_argv, fd))
4682                 report("Failed to save blob data to file");
4683         else
4684                 open_editor(file);
4685         if (fd != -1)
4686                 unlink(file);
4689 static enum request
4690 tree_request(struct view *view, enum request request, struct line *line)
4692         enum open_flags flags;
4693         struct tree_entry *entry = line->data;
4695         switch (request) {
4696         case REQ_VIEW_BLAME:
4697                 if (line->type != LINE_TREE_FILE) {
4698                         report("Blame only supported for files");
4699                         return REQ_NONE;
4700                 }
4702                 string_copy(opt_ref, view->vid);
4703                 return request;
4705         case REQ_EDIT:
4706                 if (line->type != LINE_TREE_FILE) {
4707                         report("Edit only supported for files");
4708                 } else if (!is_head_commit(view->vid)) {
4709                         open_blob_editor(entry->id);
4710                 } else {
4711                         open_editor(opt_file);
4712                 }
4713                 return REQ_NONE;
4715         case REQ_TOGGLE_SORT_FIELD:
4716         case REQ_TOGGLE_SORT_ORDER:
4717                 sort_view(view, request, &tree_sort_state, tree_compare);
4718                 return REQ_NONE;
4720         case REQ_PARENT:
4721                 if (!*opt_path) {
4722                         /* quit view if at top of tree */
4723                         return REQ_VIEW_CLOSE;
4724                 }
4725                 /* fake 'cd  ..' */
4726                 line = &view->line[1];
4727                 break;
4729         case REQ_ENTER:
4730                 break;
4732         default:
4733                 return request;
4734         }
4736         /* Cleanup the stack if the tree view is at a different tree. */
4737         while (!*opt_path && tree_stack)
4738                 pop_tree_stack_entry();
4740         switch (line->type) {
4741         case LINE_TREE_DIR:
4742                 /* Depending on whether it is a subdirectory or parent link
4743                  * mangle the path buffer. */
4744                 if (line == &view->line[1] && *opt_path) {
4745                         pop_tree_stack_entry();
4747                 } else {
4748                         const char *basename = tree_path(line);
4750                         push_tree_stack_entry(basename, view->lineno);
4751                 }
4753                 /* Trees and subtrees share the same ID, so they are not not
4754                  * unique like blobs. */
4755                 flags = OPEN_RELOAD;
4756                 request = REQ_VIEW_TREE;
4757                 break;
4759         case LINE_TREE_FILE:
4760                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4761                 request = REQ_VIEW_BLOB;
4762                 break;
4764         default:
4765                 return REQ_NONE;
4766         }
4768         open_view(view, request, flags);
4769         if (request == REQ_VIEW_TREE)
4770                 view->lineno = tree_lineno;
4772         return REQ_NONE;
4775 static bool
4776 tree_grep(struct view *view, struct line *line)
4778         struct tree_entry *entry = line->data;
4779         const char *text[] = {
4780                 entry->name,
4781                 opt_author ? entry->author : "",
4782                 mkdate(&entry->time, opt_date),
4783                 NULL
4784         };
4786         return grep_text(view, text);
4789 static void
4790 tree_select(struct view *view, struct line *line)
4792         struct tree_entry *entry = line->data;
4794         if (line->type == LINE_TREE_FILE) {
4795                 string_copy_rev(ref_blob, entry->id);
4796                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4798         } else if (line->type != LINE_TREE_DIR) {
4799                 return;
4800         }
4802         string_copy_rev(view->ref, entry->id);
4805 static bool
4806 tree_prepare(struct view *view)
4808         if (view->lines == 0 && opt_prefix[0]) {
4809                 char *pos = opt_prefix;
4811                 while (pos && *pos) {
4812                         char *end = strchr(pos, '/');
4814                         if (end)
4815                                 *end = 0;
4816                         push_tree_stack_entry(pos, 0);
4817                         pos = end;
4818                         if (end) {
4819                                 *end = '/';
4820                                 pos++;
4821                         }
4822                 }
4824         } else if (strcmp(view->vid, view->id)) {
4825                 opt_path[0] = 0;
4826         }
4828         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4831 static const char *tree_argv[SIZEOF_ARG] = {
4832         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4833 };
4835 static struct view_ops tree_ops = {
4836         "file",
4837         tree_argv,
4838         NULL,
4839         tree_read,
4840         tree_draw,
4841         tree_request,
4842         tree_grep,
4843         tree_select,
4844         tree_prepare,
4845 };
4847 static bool
4848 blob_read(struct view *view, char *line)
4850         if (!line)
4851                 return TRUE;
4852         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4855 static enum request
4856 blob_request(struct view *view, enum request request, struct line *line)
4858         switch (request) {
4859         case REQ_EDIT:
4860                 open_blob_editor(view->vid);
4861                 return REQ_NONE;
4862         default:
4863                 return pager_request(view, request, line);
4864         }
4867 static const char *blob_argv[SIZEOF_ARG] = {
4868         "git", "cat-file", "blob", "%(blob)", NULL
4869 };
4871 static struct view_ops blob_ops = {
4872         "line",
4873         blob_argv,
4874         NULL,
4875         blob_read,
4876         pager_draw,
4877         blob_request,
4878         pager_grep,
4879         pager_select,
4880 };
4882 /*
4883  * Blame backend
4884  *
4885  * Loading the blame view is a two phase job:
4886  *
4887  *  1. File content is read either using opt_file from the
4888  *     filesystem or using git-cat-file.
4889  *  2. Then blame information is incrementally added by
4890  *     reading output from git-blame.
4891  */
4893 struct blame_commit {
4894         char id[SIZEOF_REV];            /* SHA1 ID. */
4895         char title[128];                /* First line of the commit message. */
4896         const char *author;             /* Author of the commit. */
4897         struct time time;               /* Date from the author ident. */
4898         char filename[128];             /* Name of file. */
4899         bool has_previous;              /* Was a "previous" line detected. */
4900 };
4902 struct blame {
4903         struct blame_commit *commit;
4904         unsigned long lineno;
4905         char text[1];
4906 };
4908 static bool
4909 blame_open(struct view *view)
4911         char path[SIZEOF_STR];
4913         if (!view->prev && *opt_prefix) {
4914                 string_copy(path, opt_file);
4915                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4916                         return FALSE;
4917         }
4919         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4920                 const char *blame_cat_file_argv[] = {
4921                         "git", "cat-file", "blob", path, NULL
4922                 };
4924                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4925                     !io_run_rd(&view->io, blame_cat_file_argv, opt_cdup))
4926                         return FALSE;
4927         }
4929         setup_update(view, opt_file);
4930         string_format(view->ref, "%s ...", opt_file);
4932         return TRUE;
4935 static struct blame_commit *
4936 get_blame_commit(struct view *view, const char *id)
4938         size_t i;
4940         for (i = 0; i < view->lines; i++) {
4941                 struct blame *blame = view->line[i].data;
4943                 if (!blame->commit)
4944                         continue;
4946                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4947                         return blame->commit;
4948         }
4950         {
4951                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4953                 if (commit)
4954                         string_ncopy(commit->id, id, SIZEOF_REV);
4955                 return commit;
4956         }
4959 static bool
4960 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4962         const char *pos = *posref;
4964         *posref = NULL;
4965         pos = strchr(pos + 1, ' ');
4966         if (!pos || !isdigit(pos[1]))
4967                 return FALSE;
4968         *number = atoi(pos + 1);
4969         if (*number < min || *number > max)
4970                 return FALSE;
4972         *posref = pos;
4973         return TRUE;
4976 static struct blame_commit *
4977 parse_blame_commit(struct view *view, const char *text, int *blamed)
4979         struct blame_commit *commit;
4980         struct blame *blame;
4981         const char *pos = text + SIZEOF_REV - 2;
4982         size_t orig_lineno = 0;
4983         size_t lineno;
4984         size_t group;
4986         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4987                 return NULL;
4989         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4990             !parse_number(&pos, &lineno, 1, view->lines) ||
4991             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4992                 return NULL;
4994         commit = get_blame_commit(view, text);
4995         if (!commit)
4996                 return NULL;
4998         *blamed += group;
4999         while (group--) {
5000                 struct line *line = &view->line[lineno + group - 1];
5002                 blame = line->data;
5003                 blame->commit = commit;
5004                 blame->lineno = orig_lineno + group - 1;
5005                 line->dirty = 1;
5006         }
5008         return commit;
5011 static bool
5012 blame_read_file(struct view *view, const char *line, bool *read_file)
5014         if (!line) {
5015                 const char *blame_argv[] = {
5016                         "git", "blame", "--incremental",
5017                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5018                 };
5019                 struct io io = {};
5021                 if (view->lines == 0 && !view->prev)
5022                         die("No blame exist for %s", view->vid);
5024                 if (view->lines == 0 || !io_run_rd(&io, blame_argv, opt_cdup)) {
5025                         report("Failed to load blame data");
5026                         return TRUE;
5027                 }
5029                 io_done(view->pipe);
5030                 view->io = io;
5031                 *read_file = FALSE;
5032                 return FALSE;
5034         } else {
5035                 size_t linelen = strlen(line);
5036                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5038                 if (!blame)
5039                         return FALSE;
5041                 blame->commit = NULL;
5042                 strncpy(blame->text, line, linelen);
5043                 blame->text[linelen] = 0;
5044                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5045         }
5048 static bool
5049 match_blame_header(const char *name, char **line)
5051         size_t namelen = strlen(name);
5052         bool matched = !strncmp(name, *line, namelen);
5054         if (matched)
5055                 *line += namelen;
5057         return matched;
5060 static bool
5061 blame_read(struct view *view, char *line)
5063         static struct blame_commit *commit = NULL;
5064         static int blamed = 0;
5065         static bool read_file = TRUE;
5067         if (read_file)
5068                 return blame_read_file(view, line, &read_file);
5070         if (!line) {
5071                 /* Reset all! */
5072                 commit = NULL;
5073                 blamed = 0;
5074                 read_file = TRUE;
5075                 string_format(view->ref, "%s", view->vid);
5076                 if (view_is_displayed(view)) {
5077                         update_view_title(view);
5078                         redraw_view_from(view, 0);
5079                 }
5080                 return TRUE;
5081         }
5083         if (!commit) {
5084                 commit = parse_blame_commit(view, line, &blamed);
5085                 string_format(view->ref, "%s %2d%%", view->vid,
5086                               view->lines ? blamed * 100 / view->lines : 0);
5088         } else if (match_blame_header("author ", &line)) {
5089                 commit->author = get_author(line);
5091         } else if (match_blame_header("author-time ", &line)) {
5092                 parse_timesec(&commit->time, line);
5094         } else if (match_blame_header("author-tz ", &line)) {
5095                 parse_timezone(&commit->time, line);
5097         } else if (match_blame_header("summary ", &line)) {
5098                 string_ncopy(commit->title, line, strlen(line));
5100         } else if (match_blame_header("previous ", &line)) {
5101                 commit->has_previous = TRUE;
5103         } else if (match_blame_header("filename ", &line)) {
5104                 string_ncopy(commit->filename, line, strlen(line));
5105                 commit = NULL;
5106         }
5108         return TRUE;
5111 static bool
5112 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5114         struct blame *blame = line->data;
5115         struct time *time = NULL;
5116         const char *id = NULL, *author = NULL;
5117         char text[SIZEOF_STR];
5119         if (blame->commit && *blame->commit->filename) {
5120                 id = blame->commit->id;
5121                 author = blame->commit->author;
5122                 time = &blame->commit->time;
5123         }
5125         if (opt_date && draw_date(view, time))
5126                 return TRUE;
5128         if (opt_author && draw_author(view, author))
5129                 return TRUE;
5131         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5132                 return TRUE;
5134         if (draw_lineno(view, lineno))
5135                 return TRUE;
5137         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5138         draw_text(view, LINE_DEFAULT, text, TRUE);
5139         return TRUE;
5142 static bool
5143 check_blame_commit(struct blame *blame, bool check_null_id)
5145         if (!blame->commit)
5146                 report("Commit data not loaded yet");
5147         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5148                 report("No commit exist for the selected line");
5149         else
5150                 return TRUE;
5151         return FALSE;
5154 static void
5155 setup_blame_parent_line(struct view *view, struct blame *blame)
5157         const char *diff_tree_argv[] = {
5158                 "git", "diff-tree", "-U0", blame->commit->id,
5159                         "--", blame->commit->filename, NULL
5160         };
5161         struct io io = {};
5162         int parent_lineno = -1;
5163         int blamed_lineno = -1;
5164         char *line;
5166         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5167                 return;
5169         while ((line = io_get(&io, '\n', TRUE))) {
5170                 if (*line == '@') {
5171                         char *pos = strchr(line, '+');
5173                         parent_lineno = atoi(line + 4);
5174                         if (pos)
5175                                 blamed_lineno = atoi(pos + 1);
5177                 } else if (*line == '+' && parent_lineno != -1) {
5178                         if (blame->lineno == blamed_lineno - 1 &&
5179                             !strcmp(blame->text, line + 1)) {
5180                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5181                                 break;
5182                         }
5183                         blamed_lineno++;
5184                 }
5185         }
5187         io_done(&io);
5190 static enum request
5191 blame_request(struct view *view, enum request request, struct line *line)
5193         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5194         struct blame *blame = line->data;
5196         switch (request) {
5197         case REQ_VIEW_BLAME:
5198                 if (check_blame_commit(blame, TRUE)) {
5199                         string_copy(opt_ref, blame->commit->id);
5200                         string_copy(opt_file, blame->commit->filename);
5201                         if (blame->lineno)
5202                                 view->lineno = blame->lineno;
5203                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5204                 }
5205                 break;
5207         case REQ_PARENT:
5208                 if (check_blame_commit(blame, TRUE) &&
5209                     select_commit_parent(blame->commit->id, opt_ref,
5210                                          blame->commit->filename)) {
5211                         string_copy(opt_file, blame->commit->filename);
5212                         setup_blame_parent_line(view, blame);
5213                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5214                 }
5215                 break;
5217         case REQ_ENTER:
5218                 if (!check_blame_commit(blame, FALSE))
5219                         break;
5221                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5222                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5223                         break;
5225                 if (!strcmp(blame->commit->id, NULL_ID)) {
5226                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5227                         const char *diff_index_argv[] = {
5228                                 "git", "diff-index", "--root", "--patch-with-stat",
5229                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5230                         };
5232                         if (!blame->commit->has_previous) {
5233                                 diff_index_argv[1] = "diff";
5234                                 diff_index_argv[2] = "--no-color";
5235                                 diff_index_argv[6] = "--";
5236                                 diff_index_argv[7] = "/dev/null";
5237                         }
5239                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5240                                 report("Failed to allocate diff command");
5241                                 break;
5242                         }
5243                         flags |= OPEN_PREPARED;
5244                 }
5246                 open_view(view, REQ_VIEW_DIFF, flags);
5247                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5248                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5249                 break;
5251         default:
5252                 return request;
5253         }
5255         return REQ_NONE;
5258 static bool
5259 blame_grep(struct view *view, struct line *line)
5261         struct blame *blame = line->data;
5262         struct blame_commit *commit = blame->commit;
5263         const char *text[] = {
5264                 blame->text,
5265                 commit ? commit->title : "",
5266                 commit ? commit->id : "",
5267                 commit && opt_author ? commit->author : "",
5268                 commit ? mkdate(&commit->time, opt_date) : "",
5269                 NULL
5270         };
5272         return grep_text(view, text);
5275 static void
5276 blame_select(struct view *view, struct line *line)
5278         struct blame *blame = line->data;
5279         struct blame_commit *commit = blame->commit;
5281         if (!commit)
5282                 return;
5284         if (!strcmp(commit->id, NULL_ID))
5285                 string_ncopy(ref_commit, "HEAD", 4);
5286         else
5287                 string_copy_rev(ref_commit, commit->id);
5290 static struct view_ops blame_ops = {
5291         "line",
5292         NULL,
5293         blame_open,
5294         blame_read,
5295         blame_draw,
5296         blame_request,
5297         blame_grep,
5298         blame_select,
5299 };
5301 /*
5302  * Branch backend
5303  */
5305 struct branch {
5306         const char *author;             /* Author of the last commit. */
5307         struct time time;               /* Date of the last activity. */
5308         const struct ref *ref;          /* Name and commit ID information. */
5309 };
5311 static const struct ref branch_all;
5313 static const enum sort_field branch_sort_fields[] = {
5314         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5315 };
5316 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5318 static int
5319 branch_compare(const void *l1, const void *l2)
5321         const struct branch *branch1 = ((const struct line *) l1)->data;
5322         const struct branch *branch2 = ((const struct line *) l2)->data;
5324         switch (get_sort_field(branch_sort_state)) {
5325         case ORDERBY_DATE:
5326                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5328         case ORDERBY_AUTHOR:
5329                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5331         case ORDERBY_NAME:
5332         default:
5333                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5334         }
5337 static bool
5338 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5340         struct branch *branch = line->data;
5341         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5343         if (opt_date && draw_date(view, &branch->time))
5344                 return TRUE;
5346         if (opt_author && draw_author(view, branch->author))
5347                 return TRUE;
5349         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5350         return TRUE;
5353 static enum request
5354 branch_request(struct view *view, enum request request, struct line *line)
5356         struct branch *branch = line->data;
5358         switch (request) {
5359         case REQ_REFRESH:
5360                 load_refs();
5361                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5362                 return REQ_NONE;
5364         case REQ_TOGGLE_SORT_FIELD:
5365         case REQ_TOGGLE_SORT_ORDER:
5366                 sort_view(view, request, &branch_sort_state, branch_compare);
5367                 return REQ_NONE;
5369         case REQ_ENTER:
5370                 if (branch->ref == &branch_all) {
5371                         const char *all_branches_argv[] = {
5372                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5373                                       "--topo-order", "--all", NULL
5374                         };
5375                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5377                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5378                                 report("Failed to load view of all branches");
5379                                 return REQ_NONE;
5380                         }
5381                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5382                 } else {
5383                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5384                 }
5385                 return REQ_NONE;
5387         default:
5388                 return request;
5389         }
5392 static bool
5393 branch_read(struct view *view, char *line)
5395         static char id[SIZEOF_REV];
5396         struct branch *reference;
5397         size_t i;
5399         if (!line)
5400                 return TRUE;
5402         switch (get_line_type(line)) {
5403         case LINE_COMMIT:
5404                 string_copy_rev(id, line + STRING_SIZE("commit "));
5405                 return TRUE;
5407         case LINE_AUTHOR:
5408                 for (i = 0, reference = NULL; i < view->lines; i++) {
5409                         struct branch *branch = view->line[i].data;
5411                         if (strcmp(branch->ref->id, id))
5412                                 continue;
5414                         view->line[i].dirty = TRUE;
5415                         if (reference) {
5416                                 branch->author = reference->author;
5417                                 branch->time = reference->time;
5418                                 continue;
5419                         }
5421                         parse_author_line(line + STRING_SIZE("author "),
5422                                           &branch->author, &branch->time);
5423                         reference = branch;
5424                 }
5425                 return TRUE;
5427         default:
5428                 return TRUE;
5429         }
5433 static bool
5434 branch_open_visitor(void *data, const struct ref *ref)
5436         struct view *view = data;
5437         struct branch *branch;
5439         if (ref->tag || ref->ltag || ref->remote)
5440                 return TRUE;
5442         branch = calloc(1, sizeof(*branch));
5443         if (!branch)
5444                 return FALSE;
5446         branch->ref = ref;
5447         return !!add_line_data(view, branch, LINE_DEFAULT);
5450 static bool
5451 branch_open(struct view *view)
5453         const char *branch_log[] = {
5454                 "git", "log", "--no-color", "--pretty=raw",
5455                         "--simplify-by-decoration", "--all", NULL
5456         };
5458         if (!io_run_rd(&view->io, branch_log, NULL)) {
5459                 report("Failed to load branch data");
5460                 return TRUE;
5461         }
5463         setup_update(view, view->id);
5464         branch_open_visitor(view, &branch_all);
5465         foreach_ref(branch_open_visitor, view);
5466         view->p_restore = TRUE;
5468         return TRUE;
5471 static bool
5472 branch_grep(struct view *view, struct line *line)
5474         struct branch *branch = line->data;
5475         const char *text[] = {
5476                 branch->ref->name,
5477                 branch->author,
5478                 NULL
5479         };
5481         return grep_text(view, text);
5484 static void
5485 branch_select(struct view *view, struct line *line)
5487         struct branch *branch = line->data;
5489         string_copy_rev(view->ref, branch->ref->id);
5490         string_copy_rev(ref_commit, branch->ref->id);
5491         string_copy_rev(ref_head, branch->ref->id);
5492         string_copy_rev(ref_branch, branch->ref->name);
5495 static struct view_ops branch_ops = {
5496         "branch",
5497         NULL,
5498         branch_open,
5499         branch_read,
5500         branch_draw,
5501         branch_request,
5502         branch_grep,
5503         branch_select,
5504 };
5506 /*
5507  * Status backend
5508  */
5510 struct status {
5511         char status;
5512         struct {
5513                 mode_t mode;
5514                 char rev[SIZEOF_REV];
5515                 char name[SIZEOF_STR];
5516         } old;
5517         struct {
5518                 mode_t mode;
5519                 char rev[SIZEOF_REV];
5520                 char name[SIZEOF_STR];
5521         } new;
5522 };
5524 static char status_onbranch[SIZEOF_STR];
5525 static struct status stage_status;
5526 static enum line_type stage_line_type;
5527 static size_t stage_chunks;
5528 static int *stage_chunk;
5530 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5532 /* This should work even for the "On branch" line. */
5533 static inline bool
5534 status_has_none(struct view *view, struct line *line)
5536         return line < view->line + view->lines && !line[1].data;
5539 /* Get fields from the diff line:
5540  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5541  */
5542 static inline bool
5543 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5545         const char *old_mode = buf +  1;
5546         const char *new_mode = buf +  8;
5547         const char *old_rev  = buf + 15;
5548         const char *new_rev  = buf + 56;
5549         const char *status   = buf + 97;
5551         if (bufsize < 98 ||
5552             old_mode[-1] != ':' ||
5553             new_mode[-1] != ' ' ||
5554             old_rev[-1]  != ' ' ||
5555             new_rev[-1]  != ' ' ||
5556             status[-1]   != ' ')
5557                 return FALSE;
5559         file->status = *status;
5561         string_copy_rev(file->old.rev, old_rev);
5562         string_copy_rev(file->new.rev, new_rev);
5564         file->old.mode = strtoul(old_mode, NULL, 8);
5565         file->new.mode = strtoul(new_mode, NULL, 8);
5567         file->old.name[0] = file->new.name[0] = 0;
5569         return TRUE;
5572 static bool
5573 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5575         struct status *unmerged = NULL;
5576         char *buf;
5577         struct io io = {};
5579         if (!io_run(&io, argv, opt_cdup, IO_RD))
5580                 return FALSE;
5582         add_line_data(view, NULL, type);
5584         while ((buf = io_get(&io, 0, TRUE))) {
5585                 struct status *file = unmerged;
5587                 if (!file) {
5588                         file = calloc(1, sizeof(*file));
5589                         if (!file || !add_line_data(view, file, type))
5590                                 goto error_out;
5591                 }
5593                 /* Parse diff info part. */
5594                 if (status) {
5595                         file->status = status;
5596                         if (status == 'A')
5597                                 string_copy(file->old.rev, NULL_ID);
5599                 } else if (!file->status || file == unmerged) {
5600                         if (!status_get_diff(file, buf, strlen(buf)))
5601                                 goto error_out;
5603                         buf = io_get(&io, 0, TRUE);
5604                         if (!buf)
5605                                 break;
5607                         /* Collapse all modified entries that follow an
5608                          * associated unmerged entry. */
5609                         if (unmerged == file) {
5610                                 unmerged->status = 'U';
5611                                 unmerged = NULL;
5612                         } else if (file->status == 'U') {
5613                                 unmerged = file;
5614                         }
5615                 }
5617                 /* Grab the old name for rename/copy. */
5618                 if (!*file->old.name &&
5619                     (file->status == 'R' || file->status == 'C')) {
5620                         string_ncopy(file->old.name, buf, strlen(buf));
5622                         buf = io_get(&io, 0, TRUE);
5623                         if (!buf)
5624                                 break;
5625                 }
5627                 /* git-ls-files just delivers a NUL separated list of
5628                  * file names similar to the second half of the
5629                  * git-diff-* output. */
5630                 string_ncopy(file->new.name, buf, strlen(buf));
5631                 if (!*file->old.name)
5632                         string_copy(file->old.name, file->new.name);
5633                 file = NULL;
5634         }
5636         if (io_error(&io)) {
5637 error_out:
5638                 io_done(&io);
5639                 return FALSE;
5640         }
5642         if (!view->line[view->lines - 1].data)
5643                 add_line_data(view, NULL, LINE_STAT_NONE);
5645         io_done(&io);
5646         return TRUE;
5649 /* Don't show unmerged entries in the staged section. */
5650 static const char *status_diff_index_argv[] = {
5651         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5652                              "--cached", "-M", "HEAD", NULL
5653 };
5655 static const char *status_diff_files_argv[] = {
5656         "git", "diff-files", "-z", NULL
5657 };
5659 static const char *status_list_other_argv[] = {
5660         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5661 };
5663 static const char *status_list_no_head_argv[] = {
5664         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5665 };
5667 static const char *update_index_argv[] = {
5668         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5669 };
5671 /* Restore the previous line number to stay in the context or select a
5672  * line with something that can be updated. */
5673 static void
5674 status_restore(struct view *view)
5676         if (view->p_lineno >= view->lines)
5677                 view->p_lineno = view->lines - 1;
5678         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5679                 view->p_lineno++;
5680         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5681                 view->p_lineno--;
5683         /* If the above fails, always skip the "On branch" line. */
5684         if (view->p_lineno < view->lines)
5685                 view->lineno = view->p_lineno;
5686         else
5687                 view->lineno = 1;
5689         if (view->lineno < view->offset)
5690                 view->offset = view->lineno;
5691         else if (view->offset + view->height <= view->lineno)
5692                 view->offset = view->lineno - view->height + 1;
5694         view->p_restore = FALSE;
5697 static void
5698 status_update_onbranch(void)
5700         static const char *paths[][2] = {
5701                 { "rebase-apply/rebasing",      "Rebasing" },
5702                 { "rebase-apply/applying",      "Applying mailbox" },
5703                 { "rebase-apply/",              "Rebasing mailbox" },
5704                 { "rebase-merge/interactive",   "Interactive rebase" },
5705                 { "rebase-merge/",              "Rebase merge" },
5706                 { "MERGE_HEAD",                 "Merging" },
5707                 { "BISECT_LOG",                 "Bisecting" },
5708                 { "HEAD",                       "On branch" },
5709         };
5710         char buf[SIZEOF_STR];
5711         struct stat stat;
5712         int i;
5714         if (is_initial_commit()) {
5715                 string_copy(status_onbranch, "Initial commit");
5716                 return;
5717         }
5719         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5720                 char *head = opt_head;
5722                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5723                     lstat(buf, &stat) < 0)
5724                         continue;
5726                 if (!*opt_head) {
5727                         struct io io = {};
5729                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5730                             io_read_buf(&io, buf, sizeof(buf))) {
5731                                 head = buf;
5732                                 if (!prefixcmp(head, "refs/heads/"))
5733                                         head += STRING_SIZE("refs/heads/");
5734                         }
5735                 }
5737                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5738                         string_copy(status_onbranch, opt_head);
5739                 return;
5740         }
5742         string_copy(status_onbranch, "Not currently on any branch");
5745 /* First parse staged info using git-diff-index(1), then parse unstaged
5746  * info using git-diff-files(1), and finally untracked files using
5747  * git-ls-files(1). */
5748 static bool
5749 status_open(struct view *view)
5751         reset_view(view);
5753         add_line_data(view, NULL, LINE_STAT_HEAD);
5754         status_update_onbranch();
5756         io_run_bg(update_index_argv);
5758         if (is_initial_commit()) {
5759                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5760                         return FALSE;
5761         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5762                 return FALSE;
5763         }
5765         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5766             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5767                 return FALSE;
5769         /* Restore the exact position or use the specialized restore
5770          * mode? */
5771         if (!view->p_restore)
5772                 status_restore(view);
5773         return TRUE;
5776 static bool
5777 status_draw(struct view *view, struct line *line, unsigned int lineno)
5779         struct status *status = line->data;
5780         enum line_type type;
5781         const char *text;
5783         if (!status) {
5784                 switch (line->type) {
5785                 case LINE_STAT_STAGED:
5786                         type = LINE_STAT_SECTION;
5787                         text = "Changes to be committed:";
5788                         break;
5790                 case LINE_STAT_UNSTAGED:
5791                         type = LINE_STAT_SECTION;
5792                         text = "Changed but not updated:";
5793                         break;
5795                 case LINE_STAT_UNTRACKED:
5796                         type = LINE_STAT_SECTION;
5797                         text = "Untracked files:";
5798                         break;
5800                 case LINE_STAT_NONE:
5801                         type = LINE_DEFAULT;
5802                         text = "  (no files)";
5803                         break;
5805                 case LINE_STAT_HEAD:
5806                         type = LINE_STAT_HEAD;
5807                         text = status_onbranch;
5808                         break;
5810                 default:
5811                         return FALSE;
5812                 }
5813         } else {
5814                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5816                 buf[0] = status->status;
5817                 if (draw_text(view, line->type, buf, TRUE))
5818                         return TRUE;
5819                 type = LINE_DEFAULT;
5820                 text = status->new.name;
5821         }
5823         draw_text(view, type, text, TRUE);
5824         return TRUE;
5827 static enum request
5828 status_load_error(struct view *view, struct view *stage, const char *path)
5830         if (displayed_views() == 2 || display[current_view] != view)
5831                 maximize_view(view);
5832         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5833         return REQ_NONE;
5836 static enum request
5837 status_enter(struct view *view, struct line *line)
5839         struct status *status = line->data;
5840         const char *oldpath = status ? status->old.name : NULL;
5841         /* Diffs for unmerged entries are empty when passing the new
5842          * path, so leave it empty. */
5843         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5844         const char *info;
5845         enum open_flags split;
5846         struct view *stage = VIEW(REQ_VIEW_STAGE);
5848         if (line->type == LINE_STAT_NONE ||
5849             (!status && line[1].type == LINE_STAT_NONE)) {
5850                 report("No file to diff");
5851                 return REQ_NONE;
5852         }
5854         switch (line->type) {
5855         case LINE_STAT_STAGED:
5856                 if (is_initial_commit()) {
5857                         const char *no_head_diff_argv[] = {
5858                                 "git", "diff", "--no-color", "--patch-with-stat",
5859                                         "--", "/dev/null", newpath, NULL
5860                         };
5862                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5863                                 return status_load_error(view, stage, newpath);
5864                 } else {
5865                         const char *index_show_argv[] = {
5866                                 "git", "diff-index", "--root", "--patch-with-stat",
5867                                         "-C", "-M", "--cached", "HEAD", "--",
5868                                         oldpath, newpath, NULL
5869                         };
5871                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5872                                 return status_load_error(view, stage, newpath);
5873                 }
5875                 if (status)
5876                         info = "Staged changes to %s";
5877                 else
5878                         info = "Staged changes";
5879                 break;
5881         case LINE_STAT_UNSTAGED:
5882         {
5883                 const char *files_show_argv[] = {
5884                         "git", "diff-files", "--root", "--patch-with-stat",
5885                                 "-C", "-M", "--", oldpath, newpath, NULL
5886                 };
5888                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5889                         return status_load_error(view, stage, newpath);
5890                 if (status)
5891                         info = "Unstaged changes to %s";
5892                 else
5893                         info = "Unstaged changes";
5894                 break;
5895         }
5896         case LINE_STAT_UNTRACKED:
5897                 if (!newpath) {
5898                         report("No file to show");
5899                         return REQ_NONE;
5900                 }
5902                 if (!suffixcmp(status->new.name, -1, "/")) {
5903                         report("Cannot display a directory");
5904                         return REQ_NONE;
5905                 }
5907                 if (!prepare_update_file(stage, newpath))
5908                         return status_load_error(view, stage, newpath);
5909                 info = "Untracked file %s";
5910                 break;
5912         case LINE_STAT_HEAD:
5913                 return REQ_NONE;
5915         default:
5916                 die("line type %d not handled in switch", line->type);
5917         }
5919         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5920         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5921         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5922                 if (status) {
5923                         stage_status = *status;
5924                 } else {
5925                         memset(&stage_status, 0, sizeof(stage_status));
5926                 }
5928                 stage_line_type = line->type;
5929                 stage_chunks = 0;
5930                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5931         }
5933         return REQ_NONE;
5936 static bool
5937 status_exists(struct status *status, enum line_type type)
5939         struct view *view = VIEW(REQ_VIEW_STATUS);
5940         unsigned long lineno;
5942         for (lineno = 0; lineno < view->lines; lineno++) {
5943                 struct line *line = &view->line[lineno];
5944                 struct status *pos = line->data;
5946                 if (line->type != type)
5947                         continue;
5948                 if (!pos && (!status || !status->status) && line[1].data) {
5949                         select_view_line(view, lineno);
5950                         return TRUE;
5951                 }
5952                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5953                         select_view_line(view, lineno);
5954                         return TRUE;
5955                 }
5956         }
5958         return FALSE;
5962 static bool
5963 status_update_prepare(struct io *io, enum line_type type)
5965         const char *staged_argv[] = {
5966                 "git", "update-index", "-z", "--index-info", NULL
5967         };
5968         const char *others_argv[] = {
5969                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5970         };
5972         switch (type) {
5973         case LINE_STAT_STAGED:
5974                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5976         case LINE_STAT_UNSTAGED:
5977         case LINE_STAT_UNTRACKED:
5978                 return io_run(io, others_argv, opt_cdup, IO_WR);
5980         default:
5981                 die("line type %d not handled in switch", type);
5982                 return FALSE;
5983         }
5986 static bool
5987 status_update_write(struct io *io, struct status *status, enum line_type type)
5989         char buf[SIZEOF_STR];
5990         size_t bufsize = 0;
5992         switch (type) {
5993         case LINE_STAT_STAGED:
5994                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5995                                         status->old.mode,
5996                                         status->old.rev,
5997                                         status->old.name, 0))
5998                         return FALSE;
5999                 break;
6001         case LINE_STAT_UNSTAGED:
6002         case LINE_STAT_UNTRACKED:
6003                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6004                         return FALSE;
6005                 break;
6007         default:
6008                 die("line type %d not handled in switch", type);
6009         }
6011         return io_write(io, buf, bufsize);
6014 static bool
6015 status_update_file(struct status *status, enum line_type type)
6017         struct io io = {};
6018         bool result;
6020         if (!status_update_prepare(&io, type))
6021                 return FALSE;
6023         result = status_update_write(&io, status, type);
6024         return io_done(&io) && result;
6027 static bool
6028 status_update_files(struct view *view, struct line *line)
6030         char buf[sizeof(view->ref)];
6031         struct io io = {};
6032         bool result = TRUE;
6033         struct line *pos = view->line + view->lines;
6034         int files = 0;
6035         int file, done;
6036         int cursor_y = -1, cursor_x = -1;
6038         if (!status_update_prepare(&io, line->type))
6039                 return FALSE;
6041         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6042                 files++;
6044         string_copy(buf, view->ref);
6045         getsyx(cursor_y, cursor_x);
6046         for (file = 0, done = 5; result && file < files; line++, file++) {
6047                 int almost_done = file * 100 / files;
6049                 if (almost_done > done) {
6050                         done = almost_done;
6051                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6052                                       file, files, done);
6053                         update_view_title(view);
6054                         setsyx(cursor_y, cursor_x);
6055                         doupdate();
6056                 }
6057                 result = status_update_write(&io, line->data, line->type);
6058         }
6059         string_copy(view->ref, buf);
6061         return io_done(&io) && result;
6064 static bool
6065 status_update(struct view *view)
6067         struct line *line = &view->line[view->lineno];
6069         assert(view->lines);
6071         if (!line->data) {
6072                 /* This should work even for the "On branch" line. */
6073                 if (line < view->line + view->lines && !line[1].data) {
6074                         report("Nothing to update");
6075                         return FALSE;
6076                 }
6078                 if (!status_update_files(view, line + 1)) {
6079                         report("Failed to update file status");
6080                         return FALSE;
6081                 }
6083         } else if (!status_update_file(line->data, line->type)) {
6084                 report("Failed to update file status");
6085                 return FALSE;
6086         }
6088         return TRUE;
6091 static bool
6092 status_revert(struct status *status, enum line_type type, bool has_none)
6094         if (!status || type != LINE_STAT_UNSTAGED) {
6095                 if (type == LINE_STAT_STAGED) {
6096                         report("Cannot revert changes to staged files");
6097                 } else if (type == LINE_STAT_UNTRACKED) {
6098                         report("Cannot revert changes to untracked files");
6099                 } else if (has_none) {
6100                         report("Nothing to revert");
6101                 } else {
6102                         report("Cannot revert changes to multiple files");
6103                 }
6105         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6106                 char mode[10] = "100644";
6107                 const char *reset_argv[] = {
6108                         "git", "update-index", "--cacheinfo", mode,
6109                                 status->old.rev, status->old.name, NULL
6110                 };
6111                 const char *checkout_argv[] = {
6112                         "git", "checkout", "--", status->old.name, NULL
6113                 };
6115                 if (status->status == 'U') {
6116                         string_format(mode, "%5o", status->old.mode);
6118                         if (status->old.mode == 0 && status->new.mode == 0) {
6119                                 reset_argv[2] = "--force-remove";
6120                                 reset_argv[3] = status->old.name;
6121                                 reset_argv[4] = NULL;
6122                         }
6124                         if (!io_run_fg(reset_argv, opt_cdup))
6125                                 return FALSE;
6126                         if (status->old.mode == 0 && status->new.mode == 0)
6127                                 return TRUE;
6128                 }
6130                 return io_run_fg(checkout_argv, opt_cdup);
6131         }
6133         return FALSE;
6136 static enum request
6137 status_request(struct view *view, enum request request, struct line *line)
6139         struct status *status = line->data;
6141         switch (request) {
6142         case REQ_STATUS_UPDATE:
6143                 if (!status_update(view))
6144                         return REQ_NONE;
6145                 break;
6147         case REQ_STATUS_REVERT:
6148                 if (!status_revert(status, line->type, status_has_none(view, line)))
6149                         return REQ_NONE;
6150                 break;
6152         case REQ_STATUS_MERGE:
6153                 if (!status || status->status != 'U') {
6154                         report("Merging only possible for files with unmerged status ('U').");
6155                         return REQ_NONE;
6156                 }
6157                 open_mergetool(status->new.name);
6158                 break;
6160         case REQ_EDIT:
6161                 if (!status)
6162                         return request;
6163                 if (status->status == 'D') {
6164                         report("File has been deleted.");
6165                         return REQ_NONE;
6166                 }
6168                 open_editor(status->new.name);
6169                 break;
6171         case REQ_VIEW_BLAME:
6172                 if (status)
6173                         opt_ref[0] = 0;
6174                 return request;
6176         case REQ_ENTER:
6177                 /* After returning the status view has been split to
6178                  * show the stage view. No further reloading is
6179                  * necessary. */
6180                 return status_enter(view, line);
6182         case REQ_REFRESH:
6183                 /* Simply reload the view. */
6184                 break;
6186         default:
6187                 return request;
6188         }
6190         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6192         return REQ_NONE;
6195 static void
6196 status_select(struct view *view, struct line *line)
6198         struct status *status = line->data;
6199         char file[SIZEOF_STR] = "all files";
6200         const char *text;
6201         const char *key;
6203         if (status && !string_format(file, "'%s'", status->new.name))
6204                 return;
6206         if (!status && line[1].type == LINE_STAT_NONE)
6207                 line++;
6209         switch (line->type) {
6210         case LINE_STAT_STAGED:
6211                 text = "Press %s to unstage %s for commit";
6212                 break;
6214         case LINE_STAT_UNSTAGED:
6215                 text = "Press %s to stage %s for commit";
6216                 break;
6218         case LINE_STAT_UNTRACKED:
6219                 text = "Press %s to stage %s for addition";
6220                 break;
6222         case LINE_STAT_HEAD:
6223         case LINE_STAT_NONE:
6224                 text = "Nothing to update";
6225                 break;
6227         default:
6228                 die("line type %d not handled in switch", line->type);
6229         }
6231         if (status && status->status == 'U') {
6232                 text = "Press %s to resolve conflict in %s";
6233                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6235         } else {
6236                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6237         }
6239         string_format(view->ref, text, key, file);
6240         if (status)
6241                 string_copy(opt_file, status->new.name);
6244 static bool
6245 status_grep(struct view *view, struct line *line)
6247         struct status *status = line->data;
6249         if (status) {
6250                 const char buf[2] = { status->status, 0 };
6251                 const char *text[] = { status->new.name, buf, NULL };
6253                 return grep_text(view, text);
6254         }
6256         return FALSE;
6259 static struct view_ops status_ops = {
6260         "file",
6261         NULL,
6262         status_open,
6263         NULL,
6264         status_draw,
6265         status_request,
6266         status_grep,
6267         status_select,
6268 };
6271 static bool
6272 stage_diff_write(struct io *io, struct line *line, struct line *end)
6274         while (line < end) {
6275                 if (!io_write(io, line->data, strlen(line->data)) ||
6276                     !io_write(io, "\n", 1))
6277                         return FALSE;
6278                 line++;
6279                 if (line->type == LINE_DIFF_CHUNK ||
6280                     line->type == LINE_DIFF_HEADER)
6281                         break;
6282         }
6284         return TRUE;
6287 static struct line *
6288 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6290         for (; view->line < line; line--)
6291                 if (line->type == type)
6292                         return line;
6294         return NULL;
6297 static bool
6298 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6300         const char *apply_argv[SIZEOF_ARG] = {
6301                 "git", "apply", "--whitespace=nowarn", NULL
6302         };
6303         struct line *diff_hdr;
6304         struct io io = {};
6305         int argc = 3;
6307         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6308         if (!diff_hdr)
6309                 return FALSE;
6311         if (!revert)
6312                 apply_argv[argc++] = "--cached";
6313         if (revert || stage_line_type == LINE_STAT_STAGED)
6314                 apply_argv[argc++] = "-R";
6315         apply_argv[argc++] = "-";
6316         apply_argv[argc++] = NULL;
6317         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6318                 return FALSE;
6320         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6321             !stage_diff_write(&io, chunk, view->line + view->lines))
6322                 chunk = NULL;
6324         io_done(&io);
6325         io_run_bg(update_index_argv);
6327         return chunk ? TRUE : FALSE;
6330 static bool
6331 stage_update(struct view *view, struct line *line)
6333         struct line *chunk = NULL;
6335         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6336                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6338         if (chunk) {
6339                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6340                         report("Failed to apply chunk");
6341                         return FALSE;
6342                 }
6344         } else if (!stage_status.status) {
6345                 view = VIEW(REQ_VIEW_STATUS);
6347                 for (line = view->line; line < view->line + view->lines; line++)
6348                         if (line->type == stage_line_type)
6349                                 break;
6351                 if (!status_update_files(view, line + 1)) {
6352                         report("Failed to update files");
6353                         return FALSE;
6354                 }
6356         } else if (!status_update_file(&stage_status, stage_line_type)) {
6357                 report("Failed to update file");
6358                 return FALSE;
6359         }
6361         return TRUE;
6364 static bool
6365 stage_revert(struct view *view, struct line *line)
6367         struct line *chunk = NULL;
6369         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6370                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6372         if (chunk) {
6373                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6374                         return FALSE;
6376                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6377                         report("Failed to revert chunk");
6378                         return FALSE;
6379                 }
6380                 return TRUE;
6382         } else {
6383                 return status_revert(stage_status.status ? &stage_status : NULL,
6384                                      stage_line_type, FALSE);
6385         }
6389 static void
6390 stage_next(struct view *view, struct line *line)
6392         int i;
6394         if (!stage_chunks) {
6395                 for (line = view->line; line < view->line + view->lines; line++) {
6396                         if (line->type != LINE_DIFF_CHUNK)
6397                                 continue;
6399                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6400                                 report("Allocation failure");
6401                                 return;
6402                         }
6404                         stage_chunk[stage_chunks++] = line - view->line;
6405                 }
6406         }
6408         for (i = 0; i < stage_chunks; i++) {
6409                 if (stage_chunk[i] > view->lineno) {
6410                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6411                         report("Chunk %d of %d", i + 1, stage_chunks);
6412                         return;
6413                 }
6414         }
6416         report("No next chunk found");
6419 static enum request
6420 stage_request(struct view *view, enum request request, struct line *line)
6422         switch (request) {
6423         case REQ_STATUS_UPDATE:
6424                 if (!stage_update(view, line))
6425                         return REQ_NONE;
6426                 break;
6428         case REQ_STATUS_REVERT:
6429                 if (!stage_revert(view, line))
6430                         return REQ_NONE;
6431                 break;
6433         case REQ_STAGE_NEXT:
6434                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6435                         report("File is untracked; press %s to add",
6436                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6437                         return REQ_NONE;
6438                 }
6439                 stage_next(view, line);
6440                 return REQ_NONE;
6442         case REQ_EDIT:
6443                 if (!stage_status.new.name[0])
6444                         return request;
6445                 if (stage_status.status == 'D') {
6446                         report("File has been deleted.");
6447                         return REQ_NONE;
6448                 }
6450                 open_editor(stage_status.new.name);
6451                 break;
6453         case REQ_REFRESH:
6454                 /* Reload everything ... */
6455                 break;
6457         case REQ_VIEW_BLAME:
6458                 if (stage_status.new.name[0]) {
6459                         string_copy(opt_file, stage_status.new.name);
6460                         opt_ref[0] = 0;
6461                 }
6462                 return request;
6464         case REQ_ENTER:
6465                 return pager_request(view, request, line);
6467         default:
6468                 return request;
6469         }
6471         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6472         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6474         /* Check whether the staged entry still exists, and close the
6475          * stage view if it doesn't. */
6476         if (!status_exists(&stage_status, stage_line_type)) {
6477                 status_restore(VIEW(REQ_VIEW_STATUS));
6478                 return REQ_VIEW_CLOSE;
6479         }
6481         if (stage_line_type == LINE_STAT_UNTRACKED) {
6482                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6483                         report("Cannot display a directory");
6484                         return REQ_NONE;
6485                 }
6487                 if (!prepare_update_file(view, stage_status.new.name)) {
6488                         report("Failed to open file: %s", strerror(errno));
6489                         return REQ_NONE;
6490                 }
6491         }
6492         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6494         return REQ_NONE;
6497 static struct view_ops stage_ops = {
6498         "line",
6499         NULL,
6500         NULL,
6501         pager_read,
6502         pager_draw,
6503         stage_request,
6504         pager_grep,
6505         pager_select,
6506 };
6509 /*
6510  * Revision graph
6511  */
6513 struct commit {
6514         char id[SIZEOF_REV];            /* SHA1 ID. */
6515         char title[128];                /* First line of the commit message. */
6516         const char *author;             /* Author of the commit. */
6517         struct time time;               /* Date from the author ident. */
6518         struct ref_list *refs;          /* Repository references. */
6519         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6520         size_t graph_size;              /* The width of the graph array. */
6521         bool has_parents;               /* Rewritten --parents seen. */
6522 };
6524 /* Size of rev graph with no  "padding" columns */
6525 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6527 struct rev_graph {
6528         struct rev_graph *prev, *next, *parents;
6529         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6530         size_t size;
6531         struct commit *commit;
6532         size_t pos;
6533         unsigned int boundary:1;
6534 };
6536 /* Parents of the commit being visualized. */
6537 static struct rev_graph graph_parents[4];
6539 /* The current stack of revisions on the graph. */
6540 static struct rev_graph graph_stacks[4] = {
6541         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6542         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6543         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6544         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6545 };
6547 static inline bool
6548 graph_parent_is_merge(struct rev_graph *graph)
6550         return graph->parents->size > 1;
6553 static inline void
6554 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6556         struct commit *commit = graph->commit;
6558         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6559                 commit->graph[commit->graph_size++] = symbol;
6562 static void
6563 clear_rev_graph(struct rev_graph *graph)
6565         graph->boundary = 0;
6566         graph->size = graph->pos = 0;
6567         graph->commit = NULL;
6568         memset(graph->parents, 0, sizeof(*graph->parents));
6571 static void
6572 done_rev_graph(struct rev_graph *graph)
6574         if (graph_parent_is_merge(graph) &&
6575             graph->pos < graph->size - 1 &&
6576             graph->next->size == graph->size + graph->parents->size - 1) {
6577                 size_t i = graph->pos + graph->parents->size - 1;
6579                 graph->commit->graph_size = i * 2;
6580                 while (i < graph->next->size - 1) {
6581                         append_to_rev_graph(graph, ' ');
6582                         append_to_rev_graph(graph, '\\');
6583                         i++;
6584                 }
6585         }
6587         clear_rev_graph(graph);
6590 static void
6591 push_rev_graph(struct rev_graph *graph, const char *parent)
6593         int i;
6595         /* "Collapse" duplicate parents lines.
6596          *
6597          * FIXME: This needs to also update update the drawn graph but
6598          * for now it just serves as a method for pruning graph lines. */
6599         for (i = 0; i < graph->size; i++)
6600                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6601                         return;
6603         if (graph->size < SIZEOF_REVITEMS) {
6604                 string_copy_rev(graph->rev[graph->size++], parent);
6605         }
6608 static chtype
6609 get_rev_graph_symbol(struct rev_graph *graph)
6611         chtype symbol;
6613         if (graph->boundary)
6614                 symbol = REVGRAPH_BOUND;
6615         else if (graph->parents->size == 0)
6616                 symbol = REVGRAPH_INIT;
6617         else if (graph_parent_is_merge(graph))
6618                 symbol = REVGRAPH_MERGE;
6619         else if (graph->pos >= graph->size)
6620                 symbol = REVGRAPH_BRANCH;
6621         else
6622                 symbol = REVGRAPH_COMMIT;
6624         return symbol;
6627 static void
6628 draw_rev_graph(struct rev_graph *graph)
6630         struct rev_filler {
6631                 chtype separator, line;
6632         };
6633         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6634         static struct rev_filler fillers[] = {
6635                 { ' ',  '|' },
6636                 { '`',  '.' },
6637                 { '\'', ' ' },
6638                 { '/',  ' ' },
6639         };
6640         chtype symbol = get_rev_graph_symbol(graph);
6641         struct rev_filler *filler;
6642         size_t i;
6644         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6645         filler = &fillers[DEFAULT];
6647         for (i = 0; i < graph->pos; i++) {
6648                 append_to_rev_graph(graph, filler->line);
6649                 if (graph_parent_is_merge(graph->prev) &&
6650                     graph->prev->pos == i)
6651                         filler = &fillers[RSHARP];
6653                 append_to_rev_graph(graph, filler->separator);
6654         }
6656         /* Place the symbol for this revision. */
6657         append_to_rev_graph(graph, symbol);
6659         if (graph->prev->size > graph->size)
6660                 filler = &fillers[RDIAG];
6661         else
6662                 filler = &fillers[DEFAULT];
6664         i++;
6666         for (; i < graph->size; i++) {
6667                 append_to_rev_graph(graph, filler->separator);
6668                 append_to_rev_graph(graph, filler->line);
6669                 if (graph_parent_is_merge(graph->prev) &&
6670                     i < graph->prev->pos + graph->parents->size)
6671                         filler = &fillers[RSHARP];
6672                 if (graph->prev->size > graph->size)
6673                         filler = &fillers[LDIAG];
6674         }
6676         if (graph->prev->size > graph->size) {
6677                 append_to_rev_graph(graph, filler->separator);
6678                 if (filler->line != ' ')
6679                         append_to_rev_graph(graph, filler->line);
6680         }
6683 /* Prepare the next rev graph */
6684 static void
6685 prepare_rev_graph(struct rev_graph *graph)
6687         size_t i;
6689         /* First, traverse all lines of revisions up to the active one. */
6690         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6691                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6692                         break;
6694                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6695         }
6697         /* Interleave the new revision parent(s). */
6698         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6699                 push_rev_graph(graph->next, graph->parents->rev[i]);
6701         /* Lastly, put any remaining revisions. */
6702         for (i = graph->pos + 1; i < graph->size; i++)
6703                 push_rev_graph(graph->next, graph->rev[i]);
6706 static void
6707 update_rev_graph(struct view *view, struct rev_graph *graph)
6709         /* If this is the finalizing update ... */
6710         if (graph->commit)
6711                 prepare_rev_graph(graph);
6713         /* Graph visualization needs a one rev look-ahead,
6714          * so the first update doesn't visualize anything. */
6715         if (!graph->prev->commit)
6716                 return;
6718         if (view->lines > 2)
6719                 view->line[view->lines - 3].dirty = 1;
6720         if (view->lines > 1)
6721                 view->line[view->lines - 2].dirty = 1;
6722         draw_rev_graph(graph->prev);
6723         done_rev_graph(graph->prev->prev);
6727 /*
6728  * Main view backend
6729  */
6731 static const char *main_argv[SIZEOF_ARG] = {
6732         "git", "log", "--no-color", "--pretty=raw", "--parents",
6733                       "--topo-order", "%(head)", NULL
6734 };
6736 static bool
6737 main_draw(struct view *view, struct line *line, unsigned int lineno)
6739         struct commit *commit = line->data;
6741         if (!commit->author)
6742                 return FALSE;
6744         if (opt_date && draw_date(view, &commit->time))
6745                 return TRUE;
6747         if (opt_author && draw_author(view, commit->author))
6748                 return TRUE;
6750         if (opt_rev_graph && commit->graph_size &&
6751             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6752                 return TRUE;
6754         if (opt_show_refs && commit->refs) {
6755                 size_t i;
6757                 for (i = 0; i < commit->refs->size; i++) {
6758                         struct ref *ref = commit->refs->refs[i];
6759                         enum line_type type;
6761                         if (ref->head)
6762                                 type = LINE_MAIN_HEAD;
6763                         else if (ref->ltag)
6764                                 type = LINE_MAIN_LOCAL_TAG;
6765                         else if (ref->tag)
6766                                 type = LINE_MAIN_TAG;
6767                         else if (ref->tracked)
6768                                 type = LINE_MAIN_TRACKED;
6769                         else if (ref->remote)
6770                                 type = LINE_MAIN_REMOTE;
6771                         else
6772                                 type = LINE_MAIN_REF;
6774                         if (draw_text(view, type, "[", TRUE) ||
6775                             draw_text(view, type, ref->name, TRUE) ||
6776                             draw_text(view, type, "]", TRUE))
6777                                 return TRUE;
6779                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6780                                 return TRUE;
6781                 }
6782         }
6784         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6785         return TRUE;
6788 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6789 static bool
6790 main_read(struct view *view, char *line)
6792         static struct rev_graph *graph = graph_stacks;
6793         enum line_type type;
6794         struct commit *commit;
6796         if (!line) {
6797                 int i;
6799                 if (!view->lines && !view->prev)
6800                         die("No revisions match the given arguments.");
6801                 if (view->lines > 0) {
6802                         commit = view->line[view->lines - 1].data;
6803                         view->line[view->lines - 1].dirty = 1;
6804                         if (!commit->author) {
6805                                 view->lines--;
6806                                 free(commit);
6807                                 graph->commit = NULL;
6808                         }
6809                 }
6810                 update_rev_graph(view, graph);
6812                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6813                         clear_rev_graph(&graph_stacks[i]);
6814                 return TRUE;
6815         }
6817         type = get_line_type(line);
6818         if (type == LINE_COMMIT) {
6819                 commit = calloc(1, sizeof(struct commit));
6820                 if (!commit)
6821                         return FALSE;
6823                 line += STRING_SIZE("commit ");
6824                 if (*line == '-') {
6825                         graph->boundary = 1;
6826                         line++;
6827                 }
6829                 string_copy_rev(commit->id, line);
6830                 commit->refs = get_ref_list(commit->id);
6831                 graph->commit = commit;
6832                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6834                 while ((line = strchr(line, ' '))) {
6835                         line++;
6836                         push_rev_graph(graph->parents, line);
6837                         commit->has_parents = TRUE;
6838                 }
6839                 return TRUE;
6840         }
6842         if (!view->lines)
6843                 return TRUE;
6844         commit = view->line[view->lines - 1].data;
6846         switch (type) {
6847         case LINE_PARENT:
6848                 if (commit->has_parents)
6849                         break;
6850                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6851                 break;
6853         case LINE_AUTHOR:
6854                 parse_author_line(line + STRING_SIZE("author "),
6855                                   &commit->author, &commit->time);
6856                 update_rev_graph(view, graph);
6857                 graph = graph->next;
6858                 break;
6860         default:
6861                 /* Fill in the commit title if it has not already been set. */
6862                 if (commit->title[0])
6863                         break;
6865                 /* Require titles to start with a non-space character at the
6866                  * offset used by git log. */
6867                 if (strncmp(line, "    ", 4))
6868                         break;
6869                 line += 4;
6870                 /* Well, if the title starts with a whitespace character,
6871                  * try to be forgiving.  Otherwise we end up with no title. */
6872                 while (isspace(*line))
6873                         line++;
6874                 if (*line == '\0')
6875                         break;
6876                 /* FIXME: More graceful handling of titles; append "..." to
6877                  * shortened titles, etc. */
6879                 string_expand(commit->title, sizeof(commit->title), line, 1);
6880                 view->line[view->lines - 1].dirty = 1;
6881         }
6883         return TRUE;
6886 static enum request
6887 main_request(struct view *view, enum request request, struct line *line)
6889         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6891         switch (request) {
6892         case REQ_ENTER:
6893                 open_view(view, REQ_VIEW_DIFF, flags);
6894                 break;
6895         case REQ_REFRESH:
6896                 load_refs();
6897                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6898                 break;
6899         default:
6900                 return request;
6901         }
6903         return REQ_NONE;
6906 static bool
6907 grep_refs(struct ref_list *list, regex_t *regex)
6909         regmatch_t pmatch;
6910         size_t i;
6912         if (!opt_show_refs || !list)
6913                 return FALSE;
6915         for (i = 0; i < list->size; i++) {
6916                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6917                         return TRUE;
6918         }
6920         return FALSE;
6923 static bool
6924 main_grep(struct view *view, struct line *line)
6926         struct commit *commit = line->data;
6927         const char *text[] = {
6928                 commit->title,
6929                 opt_author ? commit->author : "",
6930                 mkdate(&commit->time, opt_date),
6931                 NULL
6932         };
6934         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6937 static void
6938 main_select(struct view *view, struct line *line)
6940         struct commit *commit = line->data;
6942         string_copy_rev(view->ref, commit->id);
6943         string_copy_rev(ref_commit, view->ref);
6946 static struct view_ops main_ops = {
6947         "commit",
6948         main_argv,
6949         NULL,
6950         main_read,
6951         main_draw,
6952         main_request,
6953         main_grep,
6954         main_select,
6955 };
6958 /*
6959  * Status management
6960  */
6962 /* Whether or not the curses interface has been initialized. */
6963 static bool cursed = FALSE;
6965 /* Terminal hacks and workarounds. */
6966 static bool use_scroll_redrawwin;
6967 static bool use_scroll_status_wclear;
6969 /* The status window is used for polling keystrokes. */
6970 static WINDOW *status_win;
6972 /* Reading from the prompt? */
6973 static bool input_mode = FALSE;
6975 static bool status_empty = FALSE;
6977 /* Update status and title window. */
6978 static void
6979 report(const char *msg, ...)
6981         struct view *view = display[current_view];
6983         if (input_mode)
6984                 return;
6986         if (!view) {
6987                 char buf[SIZEOF_STR];
6988                 va_list args;
6990                 va_start(args, msg);
6991                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6992                         buf[sizeof(buf) - 1] = 0;
6993                         buf[sizeof(buf) - 2] = '.';
6994                         buf[sizeof(buf) - 3] = '.';
6995                         buf[sizeof(buf) - 4] = '.';
6996                 }
6997                 va_end(args);
6998                 die("%s", buf);
6999         }
7001         if (!status_empty || *msg) {
7002                 va_list args;
7004                 va_start(args, msg);
7006                 wmove(status_win, 0, 0);
7007                 if (view->has_scrolled && use_scroll_status_wclear)
7008                         wclear(status_win);
7009                 if (*msg) {
7010                         vwprintw(status_win, msg, args);
7011                         status_empty = FALSE;
7012                 } else {
7013                         status_empty = TRUE;
7014                 }
7015                 wclrtoeol(status_win);
7016                 wnoutrefresh(status_win);
7018                 va_end(args);
7019         }
7021         update_view_title(view);
7024 static void
7025 init_display(void)
7027         const char *term;
7028         int x, y;
7030         /* Initialize the curses library */
7031         if (isatty(STDIN_FILENO)) {
7032                 cursed = !!initscr();
7033                 opt_tty = stdin;
7034         } else {
7035                 /* Leave stdin and stdout alone when acting as a pager. */
7036                 opt_tty = fopen("/dev/tty", "r+");
7037                 if (!opt_tty)
7038                         die("Failed to open /dev/tty");
7039                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7040         }
7042         if (!cursed)
7043                 die("Failed to initialize curses");
7045         nonl();         /* Disable conversion and detect newlines from input. */
7046         cbreak();       /* Take input chars one at a time, no wait for \n */
7047         noecho();       /* Don't echo input */
7048         leaveok(stdscr, FALSE);
7050         if (has_colors())
7051                 init_colors();
7053         getmaxyx(stdscr, y, x);
7054         status_win = newwin(1, 0, y - 1, 0);
7055         if (!status_win)
7056                 die("Failed to create status window");
7058         /* Enable keyboard mapping */
7059         keypad(status_win, TRUE);
7060         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7062         TABSIZE = opt_tab_size;
7064         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7065         if (term && !strcmp(term, "gnome-terminal")) {
7066                 /* In the gnome-terminal-emulator, the message from
7067                  * scrolling up one line when impossible followed by
7068                  * scrolling down one line causes corruption of the
7069                  * status line. This is fixed by calling wclear. */
7070                 use_scroll_status_wclear = TRUE;
7071                 use_scroll_redrawwin = FALSE;
7073         } else if (term && !strcmp(term, "xrvt-xpm")) {
7074                 /* No problems with full optimizations in xrvt-(unicode)
7075                  * and aterm. */
7076                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7078         } else {
7079                 /* When scrolling in (u)xterm the last line in the
7080                  * scrolling direction will update slowly. */
7081                 use_scroll_redrawwin = TRUE;
7082                 use_scroll_status_wclear = FALSE;
7083         }
7086 static int
7087 get_input(int prompt_position)
7089         struct view *view;
7090         int i, key, cursor_y, cursor_x;
7091         bool loading = FALSE;
7093         if (prompt_position)
7094                 input_mode = TRUE;
7096         while (TRUE) {
7097                 foreach_view (view, i) {
7098                         update_view(view);
7099                         if (view_is_displayed(view) && view->has_scrolled &&
7100                             use_scroll_redrawwin)
7101                                 redrawwin(view->win);
7102                         view->has_scrolled = FALSE;
7103                         if (view->pipe)
7104                                 loading = TRUE;
7105                 }
7107                 /* Update the cursor position. */
7108                 if (prompt_position) {
7109                         getbegyx(status_win, cursor_y, cursor_x);
7110                         cursor_x = prompt_position;
7111                 } else {
7112                         view = display[current_view];
7113                         getbegyx(view->win, cursor_y, cursor_x);
7114                         cursor_x = view->width - 1;
7115                         cursor_y += view->lineno - view->offset;
7116                 }
7117                 setsyx(cursor_y, cursor_x);
7119                 /* Refresh, accept single keystroke of input */
7120                 doupdate();
7121                 nodelay(status_win, loading);
7122                 key = wgetch(status_win);
7124                 /* wgetch() with nodelay() enabled returns ERR when
7125                  * there's no input. */
7126                 if (key == ERR) {
7128                 } else if (key == KEY_RESIZE) {
7129                         int height, width;
7131                         getmaxyx(stdscr, height, width);
7133                         wresize(status_win, 1, width);
7134                         mvwin(status_win, height - 1, 0);
7135                         wnoutrefresh(status_win);
7136                         resize_display();
7137                         redraw_display(TRUE);
7139                 } else {
7140                         input_mode = FALSE;
7141                         return key;
7142                 }
7143         }
7146 static char *
7147 prompt_input(const char *prompt, input_handler handler, void *data)
7149         enum input_status status = INPUT_OK;
7150         static char buf[SIZEOF_STR];
7151         size_t pos = 0;
7153         buf[pos] = 0;
7155         while (status == INPUT_OK || status == INPUT_SKIP) {
7156                 int key;
7158                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7159                 wclrtoeol(status_win);
7161                 key = get_input(pos + 1);
7162                 switch (key) {
7163                 case KEY_RETURN:
7164                 case KEY_ENTER:
7165                 case '\n':
7166                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7167                         break;
7169                 case KEY_BACKSPACE:
7170                         if (pos > 0)
7171                                 buf[--pos] = 0;
7172                         else
7173                                 status = INPUT_CANCEL;
7174                         break;
7176                 case KEY_ESC:
7177                         status = INPUT_CANCEL;
7178                         break;
7180                 default:
7181                         if (pos >= sizeof(buf)) {
7182                                 report("Input string too long");
7183                                 return NULL;
7184                         }
7186                         status = handler(data, buf, key);
7187                         if (status == INPUT_OK)
7188                                 buf[pos++] = (char) key;
7189                 }
7190         }
7192         /* Clear the status window */
7193         status_empty = FALSE;
7194         report("");
7196         if (status == INPUT_CANCEL)
7197                 return NULL;
7199         buf[pos++] = 0;
7201         return buf;
7204 static enum input_status
7205 prompt_yesno_handler(void *data, char *buf, int c)
7207         if (c == 'y' || c == 'Y')
7208                 return INPUT_STOP;
7209         if (c == 'n' || c == 'N')
7210                 return INPUT_CANCEL;
7211         return INPUT_SKIP;
7214 static bool
7215 prompt_yesno(const char *prompt)
7217         char prompt2[SIZEOF_STR];
7219         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7220                 return FALSE;
7222         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7225 static enum input_status
7226 read_prompt_handler(void *data, char *buf, int c)
7228         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7231 static char *
7232 read_prompt(const char *prompt)
7234         return prompt_input(prompt, read_prompt_handler, NULL);
7237 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7239         enum input_status status = INPUT_OK;
7240         int size = 0;
7242         while (items[size].text)
7243                 size++;
7245         while (status == INPUT_OK) {
7246                 const struct menu_item *item = &items[*selected];
7247                 int key;
7248                 int i;
7250                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7251                           prompt, *selected + 1, size);
7252                 if (item->hotkey)
7253                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7254                 wprintw(status_win, "%s", item->text);
7255                 wclrtoeol(status_win);
7257                 key = get_input(COLS - 1);
7258                 switch (key) {
7259                 case KEY_RETURN:
7260                 case KEY_ENTER:
7261                 case '\n':
7262                         status = INPUT_STOP;
7263                         break;
7265                 case KEY_LEFT:
7266                 case KEY_UP:
7267                         *selected = *selected - 1;
7268                         if (*selected < 0)
7269                                 *selected = size - 1;
7270                         break;
7272                 case KEY_RIGHT:
7273                 case KEY_DOWN:
7274                         *selected = (*selected + 1) % size;
7275                         break;
7277                 case KEY_ESC:
7278                         status = INPUT_CANCEL;
7279                         break;
7281                 default:
7282                         for (i = 0; items[i].text; i++)
7283                                 if (items[i].hotkey == key) {
7284                                         *selected = i;
7285                                         status = INPUT_STOP;
7286                                         break;
7287                                 }
7288                 }
7289         }
7291         /* Clear the status window */
7292         status_empty = FALSE;
7293         report("");
7295         return status != INPUT_CANCEL;
7298 /*
7299  * Repository properties
7300  */
7302 static struct ref **refs = NULL;
7303 static size_t refs_size = 0;
7304 static struct ref *refs_head = NULL;
7306 static struct ref_list **ref_lists = NULL;
7307 static size_t ref_lists_size = 0;
7309 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7310 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7311 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7313 static int
7314 compare_refs(const void *ref1_, const void *ref2_)
7316         const struct ref *ref1 = *(const struct ref **)ref1_;
7317         const struct ref *ref2 = *(const struct ref **)ref2_;
7319         if (ref1->tag != ref2->tag)
7320                 return ref2->tag - ref1->tag;
7321         if (ref1->ltag != ref2->ltag)
7322                 return ref2->ltag - ref2->ltag;
7323         if (ref1->head != ref2->head)
7324                 return ref2->head - ref1->head;
7325         if (ref1->tracked != ref2->tracked)
7326                 return ref2->tracked - ref1->tracked;
7327         if (ref1->remote != ref2->remote)
7328                 return ref2->remote - ref1->remote;
7329         return strcmp(ref1->name, ref2->name);
7332 static void
7333 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7335         size_t i;
7337         for (i = 0; i < refs_size; i++)
7338                 if (!visitor(data, refs[i]))
7339                         break;
7342 static struct ref *
7343 get_ref_head()
7345         return refs_head;
7348 static struct ref_list *
7349 get_ref_list(const char *id)
7351         struct ref_list *list;
7352         size_t i;
7354         for (i = 0; i < ref_lists_size; i++)
7355                 if (!strcmp(id, ref_lists[i]->id))
7356                         return ref_lists[i];
7358         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7359                 return NULL;
7360         list = calloc(1, sizeof(*list));
7361         if (!list)
7362                 return NULL;
7364         for (i = 0; i < refs_size; i++) {
7365                 if (!strcmp(id, refs[i]->id) &&
7366                     realloc_refs_list(&list->refs, list->size, 1))
7367                         list->refs[list->size++] = refs[i];
7368         }
7370         if (!list->refs) {
7371                 free(list);
7372                 return NULL;
7373         }
7375         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7376         ref_lists[ref_lists_size++] = list;
7377         return list;
7380 static int
7381 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7383         struct ref *ref = NULL;
7384         bool tag = FALSE;
7385         bool ltag = FALSE;
7386         bool remote = FALSE;
7387         bool tracked = FALSE;
7388         bool head = FALSE;
7389         int from = 0, to = refs_size - 1;
7391         if (!prefixcmp(name, "refs/tags/")) {
7392                 if (!suffixcmp(name, namelen, "^{}")) {
7393                         namelen -= 3;
7394                         name[namelen] = 0;
7395                 } else {
7396                         ltag = TRUE;
7397                 }
7399                 tag = TRUE;
7400                 namelen -= STRING_SIZE("refs/tags/");
7401                 name    += STRING_SIZE("refs/tags/");
7403         } else if (!prefixcmp(name, "refs/remotes/")) {
7404                 remote = TRUE;
7405                 namelen -= STRING_SIZE("refs/remotes/");
7406                 name    += STRING_SIZE("refs/remotes/");
7407                 tracked  = !strcmp(opt_remote, name);
7409         } else if (!prefixcmp(name, "refs/heads/")) {
7410                 namelen -= STRING_SIZE("refs/heads/");
7411                 name    += STRING_SIZE("refs/heads/");
7412                 if (!strncmp(opt_head, name, namelen))
7413                         return OK;
7415         } else if (!strcmp(name, "HEAD")) {
7416                 head     = TRUE;
7417                 if (*opt_head) {
7418                         namelen  = strlen(opt_head);
7419                         name     = opt_head;
7420                 }
7421         }
7423         /* If we are reloading or it's an annotated tag, replace the
7424          * previous SHA1 with the resolved commit id; relies on the fact
7425          * git-ls-remote lists the commit id of an annotated tag right
7426          * before the commit id it points to. */
7427         while (from <= to) {
7428                 size_t pos = (to + from) / 2;
7429                 int cmp = strcmp(name, refs[pos]->name);
7431                 if (!cmp) {
7432                         ref = refs[pos];
7433                         break;
7434                 }
7436                 if (cmp < 0)
7437                         to = pos - 1;
7438                 else
7439                         from = pos + 1;
7440         }
7442         if (!ref) {
7443                 if (!realloc_refs(&refs, refs_size, 1))
7444                         return ERR;
7445                 ref = calloc(1, sizeof(*ref) + namelen);
7446                 if (!ref)
7447                         return ERR;
7448                 memmove(refs + from + 1, refs + from,
7449                         (refs_size - from) * sizeof(*refs));
7450                 refs[from] = ref;
7451                 strncpy(ref->name, name, namelen);
7452                 refs_size++;
7453         }
7455         ref->head = head;
7456         ref->tag = tag;
7457         ref->ltag = ltag;
7458         ref->remote = remote;
7459         ref->tracked = tracked;
7460         string_copy_rev(ref->id, id);
7462         if (head)
7463                 refs_head = ref;
7464         return OK;
7467 static int
7468 load_refs(void)
7470         const char *head_argv[] = {
7471                 "git", "symbolic-ref", "HEAD", NULL
7472         };
7473         static const char *ls_remote_argv[SIZEOF_ARG] = {
7474                 "git", "ls-remote", opt_git_dir, NULL
7475         };
7476         static bool init = FALSE;
7477         size_t i;
7479         if (!init) {
7480                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7481                         die("TIG_LS_REMOTE contains too many arguments");
7482                 init = TRUE;
7483         }
7485         if (!*opt_git_dir)
7486                 return OK;
7488         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7489             !prefixcmp(opt_head, "refs/heads/")) {
7490                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7492                 memmove(opt_head, offset, strlen(offset) + 1);
7493         }
7495         refs_head = NULL;
7496         for (i = 0; i < refs_size; i++)
7497                 refs[i]->id[0] = 0;
7499         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7500                 return ERR;
7502         /* Update the ref lists to reflect changes. */
7503         for (i = 0; i < ref_lists_size; i++) {
7504                 struct ref_list *list = ref_lists[i];
7505                 size_t old, new;
7507                 for (old = new = 0; old < list->size; old++)
7508                         if (!strcmp(list->id, list->refs[old]->id))
7509                                 list->refs[new++] = list->refs[old];
7510                 list->size = new;
7511         }
7513         return OK;
7516 static void
7517 set_remote_branch(const char *name, const char *value, size_t valuelen)
7519         if (!strcmp(name, ".remote")) {
7520                 string_ncopy(opt_remote, value, valuelen);
7522         } else if (*opt_remote && !strcmp(name, ".merge")) {
7523                 size_t from = strlen(opt_remote);
7525                 if (!prefixcmp(value, "refs/heads/"))
7526                         value += STRING_SIZE("refs/heads/");
7528                 if (!string_format_from(opt_remote, &from, "/%s", value))
7529                         opt_remote[0] = 0;
7530         }
7533 static void
7534 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7536         const char *argv[SIZEOF_ARG] = { name, "=" };
7537         int argc = 1 + (cmd == option_set_command);
7538         int error = ERR;
7540         if (!argv_from_string(argv, &argc, value))
7541                 config_msg = "Too many option arguments";
7542         else
7543                 error = cmd(argc, argv);
7545         if (error == ERR)
7546                 warn("Option 'tig.%s': %s", name, config_msg);
7549 static bool
7550 set_environment_variable(const char *name, const char *value)
7552         size_t len = strlen(name) + 1 + strlen(value) + 1;
7553         char *env = malloc(len);
7555         if (env &&
7556             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7557             putenv(env) == 0)
7558                 return TRUE;
7559         free(env);
7560         return FALSE;
7563 static void
7564 set_work_tree(const char *value)
7566         char cwd[SIZEOF_STR];
7568         if (!getcwd(cwd, sizeof(cwd)))
7569                 die("Failed to get cwd path: %s", strerror(errno));
7570         if (chdir(opt_git_dir) < 0)
7571                 die("Failed to chdir(%s): %s", strerror(errno));
7572         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7573                 die("Failed to get git path: %s", strerror(errno));
7574         if (chdir(cwd) < 0)
7575                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7576         if (chdir(value) < 0)
7577                 die("Failed to chdir(%s): %s", value, strerror(errno));
7578         if (!getcwd(cwd, sizeof(cwd)))
7579                 die("Failed to get cwd path: %s", strerror(errno));
7580         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7581                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7582         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7583                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7584         opt_is_inside_work_tree = TRUE;
7587 static int
7588 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7590         if (!strcmp(name, "i18n.commitencoding"))
7591                 string_ncopy(opt_encoding, value, valuelen);
7593         else if (!strcmp(name, "core.editor"))
7594                 string_ncopy(opt_editor, value, valuelen);
7596         else if (!strcmp(name, "core.worktree"))
7597                 set_work_tree(value);
7599         else if (!prefixcmp(name, "tig.color."))
7600                 set_repo_config_option(name + 10, value, option_color_command);
7602         else if (!prefixcmp(name, "tig.bind."))
7603                 set_repo_config_option(name + 9, value, option_bind_command);
7605         else if (!prefixcmp(name, "tig."))
7606                 set_repo_config_option(name + 4, value, option_set_command);
7608         else if (*opt_head && !prefixcmp(name, "branch.") &&
7609                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7610                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7612         return OK;
7615 static int
7616 load_git_config(void)
7618         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7620         return io_run_load(config_list_argv, "=", read_repo_config_option);
7623 static int
7624 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7626         if (!opt_git_dir[0]) {
7627                 string_ncopy(opt_git_dir, name, namelen);
7629         } else if (opt_is_inside_work_tree == -1) {
7630                 /* This can be 3 different values depending on the
7631                  * version of git being used. If git-rev-parse does not
7632                  * understand --is-inside-work-tree it will simply echo
7633                  * the option else either "true" or "false" is printed.
7634                  * Default to true for the unknown case. */
7635                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7637         } else if (*name == '.') {
7638                 string_ncopy(opt_cdup, name, namelen);
7640         } else {
7641                 string_ncopy(opt_prefix, name, namelen);
7642         }
7644         return OK;
7647 static int
7648 load_repo_info(void)
7650         const char *rev_parse_argv[] = {
7651                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7652                         "--show-cdup", "--show-prefix", NULL
7653         };
7655         return io_run_load(rev_parse_argv, "=", read_repo_info);
7659 /*
7660  * Main
7661  */
7663 static const char usage[] =
7664 "tig " TIG_VERSION " (" __DATE__ ")\n"
7665 "\n"
7666 "Usage: tig        [options] [revs] [--] [paths]\n"
7667 "   or: tig show   [options] [revs] [--] [paths]\n"
7668 "   or: tig blame  [rev] path\n"
7669 "   or: tig status\n"
7670 "   or: tig <      [git command output]\n"
7671 "\n"
7672 "Options:\n"
7673 "  -v, --version   Show version and exit\n"
7674 "  -h, --help      Show help message and exit";
7676 static void __NORETURN
7677 quit(int sig)
7679         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7680         if (cursed)
7681                 endwin();
7682         exit(0);
7685 static void __NORETURN
7686 die(const char *err, ...)
7688         va_list args;
7690         endwin();
7692         va_start(args, err);
7693         fputs("tig: ", stderr);
7694         vfprintf(stderr, err, args);
7695         fputs("\n", stderr);
7696         va_end(args);
7698         exit(1);
7701 static void
7702 warn(const char *msg, ...)
7704         va_list args;
7706         va_start(args, msg);
7707         fputs("tig warning: ", stderr);
7708         vfprintf(stderr, msg, args);
7709         fputs("\n", stderr);
7710         va_end(args);
7713 static enum request
7714 parse_options(int argc, const char *argv[])
7716         enum request request = REQ_VIEW_MAIN;
7717         const char *subcommand;
7718         bool seen_dashdash = FALSE;
7719         /* XXX: This is vulnerable to the user overriding options
7720          * required for the main view parser. */
7721         const char *custom_argv[SIZEOF_ARG] = {
7722                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7723                         "--topo-order", NULL
7724         };
7725         int i, j = 6;
7727         if (!isatty(STDIN_FILENO)) {
7728                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7729                 return REQ_VIEW_PAGER;
7730         }
7732         if (argc <= 1)
7733                 return REQ_NONE;
7735         subcommand = argv[1];
7736         if (!strcmp(subcommand, "status")) {
7737                 if (argc > 2)
7738                         warn("ignoring arguments after `%s'", subcommand);
7739                 return REQ_VIEW_STATUS;
7741         } else if (!strcmp(subcommand, "blame")) {
7742                 if (argc <= 2 || argc > 4)
7743                         die("invalid number of options to blame\n\n%s", usage);
7745                 i = 2;
7746                 if (argc == 4) {
7747                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7748                         i++;
7749                 }
7751                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7752                 return REQ_VIEW_BLAME;
7754         } else if (!strcmp(subcommand, "show")) {
7755                 request = REQ_VIEW_DIFF;
7757         } else {
7758                 subcommand = NULL;
7759         }
7761         if (subcommand) {
7762                 custom_argv[1] = subcommand;
7763                 j = 2;
7764         }
7766         for (i = 1 + !!subcommand; i < argc; i++) {
7767                 const char *opt = argv[i];
7769                 if (seen_dashdash || !strcmp(opt, "--")) {
7770                         seen_dashdash = TRUE;
7772                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7773                         printf("tig version %s\n", TIG_VERSION);
7774                         quit(0);
7776                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7777                         printf("%s\n", usage);
7778                         quit(0);
7779                 }
7781                 custom_argv[j++] = opt;
7782                 if (j >= ARRAY_SIZE(custom_argv))
7783                         die("command too long");
7784         }
7786         if (!prepare_update(VIEW(request), custom_argv, NULL))
7787                 die("Failed to format arguments");
7789         return request;
7792 int
7793 main(int argc, const char *argv[])
7795         const char *codeset = "UTF-8";
7796         enum request request = parse_options(argc, argv);
7797         struct view *view;
7798         size_t i;
7800         signal(SIGINT, quit);
7801         signal(SIGPIPE, SIG_IGN);
7803         if (setlocale(LC_ALL, "")) {
7804                 codeset = nl_langinfo(CODESET);
7805         }
7807         if (load_repo_info() == ERR)
7808                 die("Failed to load repo info.");
7810         if (load_options() == ERR)
7811                 die("Failed to load user config.");
7813         if (load_git_config() == ERR)
7814                 die("Failed to load repo config.");
7816         /* Require a git repository unless when running in pager mode. */
7817         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7818                 die("Not a git repository");
7820         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7821                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7822                 if (opt_iconv_in == ICONV_NONE)
7823                         die("Failed to initialize character set conversion");
7824         }
7826         if (codeset && strcmp(codeset, "UTF-8")) {
7827                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7828                 if (opt_iconv_out == ICONV_NONE)
7829                         die("Failed to initialize character set conversion");
7830         }
7832         if (load_refs() == ERR)
7833                 die("Failed to load refs.");
7835         foreach_view (view, i)
7836                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7837                         die("Too many arguments in the `%s` environment variable",
7838                             view->cmd_env);
7840         init_display();
7842         if (request != REQ_NONE)
7843                 open_view(NULL, request, OPEN_PREPARED);
7844         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7846         while (view_driver(display[current_view], request)) {
7847                 int key = get_input(0);
7849                 view = display[current_view];
7850                 request = get_keybinding(view->keymap, key);
7852                 /* Some low-level request handling. This keeps access to
7853                  * status_win restricted. */
7854                 switch (request) {
7855                 case REQ_NONE:
7856                         report("Unknown key, press %s for help",
7857                                get_key(view->keymap, REQ_VIEW_HELP));
7858                         break;
7859                 case REQ_PROMPT:
7860                 {
7861                         char *cmd = read_prompt(":");
7863                         if (cmd && isdigit(*cmd)) {
7864                                 int lineno = view->lineno + 1;
7866                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7867                                         select_view_line(view, lineno - 1);
7868                                         report("");
7869                                 } else {
7870                                         report("Unable to parse '%s' as a line number", cmd);
7871                                 }
7873                         } else if (cmd) {
7874                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7875                                 const char *argv[SIZEOF_ARG] = { "git" };
7876                                 int argc = 1;
7878                                 /* When running random commands, initially show the
7879                                  * command in the title. However, it maybe later be
7880                                  * overwritten if a commit line is selected. */
7881                                 string_ncopy(next->ref, cmd, strlen(cmd));
7883                                 if (!argv_from_string(argv, &argc, cmd)) {
7884                                         report("Too many arguments");
7885                                 } else if (!prepare_update(next, argv, NULL)) {
7886                                         report("Failed to format command");
7887                                 } else {
7888                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7889                                 }
7890                         }
7892                         request = REQ_NONE;
7893                         break;
7894                 }
7895                 case REQ_SEARCH:
7896                 case REQ_SEARCH_BACK:
7897                 {
7898                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7899                         char *search = read_prompt(prompt);
7901                         if (search)
7902                                 string_ncopy(opt_search, search, strlen(search));
7903                         else if (*opt_search)
7904                                 request = request == REQ_SEARCH ?
7905                                         REQ_FIND_NEXT :
7906                                         REQ_FIND_PREV;
7907                         else
7908                                 request = REQ_NONE;
7909                         break;
7910                 }
7911                 default:
7912                         break;
7913                 }
7914         }
7916         quit(0);
7918         return 0;