Code

Add view_request to call the view request method
[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]);
691 /*
692  * Executing external commands.
693  */
695 enum io_type {
696         IO_FD,                  /* File descriptor based IO. */
697         IO_BG,                  /* Execute command in the background. */
698         IO_FG,                  /* Execute command with same std{in,out,err}. */
699         IO_RD,                  /* Read only fork+exec IO. */
700         IO_WR,                  /* Write only fork+exec IO. */
701         IO_AP,                  /* Append fork+exec output to file. */
702 };
704 struct io {
705         enum io_type type;      /* The requested type of pipe. */
706         const char *dir;        /* Directory from which to execute. */
707         pid_t pid;              /* PID of spawned process. */
708         int pipe;               /* Pipe end for reading or writing. */
709         int error;              /* Error status. */
710         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
711         char *buf;              /* Read buffer. */
712         size_t bufalloc;        /* Allocated buffer size. */
713         size_t bufsize;         /* Buffer content size. */
714         char *bufpos;           /* Current buffer position. */
715         unsigned int eof:1;     /* Has end of file been reached. */
716 };
718 static void
719 io_reset(struct io *io)
721         io->pipe = -1;
722         io->pid = 0;
723         io->buf = io->bufpos = NULL;
724         io->bufalloc = io->bufsize = 0;
725         io->error = 0;
726         io->eof = 0;
729 static void
730 io_init(struct io *io, const char *dir, enum io_type type)
732         io_reset(io);
733         io->type = type;
734         io->dir = dir;
737 static bool
738 io_format(struct io *io, const char *dir, enum io_type type,
739           const char *argv[], enum format_flags flags)
741         io_init(io, dir, type);
742         return format_argv(io->argv, argv, flags);
745 static bool
746 io_open(struct io *io, const char *fmt, ...)
748         char name[SIZEOF_STR] = "";
749         bool fits;
750         va_list args;
752         io_init(io, NULL, IO_FD);
754         va_start(args, fmt);
755         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
756         va_end(args);
758         if (!fits) {
759                 io->error = ENAMETOOLONG;
760                 return FALSE;
761         }
762         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
763         if (io->pipe == -1)
764                 io->error = errno;
765         return io->pipe != -1;
768 static bool
769 io_kill(struct io *io)
771         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
774 static bool
775 io_done(struct io *io)
777         pid_t pid = io->pid;
779         if (io->pipe != -1)
780                 close(io->pipe);
781         free(io->buf);
782         io_reset(io);
784         while (pid > 0) {
785                 int status;
786                 pid_t waiting = waitpid(pid, &status, 0);
788                 if (waiting < 0) {
789                         if (errno == EINTR)
790                                 continue;
791                         io->error = errno;
792                         return FALSE;
793                 }
795                 return waiting == pid &&
796                        !WIFSIGNALED(status) &&
797                        WIFEXITED(status) &&
798                        !WEXITSTATUS(status);
799         }
801         return TRUE;
804 static bool
805 io_start(struct io *io)
807         int pipefds[2] = { -1, -1 };
809         if (io->type == IO_FD)
810                 return TRUE;
812         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
813                 io->error = errno;
814                 return FALSE;
815         } else if (io->type == IO_AP) {
816                 pipefds[1] = io->pipe;
817         }
819         if ((io->pid = fork())) {
820                 if (io->pid == -1)
821                         io->error = errno;
822                 if (pipefds[!(io->type == IO_WR)] != -1)
823                         close(pipefds[!(io->type == IO_WR)]);
824                 if (io->pid != -1) {
825                         io->pipe = pipefds[!!(io->type == IO_WR)];
826                         return TRUE;
827                 }
829         } else {
830                 if (io->type != IO_FG) {
831                         int devnull = open("/dev/null", O_RDWR);
832                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
833                         int writefd = (io->type == IO_RD || io->type == IO_AP)
834                                                         ? pipefds[1] : devnull;
836                         dup2(readfd,  STDIN_FILENO);
837                         dup2(writefd, STDOUT_FILENO);
838                         dup2(devnull, STDERR_FILENO);
840                         close(devnull);
841                         if (pipefds[0] != -1)
842                                 close(pipefds[0]);
843                         if (pipefds[1] != -1)
844                                 close(pipefds[1]);
845                 }
847                 if (io->dir && *io->dir && chdir(io->dir) == -1)
848                         exit(errno);
850                 execvp(io->argv[0], (char *const*) io->argv);
851                 exit(errno);
852         }
854         if (pipefds[!!(io->type == IO_WR)] != -1)
855                 close(pipefds[!!(io->type == IO_WR)]);
856         return FALSE;
859 static bool
860 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
862         io_init(io, dir, type);
863         if (!format_argv(io->argv, argv, FORMAT_NONE))
864                 return FALSE;
865         return io_start(io);
868 static int
869 io_complete(struct io *io)
871         return io_start(io) && io_done(io);
874 static int
875 io_run_bg(const char **argv)
877         struct io io = {};
879         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
880                 return FALSE;
881         return io_complete(&io);
884 static bool
885 io_run_fg(const char **argv, const char *dir)
887         struct io io = {};
889         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
890                 return FALSE;
891         return io_complete(&io);
894 static bool
895 io_run_append(const char **argv, enum format_flags flags, int fd)
897         struct io io = {};
899         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
900                 close(fd);
901                 return FALSE;
902         }
904         io.pipe = fd;
905         return io_complete(&io);
908 static bool
909 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
911         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
914 static bool
915 io_eof(struct io *io)
917         return io->eof;
920 static int
921 io_error(struct io *io)
923         return io->error;
926 static char *
927 io_strerror(struct io *io)
929         return strerror(io->error);
932 static bool
933 io_can_read(struct io *io)
935         struct timeval tv = { 0, 500 };
936         fd_set fds;
938         FD_ZERO(&fds);
939         FD_SET(io->pipe, &fds);
941         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
944 static ssize_t
945 io_read(struct io *io, void *buf, size_t bufsize)
947         do {
948                 ssize_t readsize = read(io->pipe, buf, bufsize);
950                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
951                         continue;
952                 else if (readsize == -1)
953                         io->error = errno;
954                 else if (readsize == 0)
955                         io->eof = 1;
956                 return readsize;
957         } while (1);
960 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
962 static char *
963 io_get(struct io *io, int c, bool can_read)
965         char *eol;
966         ssize_t readsize;
968         while (TRUE) {
969                 if (io->bufsize > 0) {
970                         eol = memchr(io->bufpos, c, io->bufsize);
971                         if (eol) {
972                                 char *line = io->bufpos;
974                                 *eol = 0;
975                                 io->bufpos = eol + 1;
976                                 io->bufsize -= io->bufpos - line;
977                                 return line;
978                         }
979                 }
981                 if (io_eof(io)) {
982                         if (io->bufsize) {
983                                 io->bufpos[io->bufsize] = 0;
984                                 io->bufsize = 0;
985                                 return io->bufpos;
986                         }
987                         return NULL;
988                 }
990                 if (!can_read)
991                         return NULL;
993                 if (io->bufsize > 0 && io->bufpos > io->buf)
994                         memmove(io->buf, io->bufpos, io->bufsize);
996                 if (io->bufalloc == io->bufsize) {
997                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
998                                 return NULL;
999                         io->bufalloc += BUFSIZ;
1000                 }
1002                 io->bufpos = io->buf;
1003                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1004                 if (io_error(io))
1005                         return NULL;
1006                 io->bufsize += readsize;
1007         }
1010 static bool
1011 io_write(struct io *io, const void *buf, size_t bufsize)
1013         size_t written = 0;
1015         while (!io_error(io) && written < bufsize) {
1016                 ssize_t size;
1018                 size = write(io->pipe, buf + written, bufsize - written);
1019                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1020                         continue;
1021                 else if (size == -1)
1022                         io->error = errno;
1023                 else
1024                         written += size;
1025         }
1027         return written == bufsize;
1030 static bool
1031 io_read_buf(struct io *io, char buf[], size_t bufsize)
1033         char *result = io_get(io, '\n', TRUE);
1035         if (result) {
1036                 result = chomp_string(result);
1037                 string_ncopy_do(buf, bufsize, result, strlen(result));
1038         }
1040         return io_done(io) && result;
1043 static bool
1044 io_run_buf(const char **argv, char buf[], size_t bufsize)
1046         struct io io = {};
1048         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1049             && io_read_buf(&io, buf, bufsize);
1052 static int
1053 io_load(struct io *io, const char *separators,
1054         int (*read_property)(char *, size_t, char *, size_t))
1056         char *name;
1057         int state = OK;
1059         if (!io_start(io))
1060                 return ERR;
1062         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1063                 char *value;
1064                 size_t namelen;
1065                 size_t valuelen;
1067                 name = chomp_string(name);
1068                 namelen = strcspn(name, separators);
1070                 if (name[namelen]) {
1071                         name[namelen] = 0;
1072                         value = chomp_string(name + namelen + 1);
1073                         valuelen = strlen(value);
1075                 } else {
1076                         value = "";
1077                         valuelen = 0;
1078                 }
1080                 state = read_property(name, namelen, value, valuelen);
1081         }
1083         if (state != ERR && io_error(io))
1084                 state = ERR;
1085         io_done(io);
1087         return state;
1090 static int
1091 io_run_load(const char **argv, const char *separators,
1092             int (*read_property)(char *, size_t, char *, size_t))
1094         struct io io = {};
1096         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1097                 ? io_load(&io, separators, read_property) : ERR;
1101 /*
1102  * User requests
1103  */
1105 #define REQ_INFO \
1106         /* XXX: Keep the view request first and in sync with views[]. */ \
1107         REQ_GROUP("View switching") \
1108         REQ_(VIEW_MAIN,         "Show main view"), \
1109         REQ_(VIEW_DIFF,         "Show diff view"), \
1110         REQ_(VIEW_LOG,          "Show log view"), \
1111         REQ_(VIEW_TREE,         "Show tree view"), \
1112         REQ_(VIEW_BLOB,         "Show blob view"), \
1113         REQ_(VIEW_BLAME,        "Show blame view"), \
1114         REQ_(VIEW_BRANCH,       "Show branch view"), \
1115         REQ_(VIEW_HELP,         "Show help page"), \
1116         REQ_(VIEW_PAGER,        "Show pager view"), \
1117         REQ_(VIEW_STATUS,       "Show status view"), \
1118         REQ_(VIEW_STAGE,        "Show stage view"), \
1119         \
1120         REQ_GROUP("View manipulation") \
1121         REQ_(ENTER,             "Enter current line and scroll"), \
1122         REQ_(NEXT,              "Move to next"), \
1123         REQ_(PREVIOUS,          "Move to previous"), \
1124         REQ_(PARENT,            "Move to parent"), \
1125         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1126         REQ_(REFRESH,           "Reload and refresh"), \
1127         REQ_(MAXIMIZE,          "Maximize the current view"), \
1128         REQ_(VIEW_CLOSE,        "Close the current view"), \
1129         REQ_(QUIT,              "Close all views and quit"), \
1130         \
1131         REQ_GROUP("View specific requests") \
1132         REQ_(STATUS_UPDATE,     "Update file status"), \
1133         REQ_(STATUS_REVERT,     "Revert file changes"), \
1134         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1135         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1136         \
1137         REQ_GROUP("Cursor navigation") \
1138         REQ_(MOVE_UP,           "Move cursor one line up"), \
1139         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1140         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1141         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1142         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1143         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1144         \
1145         REQ_GROUP("Scrolling") \
1146         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1147         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1148         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1149         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1150         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1151         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1152         \
1153         REQ_GROUP("Searching") \
1154         REQ_(SEARCH,            "Search the view"), \
1155         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1156         REQ_(FIND_NEXT,         "Find next search match"), \
1157         REQ_(FIND_PREV,         "Find previous search match"), \
1158         \
1159         REQ_GROUP("Option manipulation") \
1160         REQ_(OPTIONS,           "Open option menu"), \
1161         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1162         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1163         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1164         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1165         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1166         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1167         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1168         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1169         \
1170         REQ_GROUP("Misc") \
1171         REQ_(PROMPT,            "Bring up the prompt"), \
1172         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1173         REQ_(SHOW_VERSION,      "Show version information"), \
1174         REQ_(STOP_LOADING,      "Stop all loading views"), \
1175         REQ_(EDIT,              "Open in editor"), \
1176         REQ_(NONE,              "Do nothing")
1179 /* User action requests. */
1180 enum request {
1181 #define REQ_GROUP(help)
1182 #define REQ_(req, help) REQ_##req
1184         /* Offset all requests to avoid conflicts with ncurses getch values. */
1185         REQ_UNKNOWN = KEY_MAX + 1,
1186         REQ_OFFSET,
1187         REQ_INFO
1189 #undef  REQ_GROUP
1190 #undef  REQ_
1191 };
1193 struct request_info {
1194         enum request request;
1195         const char *name;
1196         int namelen;
1197         const char *help;
1198 };
1200 static const struct request_info req_info[] = {
1201 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1202 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1203         REQ_INFO
1204 #undef  REQ_GROUP
1205 #undef  REQ_
1206 };
1208 static enum request
1209 get_request(const char *name)
1211         int namelen = strlen(name);
1212         int i;
1214         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1215                 if (enum_equals(req_info[i], name, namelen))
1216                         return req_info[i].request;
1218         return REQ_UNKNOWN;
1222 /*
1223  * Options
1224  */
1226 /* Option and state variables. */
1227 static enum date opt_date               = DATE_DEFAULT;
1228 static enum author opt_author           = AUTHOR_DEFAULT;
1229 static bool opt_line_number             = FALSE;
1230 static bool opt_line_graphics           = TRUE;
1231 static bool opt_rev_graph               = FALSE;
1232 static bool opt_show_refs               = TRUE;
1233 static int opt_num_interval             = 5;
1234 static double opt_hscroll               = 0.50;
1235 static double opt_scale_split_view      = 2.0 / 3.0;
1236 static int opt_tab_size                 = 8;
1237 static int opt_author_cols              = AUTHOR_COLS;
1238 static char opt_path[SIZEOF_STR]        = "";
1239 static char opt_file[SIZEOF_STR]        = "";
1240 static char opt_ref[SIZEOF_REF]         = "";
1241 static char opt_head[SIZEOF_REF]        = "";
1242 static char opt_remote[SIZEOF_REF]      = "";
1243 static char opt_encoding[20]            = "UTF-8";
1244 static iconv_t opt_iconv_in             = ICONV_NONE;
1245 static iconv_t opt_iconv_out            = ICONV_NONE;
1246 static char opt_search[SIZEOF_STR]      = "";
1247 static char opt_cdup[SIZEOF_STR]        = "";
1248 static char opt_prefix[SIZEOF_STR]      = "";
1249 static char opt_git_dir[SIZEOF_STR]     = "";
1250 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1251 static char opt_editor[SIZEOF_STR]      = "";
1252 static FILE *opt_tty                    = NULL;
1254 #define is_initial_commit()     (!get_ref_head())
1255 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1258 /*
1259  * Line-oriented content detection.
1260  */
1262 #define LINE_INFO \
1263 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1264 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1265 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1266 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1267 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1268 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1269 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1270 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1272 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1273 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1274 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1275 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1276 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1277 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1278 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1279 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1280 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1281 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1282 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1283 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1284 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1285 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1286 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1287 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1288 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1289 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1290 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1291 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1292 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1293 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1294 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1295 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1296 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1297 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1298 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1299 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1300 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1301 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1302 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1303 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1304 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1305 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1306 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1307 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1308 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1309 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1310 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1311 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1312 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1313 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1314 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1315 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1316 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1317 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1318 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1319 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1320 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1321 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1323 enum line_type {
1324 #define LINE(type, line, fg, bg, attr) \
1325         LINE_##type
1326         LINE_INFO,
1327         LINE_NONE
1328 #undef  LINE
1329 };
1331 struct line_info {
1332         const char *name;       /* Option name. */
1333         int namelen;            /* Size of option name. */
1334         const char *line;       /* The start of line to match. */
1335         int linelen;            /* Size of string to match. */
1336         int fg, bg, attr;       /* Color and text attributes for the lines. */
1337 };
1339 static struct line_info line_info[] = {
1340 #define LINE(type, line, fg, bg, attr) \
1341         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1342         LINE_INFO
1343 #undef  LINE
1344 };
1346 static enum line_type
1347 get_line_type(const char *line)
1349         int linelen = strlen(line);
1350         enum line_type type;
1352         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1353                 /* Case insensitive search matches Signed-off-by lines better. */
1354                 if (linelen >= line_info[type].linelen &&
1355                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1356                         return type;
1358         return LINE_DEFAULT;
1361 static inline int
1362 get_line_attr(enum line_type type)
1364         assert(type < ARRAY_SIZE(line_info));
1365         return COLOR_PAIR(type) | line_info[type].attr;
1368 static struct line_info *
1369 get_line_info(const char *name)
1371         size_t namelen = strlen(name);
1372         enum line_type type;
1374         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1375                 if (enum_equals(line_info[type], name, namelen))
1376                         return &line_info[type];
1378         return NULL;
1381 static void
1382 init_colors(void)
1384         int default_bg = line_info[LINE_DEFAULT].bg;
1385         int default_fg = line_info[LINE_DEFAULT].fg;
1386         enum line_type type;
1388         start_color();
1390         if (assume_default_colors(default_fg, default_bg) == ERR) {
1391                 default_bg = COLOR_BLACK;
1392                 default_fg = COLOR_WHITE;
1393         }
1395         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1396                 struct line_info *info = &line_info[type];
1397                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1398                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1400                 init_pair(type, fg, bg);
1401         }
1404 struct line {
1405         enum line_type type;
1407         /* State flags */
1408         unsigned int selected:1;
1409         unsigned int dirty:1;
1410         unsigned int cleareol:1;
1411         unsigned int other:16;
1413         void *data;             /* User data */
1414 };
1417 /*
1418  * Keys
1419  */
1421 struct keybinding {
1422         int alias;
1423         enum request request;
1424 };
1426 static struct keybinding default_keybindings[] = {
1427         /* View switching */
1428         { 'm',          REQ_VIEW_MAIN },
1429         { 'd',          REQ_VIEW_DIFF },
1430         { 'l',          REQ_VIEW_LOG },
1431         { 't',          REQ_VIEW_TREE },
1432         { 'f',          REQ_VIEW_BLOB },
1433         { 'B',          REQ_VIEW_BLAME },
1434         { 'H',          REQ_VIEW_BRANCH },
1435         { 'p',          REQ_VIEW_PAGER },
1436         { 'h',          REQ_VIEW_HELP },
1437         { 'S',          REQ_VIEW_STATUS },
1438         { 'c',          REQ_VIEW_STAGE },
1440         /* View manipulation */
1441         { 'q',          REQ_VIEW_CLOSE },
1442         { KEY_TAB,      REQ_VIEW_NEXT },
1443         { KEY_RETURN,   REQ_ENTER },
1444         { KEY_UP,       REQ_PREVIOUS },
1445         { KEY_DOWN,     REQ_NEXT },
1446         { 'R',          REQ_REFRESH },
1447         { KEY_F(5),     REQ_REFRESH },
1448         { 'O',          REQ_MAXIMIZE },
1450         /* Cursor navigation */
1451         { 'k',          REQ_MOVE_UP },
1452         { 'j',          REQ_MOVE_DOWN },
1453         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1454         { KEY_END,      REQ_MOVE_LAST_LINE },
1455         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1456         { ' ',          REQ_MOVE_PAGE_DOWN },
1457         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1458         { 'b',          REQ_MOVE_PAGE_UP },
1459         { '-',          REQ_MOVE_PAGE_UP },
1461         /* Scrolling */
1462         { KEY_LEFT,     REQ_SCROLL_LEFT },
1463         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1464         { KEY_IC,       REQ_SCROLL_LINE_UP },
1465         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1466         { 'w',          REQ_SCROLL_PAGE_UP },
1467         { 's',          REQ_SCROLL_PAGE_DOWN },
1469         /* Searching */
1470         { '/',          REQ_SEARCH },
1471         { '?',          REQ_SEARCH_BACK },
1472         { 'n',          REQ_FIND_NEXT },
1473         { 'N',          REQ_FIND_PREV },
1475         /* Misc */
1476         { 'Q',          REQ_QUIT },
1477         { 'z',          REQ_STOP_LOADING },
1478         { 'v',          REQ_SHOW_VERSION },
1479         { 'r',          REQ_SCREEN_REDRAW },
1480         { 'o',          REQ_OPTIONS },
1481         { '.',          REQ_TOGGLE_LINENO },
1482         { 'D',          REQ_TOGGLE_DATE },
1483         { 'A',          REQ_TOGGLE_AUTHOR },
1484         { 'g',          REQ_TOGGLE_REV_GRAPH },
1485         { 'F',          REQ_TOGGLE_REFS },
1486         { 'I',          REQ_TOGGLE_SORT_ORDER },
1487         { 'i',          REQ_TOGGLE_SORT_FIELD },
1488         { ':',          REQ_PROMPT },
1489         { 'u',          REQ_STATUS_UPDATE },
1490         { '!',          REQ_STATUS_REVERT },
1491         { 'M',          REQ_STATUS_MERGE },
1492         { '@',          REQ_STAGE_NEXT },
1493         { ',',          REQ_PARENT },
1494         { 'e',          REQ_EDIT },
1495 };
1497 #define KEYMAP_INFO \
1498         KEYMAP_(GENERIC), \
1499         KEYMAP_(MAIN), \
1500         KEYMAP_(DIFF), \
1501         KEYMAP_(LOG), \
1502         KEYMAP_(TREE), \
1503         KEYMAP_(BLOB), \
1504         KEYMAP_(BLAME), \
1505         KEYMAP_(BRANCH), \
1506         KEYMAP_(PAGER), \
1507         KEYMAP_(HELP), \
1508         KEYMAP_(STATUS), \
1509         KEYMAP_(STAGE)
1511 enum keymap {
1512 #define KEYMAP_(name) KEYMAP_##name
1513         KEYMAP_INFO
1514 #undef  KEYMAP_
1515 };
1517 static const struct enum_map keymap_table[] = {
1518 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1519         KEYMAP_INFO
1520 #undef  KEYMAP_
1521 };
1523 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1525 struct keybinding_table {
1526         struct keybinding *data;
1527         size_t size;
1528 };
1530 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1532 static void
1533 add_keybinding(enum keymap keymap, enum request request, int key)
1535         struct keybinding_table *table = &keybindings[keymap];
1536         size_t i;
1538         for (i = 0; i < keybindings[keymap].size; i++) {
1539                 if (keybindings[keymap].data[i].alias == key) {
1540                         keybindings[keymap].data[i].request = request;
1541                         return;
1542                 }
1543         }
1545         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1546         if (!table->data)
1547                 die("Failed to allocate keybinding");
1548         table->data[table->size].alias = key;
1549         table->data[table->size++].request = request;
1551         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1552                 int i;
1554                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1555                         if (default_keybindings[i].alias == key)
1556                                 default_keybindings[i].request = REQ_NONE;
1557         }
1560 /* Looks for a key binding first in the given map, then in the generic map, and
1561  * lastly in the default keybindings. */
1562 static enum request
1563 get_keybinding(enum keymap keymap, int key)
1565         size_t i;
1567         for (i = 0; i < keybindings[keymap].size; i++)
1568                 if (keybindings[keymap].data[i].alias == key)
1569                         return keybindings[keymap].data[i].request;
1571         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1572                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1573                         return keybindings[KEYMAP_GENERIC].data[i].request;
1575         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1576                 if (default_keybindings[i].alias == key)
1577                         return default_keybindings[i].request;
1579         return (enum request) key;
1583 struct key {
1584         const char *name;
1585         int value;
1586 };
1588 static const struct key key_table[] = {
1589         { "Enter",      KEY_RETURN },
1590         { "Space",      ' ' },
1591         { "Backspace",  KEY_BACKSPACE },
1592         { "Tab",        KEY_TAB },
1593         { "Escape",     KEY_ESC },
1594         { "Left",       KEY_LEFT },
1595         { "Right",      KEY_RIGHT },
1596         { "Up",         KEY_UP },
1597         { "Down",       KEY_DOWN },
1598         { "Insert",     KEY_IC },
1599         { "Delete",     KEY_DC },
1600         { "Hash",       '#' },
1601         { "Home",       KEY_HOME },
1602         { "End",        KEY_END },
1603         { "PageUp",     KEY_PPAGE },
1604         { "PageDown",   KEY_NPAGE },
1605         { "F1",         KEY_F(1) },
1606         { "F2",         KEY_F(2) },
1607         { "F3",         KEY_F(3) },
1608         { "F4",         KEY_F(4) },
1609         { "F5",         KEY_F(5) },
1610         { "F6",         KEY_F(6) },
1611         { "F7",         KEY_F(7) },
1612         { "F8",         KEY_F(8) },
1613         { "F9",         KEY_F(9) },
1614         { "F10",        KEY_F(10) },
1615         { "F11",        KEY_F(11) },
1616         { "F12",        KEY_F(12) },
1617 };
1619 static int
1620 get_key_value(const char *name)
1622         int i;
1624         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1625                 if (!strcasecmp(key_table[i].name, name))
1626                         return key_table[i].value;
1628         if (strlen(name) == 1 && isprint(*name))
1629                 return (int) *name;
1631         return ERR;
1634 static const char *
1635 get_key_name(int key_value)
1637         static char key_char[] = "'X'";
1638         const char *seq = NULL;
1639         int key;
1641         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1642                 if (key_table[key].value == key_value)
1643                         seq = key_table[key].name;
1645         if (seq == NULL &&
1646             key_value < 127 &&
1647             isprint(key_value)) {
1648                 key_char[1] = (char) key_value;
1649                 seq = key_char;
1650         }
1652         return seq ? seq : "(no key)";
1655 static bool
1656 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1658         const char *sep = *pos > 0 ? ", " : "";
1659         const char *keyname = get_key_name(keybinding->alias);
1661         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1664 static bool
1665 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1666                            enum keymap keymap, bool all)
1668         int i;
1670         for (i = 0; i < keybindings[keymap].size; i++) {
1671                 if (keybindings[keymap].data[i].request == request) {
1672                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1673                                 return FALSE;
1674                         if (!all)
1675                                 break;
1676                 }
1677         }
1679         return TRUE;
1682 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1684 static const char *
1685 get_keys(enum keymap keymap, enum request request, bool all)
1687         static char buf[BUFSIZ];
1688         size_t pos = 0;
1689         int i;
1691         buf[pos] = 0;
1693         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1694                 return "Too many keybindings!";
1695         if (pos > 0 && !all)
1696                 return buf;
1698         if (keymap != KEYMAP_GENERIC) {
1699                 /* Only the generic keymap includes the default keybindings when
1700                  * listing all keys. */
1701                 if (all)
1702                         return buf;
1704                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1705                         return "Too many keybindings!";
1706                 if (pos)
1707                         return buf;
1708         }
1710         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1711                 if (default_keybindings[i].request == request) {
1712                         if (!append_key(buf, &pos, &default_keybindings[i]))
1713                                 return "Too many keybindings!";
1714                         if (!all)
1715                                 return buf;
1716                 }
1717         }
1719         return buf;
1722 struct run_request {
1723         enum keymap keymap;
1724         int key;
1725         const char *argv[SIZEOF_ARG];
1726 };
1728 static struct run_request *run_request;
1729 static size_t run_requests;
1731 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1733 static enum request
1734 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1736         struct run_request *req;
1738         if (argc >= ARRAY_SIZE(req->argv) - 1)
1739                 return REQ_NONE;
1741         if (!realloc_run_requests(&run_request, run_requests, 1))
1742                 return REQ_NONE;
1744         req = &run_request[run_requests];
1745         req->keymap = keymap;
1746         req->key = key;
1747         req->argv[0] = NULL;
1749         if (!format_argv(req->argv, argv, FORMAT_NONE))
1750                 return REQ_NONE;
1752         return REQ_NONE + ++run_requests;
1755 static struct run_request *
1756 get_run_request(enum request request)
1758         if (request <= REQ_NONE)
1759                 return NULL;
1760         return &run_request[request - REQ_NONE - 1];
1763 static void
1764 add_builtin_run_requests(void)
1766         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1767         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1768         const char *commit[] = { "git", "commit", NULL };
1769         const char *gc[] = { "git", "gc", NULL };
1770         struct {
1771                 enum keymap keymap;
1772                 int key;
1773                 int argc;
1774                 const char **argv;
1775         } reqs[] = {
1776                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1777                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1778                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1779                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1780         };
1781         int i;
1783         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1784                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1786                 if (req != reqs[i].key)
1787                         continue;
1788                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1789                 if (req != REQ_NONE)
1790                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1791         }
1794 /*
1795  * User config file handling.
1796  */
1798 static int   config_lineno;
1799 static bool  config_errors;
1800 static const char *config_msg;
1802 static const struct enum_map color_map[] = {
1803 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1804         COLOR_MAP(DEFAULT),
1805         COLOR_MAP(BLACK),
1806         COLOR_MAP(BLUE),
1807         COLOR_MAP(CYAN),
1808         COLOR_MAP(GREEN),
1809         COLOR_MAP(MAGENTA),
1810         COLOR_MAP(RED),
1811         COLOR_MAP(WHITE),
1812         COLOR_MAP(YELLOW),
1813 };
1815 static const struct enum_map attr_map[] = {
1816 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1817         ATTR_MAP(NORMAL),
1818         ATTR_MAP(BLINK),
1819         ATTR_MAP(BOLD),
1820         ATTR_MAP(DIM),
1821         ATTR_MAP(REVERSE),
1822         ATTR_MAP(STANDOUT),
1823         ATTR_MAP(UNDERLINE),
1824 };
1826 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1828 static int parse_step(double *opt, const char *arg)
1830         *opt = atoi(arg);
1831         if (!strchr(arg, '%'))
1832                 return OK;
1834         /* "Shift down" so 100% and 1 does not conflict. */
1835         *opt = (*opt - 1) / 100;
1836         if (*opt >= 1.0) {
1837                 *opt = 0.99;
1838                 config_msg = "Step value larger than 100%";
1839                 return ERR;
1840         }
1841         if (*opt < 0.0) {
1842                 *opt = 1;
1843                 config_msg = "Invalid step value";
1844                 return ERR;
1845         }
1846         return OK;
1849 static int
1850 parse_int(int *opt, const char *arg, int min, int max)
1852         int value = atoi(arg);
1854         if (min <= value && value <= max) {
1855                 *opt = value;
1856                 return OK;
1857         }
1859         config_msg = "Integer value out of bound";
1860         return ERR;
1863 static bool
1864 set_color(int *color, const char *name)
1866         if (map_enum(color, color_map, name))
1867                 return TRUE;
1868         if (!prefixcmp(name, "color"))
1869                 return parse_int(color, name + 5, 0, 255) == OK;
1870         return FALSE;
1873 /* Wants: object fgcolor bgcolor [attribute] */
1874 static int
1875 option_color_command(int argc, const char *argv[])
1877         struct line_info *info;
1879         if (argc < 3) {
1880                 config_msg = "Wrong number of arguments given to color command";
1881                 return ERR;
1882         }
1884         info = get_line_info(argv[0]);
1885         if (!info) {
1886                 static const struct enum_map obsolete[] = {
1887                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1888                         ENUM_MAP("main-date",   LINE_DATE),
1889                         ENUM_MAP("main-author", LINE_AUTHOR),
1890                 };
1891                 int index;
1893                 if (!map_enum(&index, obsolete, argv[0])) {
1894                         config_msg = "Unknown color name";
1895                         return ERR;
1896                 }
1897                 info = &line_info[index];
1898         }
1900         if (!set_color(&info->fg, argv[1]) ||
1901             !set_color(&info->bg, argv[2])) {
1902                 config_msg = "Unknown color";
1903                 return ERR;
1904         }
1906         info->attr = 0;
1907         while (argc-- > 3) {
1908                 int attr;
1910                 if (!set_attribute(&attr, argv[argc])) {
1911                         config_msg = "Unknown attribute";
1912                         return ERR;
1913                 }
1914                 info->attr |= attr;
1915         }
1917         return OK;
1920 static int parse_bool(bool *opt, const char *arg)
1922         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1923                 ? TRUE : FALSE;
1924         return OK;
1927 static int parse_enum_do(unsigned int *opt, const char *arg,
1928                          const struct enum_map *map, size_t map_size)
1930         bool is_true;
1932         assert(map_size > 1);
1934         if (map_enum_do(map, map_size, (int *) opt, arg))
1935                 return OK;
1937         if (parse_bool(&is_true, arg) != OK)
1938                 return ERR;
1940         *opt = is_true ? map[1].value : map[0].value;
1941         return OK;
1944 #define parse_enum(opt, arg, map) \
1945         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1947 static int
1948 parse_string(char *opt, const char *arg, size_t optsize)
1950         int arglen = strlen(arg);
1952         switch (arg[0]) {
1953         case '\"':
1954         case '\'':
1955                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1956                         config_msg = "Unmatched quotation";
1957                         return ERR;
1958                 }
1959                 arg += 1; arglen -= 2;
1960         default:
1961                 string_ncopy_do(opt, optsize, arg, arglen);
1962                 return OK;
1963         }
1966 /* Wants: name = value */
1967 static int
1968 option_set_command(int argc, const char *argv[])
1970         if (argc != 3) {
1971                 config_msg = "Wrong number of arguments given to set command";
1972                 return ERR;
1973         }
1975         if (strcmp(argv[1], "=")) {
1976                 config_msg = "No value assigned";
1977                 return ERR;
1978         }
1980         if (!strcmp(argv[0], "show-author"))
1981                 return parse_enum(&opt_author, argv[2], author_map);
1983         if (!strcmp(argv[0], "show-date"))
1984                 return parse_enum(&opt_date, argv[2], date_map);
1986         if (!strcmp(argv[0], "show-rev-graph"))
1987                 return parse_bool(&opt_rev_graph, argv[2]);
1989         if (!strcmp(argv[0], "show-refs"))
1990                 return parse_bool(&opt_show_refs, argv[2]);
1992         if (!strcmp(argv[0], "show-line-numbers"))
1993                 return parse_bool(&opt_line_number, argv[2]);
1995         if (!strcmp(argv[0], "line-graphics"))
1996                 return parse_bool(&opt_line_graphics, argv[2]);
1998         if (!strcmp(argv[0], "line-number-interval"))
1999                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2001         if (!strcmp(argv[0], "author-width"))
2002                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2004         if (!strcmp(argv[0], "horizontal-scroll"))
2005                 return parse_step(&opt_hscroll, argv[2]);
2007         if (!strcmp(argv[0], "split-view-height"))
2008                 return parse_step(&opt_scale_split_view, argv[2]);
2010         if (!strcmp(argv[0], "tab-size"))
2011                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2013         if (!strcmp(argv[0], "commit-encoding"))
2014                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2016         config_msg = "Unknown variable name";
2017         return ERR;
2020 /* Wants: mode request key */
2021 static int
2022 option_bind_command(int argc, const char *argv[])
2024         enum request request;
2025         int keymap = -1;
2026         int key;
2028         if (argc < 3) {
2029                 config_msg = "Wrong number of arguments given to bind command";
2030                 return ERR;
2031         }
2033         if (!set_keymap(&keymap, argv[0])) {
2034                 config_msg = "Unknown key map";
2035                 return ERR;
2036         }
2038         key = get_key_value(argv[1]);
2039         if (key == ERR) {
2040                 config_msg = "Unknown key";
2041                 return ERR;
2042         }
2044         request = get_request(argv[2]);
2045         if (request == REQ_UNKNOWN) {
2046                 static const struct enum_map obsolete[] = {
2047                         ENUM_MAP("cherry-pick",         REQ_NONE),
2048                         ENUM_MAP("screen-resize",       REQ_NONE),
2049                         ENUM_MAP("tree-parent",         REQ_PARENT),
2050                 };
2051                 int alias;
2053                 if (map_enum(&alias, obsolete, argv[2])) {
2054                         if (alias != REQ_NONE)
2055                                 add_keybinding(keymap, alias, key);
2056                         config_msg = "Obsolete request name";
2057                         return ERR;
2058                 }
2059         }
2060         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2061                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2062         if (request == REQ_UNKNOWN) {
2063                 config_msg = "Unknown request name";
2064                 return ERR;
2065         }
2067         add_keybinding(keymap, request, key);
2069         return OK;
2072 static int
2073 set_option(const char *opt, char *value)
2075         const char *argv[SIZEOF_ARG];
2076         int argc = 0;
2078         if (!argv_from_string(argv, &argc, value)) {
2079                 config_msg = "Too many option arguments";
2080                 return ERR;
2081         }
2083         if (!strcmp(opt, "color"))
2084                 return option_color_command(argc, argv);
2086         if (!strcmp(opt, "set"))
2087                 return option_set_command(argc, argv);
2089         if (!strcmp(opt, "bind"))
2090                 return option_bind_command(argc, argv);
2092         config_msg = "Unknown option command";
2093         return ERR;
2096 static int
2097 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2099         int status = OK;
2101         config_lineno++;
2102         config_msg = "Internal error";
2104         /* Check for comment markers, since read_properties() will
2105          * only ensure opt and value are split at first " \t". */
2106         optlen = strcspn(opt, "#");
2107         if (optlen == 0)
2108                 return OK;
2110         if (opt[optlen] != 0) {
2111                 config_msg = "No option value";
2112                 status = ERR;
2114         }  else {
2115                 /* Look for comment endings in the value. */
2116                 size_t len = strcspn(value, "#");
2118                 if (len < valuelen) {
2119                         valuelen = len;
2120                         value[valuelen] = 0;
2121                 }
2123                 status = set_option(opt, value);
2124         }
2126         if (status == ERR) {
2127                 warn("Error on line %d, near '%.*s': %s",
2128                      config_lineno, (int) optlen, opt, config_msg);
2129                 config_errors = TRUE;
2130         }
2132         /* Always keep going if errors are encountered. */
2133         return OK;
2136 static void
2137 load_option_file(const char *path)
2139         struct io io = {};
2141         /* It's OK that the file doesn't exist. */
2142         if (!io_open(&io, "%s", path))
2143                 return;
2145         config_lineno = 0;
2146         config_errors = FALSE;
2148         if (io_load(&io, " \t", read_option) == ERR ||
2149             config_errors == TRUE)
2150                 warn("Errors while loading %s.", path);
2153 static int
2154 load_options(void)
2156         const char *home = getenv("HOME");
2157         const char *tigrc_user = getenv("TIGRC_USER");
2158         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2159         char buf[SIZEOF_STR];
2161         if (!tigrc_system)
2162                 tigrc_system = SYSCONFDIR "/tigrc";
2163         load_option_file(tigrc_system);
2165         if (!tigrc_user) {
2166                 if (!home || !string_format(buf, "%s/.tigrc", home))
2167                         return ERR;
2168                 tigrc_user = buf;
2169         }
2170         load_option_file(tigrc_user);
2172         /* Add _after_ loading config files to avoid adding run requests
2173          * that conflict with keybindings. */
2174         add_builtin_run_requests();
2176         return OK;
2180 /*
2181  * The viewer
2182  */
2184 struct view;
2185 struct view_ops;
2187 /* The display array of active views and the index of the current view. */
2188 static struct view *display[2];
2189 static unsigned int current_view;
2191 #define foreach_displayed_view(view, i) \
2192         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2194 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2196 /* Current head and commit ID */
2197 static char ref_blob[SIZEOF_REF]        = "";
2198 static char ref_commit[SIZEOF_REF]      = "HEAD";
2199 static char ref_head[SIZEOF_REF]        = "HEAD";
2200 static char ref_branch[SIZEOF_REF]      = "";
2202 enum view_type {
2203         VIEW_MAIN,
2204         VIEW_DIFF,
2205         VIEW_LOG,
2206         VIEW_TREE,
2207         VIEW_BLOB,
2208         VIEW_BLAME,
2209         VIEW_BRANCH,
2210         VIEW_HELP,
2211         VIEW_PAGER,
2212         VIEW_STATUS,
2213         VIEW_STAGE,
2214 };
2216 struct view {
2217         enum view_type type;    /* View type */
2218         const char *name;       /* View name */
2219         const char *cmd_env;    /* Command line set via environment */
2220         const char *id;         /* Points to either of ref_{head,commit,blob} */
2222         struct view_ops *ops;   /* View operations */
2224         enum keymap keymap;     /* What keymap does this view have */
2225         bool git_dir;           /* Whether the view requires a git directory. */
2226         bool refresh;           /* Whether the view supports refreshing. */
2228         char ref[SIZEOF_REF];   /* Hovered commit reference */
2229         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2231         int height, width;      /* The width and height of the main window */
2232         WINDOW *win;            /* The main window */
2233         WINDOW *title;          /* The title window living below the main window */
2235         /* Navigation */
2236         unsigned long offset;   /* Offset of the window top */
2237         unsigned long yoffset;  /* Offset from the window side. */
2238         unsigned long lineno;   /* Current line number */
2239         unsigned long p_offset; /* Previous offset of the window top */
2240         unsigned long p_yoffset;/* Previous offset from the window side */
2241         unsigned long p_lineno; /* Previous current line number */
2242         bool p_restore;         /* Should the previous position be restored. */
2244         /* Searching */
2245         char grep[SIZEOF_STR];  /* Search string */
2246         regex_t *regex;         /* Pre-compiled regexp */
2248         /* If non-NULL, points to the view that opened this view. If this view
2249          * is closed tig will switch back to the parent view. */
2250         struct view *parent;
2251         struct view *prev;
2253         /* Buffering */
2254         size_t lines;           /* Total number of lines */
2255         struct line *line;      /* Line index */
2256         unsigned int digits;    /* Number of digits in the lines member. */
2258         /* Drawing */
2259         struct line *curline;   /* Line currently being drawn. */
2260         enum line_type curtype; /* Attribute currently used for drawing. */
2261         unsigned long col;      /* Column when drawing. */
2262         bool has_scrolled;      /* View was scrolled. */
2264         /* Loading */
2265         struct io io;
2266         struct io *pipe;
2267         time_t start_time;
2268         time_t update_secs;
2269 };
2271 struct view_ops {
2272         /* What type of content being displayed. Used in the title bar. */
2273         const char *type;
2274         /* Default command arguments. */
2275         const char **argv;
2276         /* Open and reads in all view content. */
2277         bool (*open)(struct view *view);
2278         /* Read one line; updates view->line. */
2279         bool (*read)(struct view *view, char *data);
2280         /* Draw one line; @lineno must be < view->height. */
2281         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2282         /* Depending on view handle a special requests. */
2283         enum request (*request)(struct view *view, enum request request, struct line *line);
2284         /* Search for regexp in a line. */
2285         bool (*grep)(struct view *view, struct line *line);
2286         /* Select line */
2287         void (*select)(struct view *view, struct line *line);
2288         /* Prepare view for loading */
2289         bool (*prepare)(struct view *view);
2290 };
2292 static struct view_ops blame_ops;
2293 static struct view_ops blob_ops;
2294 static struct view_ops diff_ops;
2295 static struct view_ops help_ops;
2296 static struct view_ops log_ops;
2297 static struct view_ops main_ops;
2298 static struct view_ops pager_ops;
2299 static struct view_ops stage_ops;
2300 static struct view_ops status_ops;
2301 static struct view_ops tree_ops;
2302 static struct view_ops branch_ops;
2304 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2305         { type, name, #env, ref, ops, map, git, refresh }
2307 #define VIEW_(id, name, ops, git, refresh, ref) \
2308         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2310 static struct view views[] = {
2311         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  TRUE,  ref_head),
2312         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  FALSE, ref_commit),
2313         VIEW_(LOG,    "log",    &log_ops,    TRUE,  TRUE,  ref_head),
2314         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  FALSE, ref_commit),
2315         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  FALSE, ref_blob),
2316         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  FALSE, ref_commit),
2317         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  TRUE,  ref_head),
2318         VIEW_(HELP,   "help",   &help_ops,   FALSE, FALSE, ""),
2319         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, FALSE, "stdin"),
2320         VIEW_(STATUS, "status", &status_ops, TRUE,  TRUE,  ""),
2321         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  TRUE,  ""),
2322 };
2324 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2326 #define foreach_view(view, i) \
2327         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2329 #define view_is_displayed(view) \
2330         (view == display[0] || view == display[1])
2332 static enum request
2333 view_request(struct view *view, enum request request)
2335         if (!view || !view->lines)
2336                 return request;
2337         return view->ops->request(view, request, &view->line[view->lineno]);
2341 /*
2342  * View drawing.
2343  */
2345 static inline void
2346 set_view_attr(struct view *view, enum line_type type)
2348         if (!view->curline->selected && view->curtype != type) {
2349                 (void) wattrset(view->win, get_line_attr(type));
2350                 wchgat(view->win, -1, 0, type, NULL);
2351                 view->curtype = type;
2352         }
2355 static int
2356 draw_chars(struct view *view, enum line_type type, const char *string,
2357            int max_len, bool use_tilde)
2359         static char out_buffer[BUFSIZ * 2];
2360         int len = 0;
2361         int col = 0;
2362         int trimmed = FALSE;
2363         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2365         if (max_len <= 0)
2366                 return 0;
2368         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2370         set_view_attr(view, type);
2371         if (len > 0) {
2372                 if (opt_iconv_out != ICONV_NONE) {
2373                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2374                         size_t inlen = len + 1;
2376                         char *outbuf = out_buffer;
2377                         size_t outlen = sizeof(out_buffer);
2379                         size_t ret;
2381                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2382                         if (ret != (size_t) -1) {
2383                                 string = out_buffer;
2384                                 len = sizeof(out_buffer) - outlen;
2385                         }
2386                 }
2388                 waddnstr(view->win, string, len);
2389         }
2390         if (trimmed && use_tilde) {
2391                 set_view_attr(view, LINE_DELIMITER);
2392                 waddch(view->win, '~');
2393                 col++;
2394         }
2396         return col;
2399 static int
2400 draw_space(struct view *view, enum line_type type, int max, int spaces)
2402         static char space[] = "                    ";
2403         int col = 0;
2405         spaces = MIN(max, spaces);
2407         while (spaces > 0) {
2408                 int len = MIN(spaces, sizeof(space) - 1);
2410                 col += draw_chars(view, type, space, len, FALSE);
2411                 spaces -= len;
2412         }
2414         return col;
2417 static bool
2418 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2420         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2421         return view->width + view->yoffset <= view->col;
2424 static bool
2425 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2427         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2428         int max = view->width + view->yoffset - view->col;
2429         int i;
2431         if (max < size)
2432                 size = max;
2434         set_view_attr(view, type);
2435         /* Using waddch() instead of waddnstr() ensures that
2436          * they'll be rendered correctly for the cursor line. */
2437         for (i = skip; i < size; i++)
2438                 waddch(view->win, graphic[i]);
2440         view->col += size;
2441         if (size < max && skip <= size)
2442                 waddch(view->win, ' ');
2443         view->col++;
2445         return view->width + view->yoffset <= view->col;
2448 static bool
2449 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2451         int max = MIN(view->width + view->yoffset - view->col, len);
2452         int col;
2454         if (text)
2455                 col = draw_chars(view, type, text, max - 1, trim);
2456         else
2457                 col = draw_space(view, type, max - 1, max - 1);
2459         view->col += col;
2460         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2461         return view->width + view->yoffset <= view->col;
2464 static bool
2465 draw_date(struct view *view, struct time *time)
2467         const char *date = mkdate(time, opt_date);
2468         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2470         return draw_field(view, LINE_DATE, date, cols, FALSE);
2473 static bool
2474 draw_author(struct view *view, const char *author)
2476         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2477         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2479         if (abbreviate && author)
2480                 author = get_author_initials(author);
2482         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2485 static bool
2486 draw_mode(struct view *view, mode_t mode)
2488         const char *str;
2490         if (S_ISDIR(mode))
2491                 str = "drwxr-xr-x";
2492         else if (S_ISLNK(mode))
2493                 str = "lrwxrwxrwx";
2494         else if (S_ISGITLINK(mode))
2495                 str = "m---------";
2496         else if (S_ISREG(mode) && mode & S_IXUSR)
2497                 str = "-rwxr-xr-x";
2498         else if (S_ISREG(mode))
2499                 str = "-rw-r--r--";
2500         else
2501                 str = "----------";
2503         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2506 static bool
2507 draw_lineno(struct view *view, unsigned int lineno)
2509         char number[10];
2510         int digits3 = view->digits < 3 ? 3 : view->digits;
2511         int max = MIN(view->width + view->yoffset - view->col, digits3);
2512         char *text = NULL;
2513         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2515         lineno += view->offset + 1;
2516         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2517                 static char fmt[] = "%1ld";
2519                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2520                 if (string_format(number, fmt, lineno))
2521                         text = number;
2522         }
2523         if (text)
2524                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2525         else
2526                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2527         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2530 static bool
2531 draw_view_line(struct view *view, unsigned int lineno)
2533         struct line *line;
2534         bool selected = (view->offset + lineno == view->lineno);
2536         assert(view_is_displayed(view));
2538         if (view->offset + lineno >= view->lines)
2539                 return FALSE;
2541         line = &view->line[view->offset + lineno];
2543         wmove(view->win, lineno, 0);
2544         if (line->cleareol)
2545                 wclrtoeol(view->win);
2546         view->col = 0;
2547         view->curline = line;
2548         view->curtype = LINE_NONE;
2549         line->selected = FALSE;
2550         line->dirty = line->cleareol = 0;
2552         if (selected) {
2553                 set_view_attr(view, LINE_CURSOR);
2554                 line->selected = TRUE;
2555                 view->ops->select(view, line);
2556         }
2558         return view->ops->draw(view, line, lineno);
2561 static void
2562 redraw_view_dirty(struct view *view)
2564         bool dirty = FALSE;
2565         int lineno;
2567         for (lineno = 0; lineno < view->height; lineno++) {
2568                 if (view->offset + lineno >= view->lines)
2569                         break;
2570                 if (!view->line[view->offset + lineno].dirty)
2571                         continue;
2572                 dirty = TRUE;
2573                 if (!draw_view_line(view, lineno))
2574                         break;
2575         }
2577         if (!dirty)
2578                 return;
2579         wnoutrefresh(view->win);
2582 static void
2583 redraw_view_from(struct view *view, int lineno)
2585         assert(0 <= lineno && lineno < view->height);
2587         for (; lineno < view->height; lineno++) {
2588                 if (!draw_view_line(view, lineno))
2589                         break;
2590         }
2592         wnoutrefresh(view->win);
2595 static void
2596 redraw_view(struct view *view)
2598         werase(view->win);
2599         redraw_view_from(view, 0);
2603 static void
2604 update_view_title(struct view *view)
2606         char buf[SIZEOF_STR];
2607         char state[SIZEOF_STR];
2608         size_t bufpos = 0, statelen = 0;
2610         assert(view_is_displayed(view));
2612         if (view->type != VIEW_STATUS && view->lines) {
2613                 unsigned int view_lines = view->offset + view->height;
2614                 unsigned int lines = view->lines
2615                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2616                                    : 0;
2618                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2619                                    view->ops->type,
2620                                    view->lineno + 1,
2621                                    view->lines,
2622                                    lines);
2624         }
2626         if (view->pipe) {
2627                 time_t secs = time(NULL) - view->start_time;
2629                 /* Three git seconds are a long time ... */
2630                 if (secs > 2)
2631                         string_format_from(state, &statelen, " loading %lds", secs);
2632         }
2634         string_format_from(buf, &bufpos, "[%s]", view->name);
2635         if (*view->ref && bufpos < view->width) {
2636                 size_t refsize = strlen(view->ref);
2637                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2639                 if (minsize < view->width)
2640                         refsize = view->width - minsize + 7;
2641                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2642         }
2644         if (statelen && bufpos < view->width) {
2645                 string_format_from(buf, &bufpos, "%s", state);
2646         }
2648         if (view == display[current_view])
2649                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2650         else
2651                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2653         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2654         wclrtoeol(view->title);
2655         wnoutrefresh(view->title);
2658 static int
2659 apply_step(double step, int value)
2661         if (step >= 1)
2662                 return (int) step;
2663         value *= step + 0.01;
2664         return value ? value : 1;
2667 static void
2668 resize_display(void)
2670         int offset, i;
2671         struct view *base = display[0];
2672         struct view *view = display[1] ? display[1] : display[0];
2674         /* Setup window dimensions */
2676         getmaxyx(stdscr, base->height, base->width);
2678         /* Make room for the status window. */
2679         base->height -= 1;
2681         if (view != base) {
2682                 /* Horizontal split. */
2683                 view->width   = base->width;
2684                 view->height  = apply_step(opt_scale_split_view, base->height);
2685                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2686                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2687                 base->height -= view->height;
2689                 /* Make room for the title bar. */
2690                 view->height -= 1;
2691         }
2693         /* Make room for the title bar. */
2694         base->height -= 1;
2696         offset = 0;
2698         foreach_displayed_view (view, i) {
2699                 if (!view->win) {
2700                         view->win = newwin(view->height, 0, offset, 0);
2701                         if (!view->win)
2702                                 die("Failed to create %s view", view->name);
2704                         scrollok(view->win, FALSE);
2706                         view->title = newwin(1, 0, offset + view->height, 0);
2707                         if (!view->title)
2708                                 die("Failed to create title window");
2710                 } else {
2711                         wresize(view->win, view->height, view->width);
2712                         mvwin(view->win,   offset, 0);
2713                         mvwin(view->title, offset + view->height, 0);
2714                 }
2716                 offset += view->height + 1;
2717         }
2720 static void
2721 redraw_display(bool clear)
2723         struct view *view;
2724         int i;
2726         foreach_displayed_view (view, i) {
2727                 if (clear)
2728                         wclear(view->win);
2729                 redraw_view(view);
2730                 update_view_title(view);
2731         }
2735 /*
2736  * Option management
2737  */
2739 static void
2740 toggle_enum_option_do(unsigned int *opt, const char *help,
2741                       const struct enum_map *map, size_t size)
2743         *opt = (*opt + 1) % size;
2744         redraw_display(FALSE);
2745         report("Displaying %s %s", enum_name(map[*opt]), help);
2748 #define toggle_enum_option(opt, help, map) \
2749         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2751 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2752 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2754 static void
2755 toggle_view_option(bool *option, const char *help)
2757         *option = !*option;
2758         redraw_display(FALSE);
2759         report("%sabling %s", *option ? "En" : "Dis", help);
2762 static void
2763 open_option_menu(void)
2765         const struct menu_item menu[] = {
2766                 { '.', "line numbers", &opt_line_number },
2767                 { 'D', "date display", &opt_date },
2768                 { 'A', "author display", &opt_author },
2769                 { 'g', "revision graph display", &opt_rev_graph },
2770                 { 'F', "reference display", &opt_show_refs },
2771                 { 0 }
2772         };
2773         int selected = 0;
2775         if (prompt_menu("Toggle option", menu, &selected)) {
2776                 if (menu[selected].data == &opt_date)
2777                         toggle_date();
2778                 else if (menu[selected].data == &opt_author)
2779                         toggle_author();
2780                 else
2781                         toggle_view_option(menu[selected].data, menu[selected].text);
2782         }
2785 static void
2786 maximize_view(struct view *view)
2788         memset(display, 0, sizeof(display));
2789         current_view = 0;
2790         display[current_view] = view;
2791         resize_display();
2792         redraw_display(FALSE);
2793         report("");
2797 /*
2798  * Navigation
2799  */
2801 static bool
2802 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2804         if (lineno >= view->lines)
2805                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2807         if (offset > lineno || offset + view->height <= lineno) {
2808                 unsigned long half = view->height / 2;
2810                 if (lineno > half)
2811                         offset = lineno - half;
2812                 else
2813                         offset = 0;
2814         }
2816         if (offset != view->offset || lineno != view->lineno) {
2817                 view->offset = offset;
2818                 view->lineno = lineno;
2819                 return TRUE;
2820         }
2822         return FALSE;
2825 /* Scrolling backend */
2826 static void
2827 do_scroll_view(struct view *view, int lines)
2829         bool redraw_current_line = FALSE;
2831         /* The rendering expects the new offset. */
2832         view->offset += lines;
2834         assert(0 <= view->offset && view->offset < view->lines);
2835         assert(lines);
2837         /* Move current line into the view. */
2838         if (view->lineno < view->offset) {
2839                 view->lineno = view->offset;
2840                 redraw_current_line = TRUE;
2841         } else if (view->lineno >= view->offset + view->height) {
2842                 view->lineno = view->offset + view->height - 1;
2843                 redraw_current_line = TRUE;
2844         }
2846         assert(view->offset <= view->lineno && view->lineno < view->lines);
2848         /* Redraw the whole screen if scrolling is pointless. */
2849         if (view->height < ABS(lines)) {
2850                 redraw_view(view);
2852         } else {
2853                 int line = lines > 0 ? view->height - lines : 0;
2854                 int end = line + ABS(lines);
2856                 scrollok(view->win, TRUE);
2857                 wscrl(view->win, lines);
2858                 scrollok(view->win, FALSE);
2860                 while (line < end && draw_view_line(view, line))
2861                         line++;
2863                 if (redraw_current_line)
2864                         draw_view_line(view, view->lineno - view->offset);
2865                 wnoutrefresh(view->win);
2866         }
2868         view->has_scrolled = TRUE;
2869         report("");
2872 /* Scroll frontend */
2873 static void
2874 scroll_view(struct view *view, enum request request)
2876         int lines = 1;
2878         assert(view_is_displayed(view));
2880         switch (request) {
2881         case REQ_SCROLL_LEFT:
2882                 if (view->yoffset == 0) {
2883                         report("Cannot scroll beyond the first column");
2884                         return;
2885                 }
2886                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2887                         view->yoffset = 0;
2888                 else
2889                         view->yoffset -= apply_step(opt_hscroll, view->width);
2890                 redraw_view_from(view, 0);
2891                 report("");
2892                 return;
2893         case REQ_SCROLL_RIGHT:
2894                 view->yoffset += apply_step(opt_hscroll, view->width);
2895                 redraw_view(view);
2896                 report("");
2897                 return;
2898         case REQ_SCROLL_PAGE_DOWN:
2899                 lines = view->height;
2900         case REQ_SCROLL_LINE_DOWN:
2901                 if (view->offset + lines > view->lines)
2902                         lines = view->lines - view->offset;
2904                 if (lines == 0 || view->offset + view->height >= view->lines) {
2905                         report("Cannot scroll beyond the last line");
2906                         return;
2907                 }
2908                 break;
2910         case REQ_SCROLL_PAGE_UP:
2911                 lines = view->height;
2912         case REQ_SCROLL_LINE_UP:
2913                 if (lines > view->offset)
2914                         lines = view->offset;
2916                 if (lines == 0) {
2917                         report("Cannot scroll beyond the first line");
2918                         return;
2919                 }
2921                 lines = -lines;
2922                 break;
2924         default:
2925                 die("request %d not handled in switch", request);
2926         }
2928         do_scroll_view(view, lines);
2931 /* Cursor moving */
2932 static void
2933 move_view(struct view *view, enum request request)
2935         int scroll_steps = 0;
2936         int steps;
2938         switch (request) {
2939         case REQ_MOVE_FIRST_LINE:
2940                 steps = -view->lineno;
2941                 break;
2943         case REQ_MOVE_LAST_LINE:
2944                 steps = view->lines - view->lineno - 1;
2945                 break;
2947         case REQ_MOVE_PAGE_UP:
2948                 steps = view->height > view->lineno
2949                       ? -view->lineno : -view->height;
2950                 break;
2952         case REQ_MOVE_PAGE_DOWN:
2953                 steps = view->lineno + view->height >= view->lines
2954                       ? view->lines - view->lineno - 1 : view->height;
2955                 break;
2957         case REQ_MOVE_UP:
2958                 steps = -1;
2959                 break;
2961         case REQ_MOVE_DOWN:
2962                 steps = 1;
2963                 break;
2965         default:
2966                 die("request %d not handled in switch", request);
2967         }
2969         if (steps <= 0 && view->lineno == 0) {
2970                 report("Cannot move beyond the first line");
2971                 return;
2973         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2974                 report("Cannot move beyond the last line");
2975                 return;
2976         }
2978         /* Move the current line */
2979         view->lineno += steps;
2980         assert(0 <= view->lineno && view->lineno < view->lines);
2982         /* Check whether the view needs to be scrolled */
2983         if (view->lineno < view->offset ||
2984             view->lineno >= view->offset + view->height) {
2985                 scroll_steps = steps;
2986                 if (steps < 0 && -steps > view->offset) {
2987                         scroll_steps = -view->offset;
2989                 } else if (steps > 0) {
2990                         if (view->lineno == view->lines - 1 &&
2991                             view->lines > view->height) {
2992                                 scroll_steps = view->lines - view->offset - 1;
2993                                 if (scroll_steps >= view->height)
2994                                         scroll_steps -= view->height - 1;
2995                         }
2996                 }
2997         }
2999         if (!view_is_displayed(view)) {
3000                 view->offset += scroll_steps;
3001                 assert(0 <= view->offset && view->offset < view->lines);
3002                 view->ops->select(view, &view->line[view->lineno]);
3003                 return;
3004         }
3006         /* Repaint the old "current" line if we be scrolling */
3007         if (ABS(steps) < view->height)
3008                 draw_view_line(view, view->lineno - steps - view->offset);
3010         if (scroll_steps) {
3011                 do_scroll_view(view, scroll_steps);
3012                 return;
3013         }
3015         /* Draw the current line */
3016         draw_view_line(view, view->lineno - view->offset);
3018         wnoutrefresh(view->win);
3019         report("");
3023 /*
3024  * Searching
3025  */
3027 static void search_view(struct view *view, enum request request);
3029 static bool
3030 grep_text(struct view *view, const char *text[])
3032         regmatch_t pmatch;
3033         size_t i;
3035         for (i = 0; text[i]; i++)
3036                 if (*text[i] &&
3037                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3038                         return TRUE;
3039         return FALSE;
3042 static void
3043 select_view_line(struct view *view, unsigned long lineno)
3045         unsigned long old_lineno = view->lineno;
3046         unsigned long old_offset = view->offset;
3048         if (goto_view_line(view, view->offset, lineno)) {
3049                 if (view_is_displayed(view)) {
3050                         if (old_offset != view->offset) {
3051                                 redraw_view(view);
3052                         } else {
3053                                 draw_view_line(view, old_lineno - view->offset);
3054                                 draw_view_line(view, view->lineno - view->offset);
3055                                 wnoutrefresh(view->win);
3056                         }
3057                 } else {
3058                         view->ops->select(view, &view->line[view->lineno]);
3059                 }
3060         }
3063 static void
3064 find_next(struct view *view, enum request request)
3066         unsigned long lineno = view->lineno;
3067         int direction;
3069         if (!*view->grep) {
3070                 if (!*opt_search)
3071                         report("No previous search");
3072                 else
3073                         search_view(view, request);
3074                 return;
3075         }
3077         switch (request) {
3078         case REQ_SEARCH:
3079         case REQ_FIND_NEXT:
3080                 direction = 1;
3081                 break;
3083         case REQ_SEARCH_BACK:
3084         case REQ_FIND_PREV:
3085                 direction = -1;
3086                 break;
3088         default:
3089                 return;
3090         }
3092         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3093                 lineno += direction;
3095         /* Note, lineno is unsigned long so will wrap around in which case it
3096          * will become bigger than view->lines. */
3097         for (; lineno < view->lines; lineno += direction) {
3098                 if (view->ops->grep(view, &view->line[lineno])) {
3099                         select_view_line(view, lineno);
3100                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3101                         return;
3102                 }
3103         }
3105         report("No match found for '%s'", view->grep);
3108 static void
3109 search_view(struct view *view, enum request request)
3111         int regex_err;
3113         if (view->regex) {
3114                 regfree(view->regex);
3115                 *view->grep = 0;
3116         } else {
3117                 view->regex = calloc(1, sizeof(*view->regex));
3118                 if (!view->regex)
3119                         return;
3120         }
3122         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3123         if (regex_err != 0) {
3124                 char buf[SIZEOF_STR] = "unknown error";
3126                 regerror(regex_err, view->regex, buf, sizeof(buf));
3127                 report("Search failed: %s", buf);
3128                 return;
3129         }
3131         string_copy(view->grep, opt_search);
3133         find_next(view, request);
3136 /*
3137  * Incremental updating
3138  */
3140 static void
3141 reset_view(struct view *view)
3143         int i;
3145         for (i = 0; i < view->lines; i++)
3146                 free(view->line[i].data);
3147         free(view->line);
3149         view->p_offset = view->offset;
3150         view->p_yoffset = view->yoffset;
3151         view->p_lineno = view->lineno;
3153         view->line = NULL;
3154         view->offset = 0;
3155         view->yoffset = 0;
3156         view->lines  = 0;
3157         view->lineno = 0;
3158         view->vid[0] = 0;
3159         view->update_secs = 0;
3162 static const char *
3163 format_arg(const char *name)
3165         static struct {
3166                 const char *name;
3167                 size_t namelen;
3168                 const char *value;
3169                 const char *value_if_empty;
3170         } vars[] = {
3171 #define FORMAT_VAR(name, value, value_if_empty) \
3172         { name, STRING_SIZE(name), value, value_if_empty }
3173                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3174                 FORMAT_VAR("%(file)",           opt_file,       ""),
3175                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3176                 FORMAT_VAR("%(head)",           ref_head,       ""),
3177                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3178                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3179                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3180         };
3181         int i;
3183         for (i = 0; i < ARRAY_SIZE(vars); i++)
3184                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3185                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3187         report("Unknown replacement: `%s`", name);
3188         return NULL;
3191 static bool
3192 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3194         char buf[SIZEOF_STR];
3195         int argc;
3196         bool noreplace = flags == FORMAT_NONE;
3198         argv_free(dst_argv);
3200         for (argc = 0; src_argv[argc]; argc++) {
3201                 const char *arg = src_argv[argc];
3202                 size_t bufpos = 0;
3204                 while (arg) {
3205                         char *next = strstr(arg, "%(");
3206                         int len = next - arg;
3207                         const char *value;
3209                         if (!next || noreplace) {
3210                                 len = strlen(arg);
3211                                 value = "";
3213                         } else {
3214                                 value = format_arg(next);
3216                                 if (!value) {
3217                                         return FALSE;
3218                                 }
3219                         }
3221                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3222                                 return FALSE;
3224                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3225                 }
3227                 dst_argv[argc] = strdup(buf);
3228                 if (!dst_argv[argc])
3229                         break;
3230         }
3232         dst_argv[argc] = NULL;
3234         return src_argv[argc] == NULL;
3237 static bool
3238 restore_view_position(struct view *view)
3240         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3241                 return FALSE;
3243         /* Changing the view position cancels the restoring. */
3244         /* FIXME: Changing back to the first line is not detected. */
3245         if (view->offset != 0 || view->lineno != 0) {
3246                 view->p_restore = FALSE;
3247                 return FALSE;
3248         }
3250         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3251             view_is_displayed(view))
3252                 werase(view->win);
3254         view->yoffset = view->p_yoffset;
3255         view->p_restore = FALSE;
3257         return TRUE;
3260 static void
3261 end_update(struct view *view, bool force)
3263         if (!view->pipe)
3264                 return;
3265         while (!view->ops->read(view, NULL))
3266                 if (!force)
3267                         return;
3268         if (force)
3269                 io_kill(view->pipe);
3270         io_done(view->pipe);
3271         view->pipe = NULL;
3274 static void
3275 setup_update(struct view *view, const char *vid)
3277         reset_view(view);
3278         string_copy_rev(view->vid, vid);
3279         view->pipe = &view->io;
3280         view->start_time = time(NULL);
3283 static bool
3284 prepare_update(struct view *view, const char *argv[], const char *dir)
3286         if (view->pipe)
3287                 end_update(view, TRUE);
3288         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3291 static bool
3292 prepare_update_file(struct view *view, const char *name)
3294         if (view->pipe)
3295                 end_update(view, TRUE);
3296         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3299 static bool
3300 begin_update(struct view *view, bool refresh)
3302         if (view->pipe)
3303                 end_update(view, TRUE);
3305         if (!refresh) {
3306                 if (view->ops->prepare) {
3307                         if (!view->ops->prepare(view))
3308                                 return FALSE;
3309                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3310                         return FALSE;
3311                 }
3313                 /* Put the current ref_* value to the view title ref
3314                  * member. This is needed by the blob view. Most other
3315                  * views sets it automatically after loading because the
3316                  * first line is a commit line. */
3317                 string_copy_rev(view->ref, view->id);
3318         }
3320         if (!io_start(&view->io))
3321                 return FALSE;
3323         setup_update(view, view->id);
3325         return TRUE;
3328 static bool
3329 update_view(struct view *view)
3331         char out_buffer[BUFSIZ * 2];
3332         char *line;
3333         /* Clear the view and redraw everything since the tree sorting
3334          * might have rearranged things. */
3335         bool redraw = view->lines == 0;
3336         bool can_read = TRUE;
3338         if (!view->pipe)
3339                 return TRUE;
3341         if (!io_can_read(view->pipe)) {
3342                 if (view->lines == 0 && view_is_displayed(view)) {
3343                         time_t secs = time(NULL) - view->start_time;
3345                         if (secs > 1 && secs > view->update_secs) {
3346                                 if (view->update_secs == 0)
3347                                         redraw_view(view);
3348                                 update_view_title(view);
3349                                 view->update_secs = secs;
3350                         }
3351                 }
3352                 return TRUE;
3353         }
3355         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3356                 if (opt_iconv_in != ICONV_NONE) {
3357                         ICONV_CONST char *inbuf = line;
3358                         size_t inlen = strlen(line) + 1;
3360                         char *outbuf = out_buffer;
3361                         size_t outlen = sizeof(out_buffer);
3363                         size_t ret;
3365                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3366                         if (ret != (size_t) -1)
3367                                 line = out_buffer;
3368                 }
3370                 if (!view->ops->read(view, line)) {
3371                         report("Allocation failure");
3372                         end_update(view, TRUE);
3373                         return FALSE;
3374                 }
3375         }
3377         {
3378                 unsigned long lines = view->lines;
3379                 int digits;
3381                 for (digits = 0; lines; digits++)
3382                         lines /= 10;
3384                 /* Keep the displayed view in sync with line number scaling. */
3385                 if (digits != view->digits) {
3386                         view->digits = digits;
3387                         if (opt_line_number || view->type == VIEW_BLAME)
3388                                 redraw = TRUE;
3389                 }
3390         }
3392         if (io_error(view->pipe)) {
3393                 report("Failed to read: %s", io_strerror(view->pipe));
3394                 end_update(view, TRUE);
3396         } else if (io_eof(view->pipe)) {
3397                 if (view_is_displayed(view))
3398                         report("");
3399                 end_update(view, FALSE);
3400         }
3402         if (restore_view_position(view))
3403                 redraw = TRUE;
3405         if (!view_is_displayed(view))
3406                 return TRUE;
3408         if (redraw)
3409                 redraw_view_from(view, 0);
3410         else
3411                 redraw_view_dirty(view);
3413         /* Update the title _after_ the redraw so that if the redraw picks up a
3414          * commit reference in view->ref it'll be available here. */
3415         update_view_title(view);
3416         return TRUE;
3419 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3421 static struct line *
3422 add_line_data(struct view *view, void *data, enum line_type type)
3424         struct line *line;
3426         if (!realloc_lines(&view->line, view->lines, 1))
3427                 return NULL;
3429         line = &view->line[view->lines++];
3430         memset(line, 0, sizeof(*line));
3431         line->type = type;
3432         line->data = data;
3433         line->dirty = 1;
3435         return line;
3438 static struct line *
3439 add_line_text(struct view *view, const char *text, enum line_type type)
3441         char *data = text ? strdup(text) : NULL;
3443         return data ? add_line_data(view, data, type) : NULL;
3446 static struct line *
3447 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3449         char buf[SIZEOF_STR];
3450         va_list args;
3452         va_start(args, fmt);
3453         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3454                 buf[0] = 0;
3455         va_end(args);
3457         return buf[0] ? add_line_text(view, buf, type) : NULL;
3460 /*
3461  * View opening
3462  */
3464 enum open_flags {
3465         OPEN_DEFAULT = 0,       /* Use default view switching. */
3466         OPEN_SPLIT = 1,         /* Split current view. */
3467         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3468         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3469         OPEN_PREPARED = 32,     /* Open already prepared command. */
3470 };
3472 static void
3473 open_view(struct view *prev, enum request request, enum open_flags flags)
3475         bool split = !!(flags & OPEN_SPLIT);
3476         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3477         bool nomaximize = !!(flags & OPEN_REFRESH);
3478         struct view *view = VIEW(request);
3479         int nviews = displayed_views();
3480         struct view *base_view = display[0];
3482         if (view == prev && nviews == 1 && !reload) {
3483                 report("Already in %s view", view->name);
3484                 return;
3485         }
3487         if (view->git_dir && !opt_git_dir[0]) {
3488                 report("The %s view is disabled in pager view", view->name);
3489                 return;
3490         }
3492         if (split) {
3493                 display[1] = view;
3494                 current_view = 1;
3495                 view->parent = prev;
3496         } else if (!nomaximize) {
3497                 /* Maximize the current view. */
3498                 memset(display, 0, sizeof(display));
3499                 current_view = 0;
3500                 display[current_view] = view;
3501         }
3503         /* No prev signals that this is the first loaded view. */
3504         if (prev && view != prev) {
3505                 view->prev = prev;
3506         }
3508         /* Resize the view when switching between split- and full-screen,
3509          * or when switching between two different full-screen views. */
3510         if (nviews != displayed_views() ||
3511             (nviews == 1 && base_view != display[0]))
3512                 resize_display();
3514         if (view->ops->open) {
3515                 if (view->pipe)
3516                         end_update(view, TRUE);
3517                 if (!view->ops->open(view)) {
3518                         report("Failed to load %s view", view->name);
3519                         return;
3520                 }
3521                 restore_view_position(view);
3523         } else if ((reload || strcmp(view->vid, view->id)) &&
3524                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3525                 report("Failed to load %s view", view->name);
3526                 return;
3527         }
3529         if (split && prev->lineno - prev->offset >= prev->height) {
3530                 /* Take the title line into account. */
3531                 int lines = prev->lineno - prev->offset - prev->height + 1;
3533                 /* Scroll the view that was split if the current line is
3534                  * outside the new limited view. */
3535                 do_scroll_view(prev, lines);
3536         }
3538         if (prev && view != prev && split && view_is_displayed(prev)) {
3539                 /* "Blur" the previous view. */
3540                 update_view_title(prev);
3541         }
3543         if (view->pipe && view->lines == 0) {
3544                 /* Clear the old view and let the incremental updating refill
3545                  * the screen. */
3546                 werase(view->win);
3547                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3548                 report("");
3549         } else if (view_is_displayed(view)) {
3550                 redraw_view(view);
3551                 report("");
3552         }
3555 static void
3556 open_external_viewer(const char *argv[], const char *dir)
3558         def_prog_mode();           /* save current tty modes */
3559         endwin();                  /* restore original tty modes */
3560         io_run_fg(argv, dir);
3561         fprintf(stderr, "Press Enter to continue");
3562         getc(opt_tty);
3563         reset_prog_mode();
3564         redraw_display(TRUE);
3567 static void
3568 open_mergetool(const char *file)
3570         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3572         open_external_viewer(mergetool_argv, opt_cdup);
3575 static void
3576 open_editor(const char *file)
3578         const char *editor_argv[] = { "vi", file, NULL };
3579         const char *editor;
3581         editor = getenv("GIT_EDITOR");
3582         if (!editor && *opt_editor)
3583                 editor = opt_editor;
3584         if (!editor)
3585                 editor = getenv("VISUAL");
3586         if (!editor)
3587                 editor = getenv("EDITOR");
3588         if (!editor)
3589                 editor = "vi";
3591         editor_argv[0] = editor;
3592         open_external_viewer(editor_argv, opt_cdup);
3595 static void
3596 open_run_request(enum request request)
3598         struct run_request *req = get_run_request(request);
3599         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3601         if (!req) {
3602                 report("Unknown run request");
3603                 return;
3604         }
3606         if (format_argv(argv, req->argv, FORMAT_ALL))
3607                 open_external_viewer(argv, NULL);
3608         argv_free(argv);
3611 /*
3612  * User request switch noodle
3613  */
3615 static int
3616 view_driver(struct view *view, enum request request)
3618         int i;
3620         if (request == REQ_NONE)
3621                 return TRUE;
3623         if (request > REQ_NONE) {
3624                 open_run_request(request);
3625                 /* FIXME: When all views can refresh always do this. */
3626                 if (view->refresh)
3627                         request = REQ_REFRESH;
3628                 else
3629                         return TRUE;
3630         }
3632         request = view_request(view, request);
3633         if (request == REQ_NONE)
3634                 return TRUE;
3636         switch (request) {
3637         case REQ_MOVE_UP:
3638         case REQ_MOVE_DOWN:
3639         case REQ_MOVE_PAGE_UP:
3640         case REQ_MOVE_PAGE_DOWN:
3641         case REQ_MOVE_FIRST_LINE:
3642         case REQ_MOVE_LAST_LINE:
3643                 move_view(view, request);
3644                 break;
3646         case REQ_SCROLL_LEFT:
3647         case REQ_SCROLL_RIGHT:
3648         case REQ_SCROLL_LINE_DOWN:
3649         case REQ_SCROLL_LINE_UP:
3650         case REQ_SCROLL_PAGE_DOWN:
3651         case REQ_SCROLL_PAGE_UP:
3652                 scroll_view(view, request);
3653                 break;
3655         case REQ_VIEW_BLAME:
3656                 if (!opt_file[0]) {
3657                         report("No file chosen, press %s to open tree view",
3658                                get_key(view->keymap, REQ_VIEW_TREE));
3659                         break;
3660                 }
3661                 open_view(view, request, OPEN_DEFAULT);
3662                 break;
3664         case REQ_VIEW_BLOB:
3665                 if (!ref_blob[0]) {
3666                         report("No file chosen, press %s to open tree view",
3667                                get_key(view->keymap, REQ_VIEW_TREE));
3668                         break;
3669                 }
3670                 open_view(view, request, OPEN_DEFAULT);
3671                 break;
3673         case REQ_VIEW_PAGER:
3674                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3675                         report("No pager content, press %s to run command from prompt",
3676                                get_key(view->keymap, REQ_PROMPT));
3677                         break;
3678                 }
3679                 open_view(view, request, OPEN_DEFAULT);
3680                 break;
3682         case REQ_VIEW_STAGE:
3683                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3684                         report("No stage content, press %s to open the status view and choose file",
3685                                get_key(view->keymap, REQ_VIEW_STATUS));
3686                         break;
3687                 }
3688                 open_view(view, request, OPEN_DEFAULT);
3689                 break;
3691         case REQ_VIEW_STATUS:
3692                 if (opt_is_inside_work_tree == FALSE) {
3693                         report("The status view requires a working tree");
3694                         break;
3695                 }
3696                 open_view(view, request, OPEN_DEFAULT);
3697                 break;
3699         case REQ_VIEW_MAIN:
3700         case REQ_VIEW_DIFF:
3701         case REQ_VIEW_LOG:
3702         case REQ_VIEW_TREE:
3703         case REQ_VIEW_HELP:
3704         case REQ_VIEW_BRANCH:
3705                 open_view(view, request, OPEN_DEFAULT);
3706                 break;
3708         case REQ_NEXT:
3709         case REQ_PREVIOUS:
3710                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3712                 if (view->parent) {
3713                         int line;
3715                         view = view->parent;
3716                         line = view->lineno;
3717                         move_view(view, request);
3718                         if (view_is_displayed(view))
3719                                 update_view_title(view);
3720                         if (line != view->lineno)
3721                                 view_request(view, REQ_ENTER);
3722                 } else {
3723                         move_view(view, request);
3724                 }
3725                 break;
3727         case REQ_VIEW_NEXT:
3728         {
3729                 int nviews = displayed_views();
3730                 int next_view = (current_view + 1) % nviews;
3732                 if (next_view == current_view) {
3733                         report("Only one view is displayed");
3734                         break;
3735                 }
3737                 current_view = next_view;
3738                 /* Blur out the title of the previous view. */
3739                 update_view_title(view);
3740                 report("");
3741                 break;
3742         }
3743         case REQ_REFRESH:
3744                 report("Refreshing is not yet supported for the %s view", view->name);
3745                 break;
3747         case REQ_MAXIMIZE:
3748                 if (displayed_views() == 2)
3749                         maximize_view(view);
3750                 break;
3752         case REQ_OPTIONS:
3753                 open_option_menu();
3754                 break;
3756         case REQ_TOGGLE_LINENO:
3757                 toggle_view_option(&opt_line_number, "line numbers");
3758                 break;
3760         case REQ_TOGGLE_DATE:
3761                 toggle_date();
3762                 break;
3764         case REQ_TOGGLE_AUTHOR:
3765                 toggle_author();
3766                 break;
3768         case REQ_TOGGLE_REV_GRAPH:
3769                 toggle_view_option(&opt_rev_graph, "revision graph display");
3770                 break;
3772         case REQ_TOGGLE_REFS:
3773                 toggle_view_option(&opt_show_refs, "reference display");
3774                 break;
3776         case REQ_TOGGLE_SORT_FIELD:
3777         case REQ_TOGGLE_SORT_ORDER:
3778                 report("Sorting is not yet supported for the %s view", view->name);
3779                 break;
3781         case REQ_SEARCH:
3782         case REQ_SEARCH_BACK:
3783                 search_view(view, request);
3784                 break;
3786         case REQ_FIND_NEXT:
3787         case REQ_FIND_PREV:
3788                 find_next(view, request);
3789                 break;
3791         case REQ_STOP_LOADING:
3792                 foreach_view(view, i) {
3793                         if (view->pipe)
3794                                 report("Stopped loading the %s view", view->name),
3795                         end_update(view, TRUE);
3796                 }
3797                 break;
3799         case REQ_SHOW_VERSION:
3800                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3801                 return TRUE;
3803         case REQ_SCREEN_REDRAW:
3804                 redraw_display(TRUE);
3805                 break;
3807         case REQ_EDIT:
3808                 report("Nothing to edit");
3809                 break;
3811         case REQ_ENTER:
3812                 report("Nothing to enter");
3813                 break;
3815         case REQ_VIEW_CLOSE:
3816                 /* XXX: Mark closed views by letting view->prev point to the
3817                  * view itself. Parents to closed view should never be
3818                  * followed. */
3819                 if (view->prev && view->prev != view) {
3820                         maximize_view(view->prev);
3821                         view->prev = view;
3822                         break;
3823                 }
3824                 /* Fall-through */
3825         case REQ_QUIT:
3826                 return FALSE;
3828         default:
3829                 report("Unknown key, press %s for help",
3830                        get_key(view->keymap, REQ_VIEW_HELP));
3831                 return TRUE;
3832         }
3834         return TRUE;
3838 /*
3839  * View backend utilities
3840  */
3842 enum sort_field {
3843         ORDERBY_NAME,
3844         ORDERBY_DATE,
3845         ORDERBY_AUTHOR,
3846 };
3848 struct sort_state {
3849         const enum sort_field *fields;
3850         size_t size, current;
3851         bool reverse;
3852 };
3854 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3855 #define get_sort_field(state) ((state).fields[(state).current])
3856 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3858 static void
3859 sort_view(struct view *view, enum request request, struct sort_state *state,
3860           int (*compare)(const void *, const void *))
3862         switch (request) {
3863         case REQ_TOGGLE_SORT_FIELD:
3864                 state->current = (state->current + 1) % state->size;
3865                 break;
3867         case REQ_TOGGLE_SORT_ORDER:
3868                 state->reverse = !state->reverse;
3869                 break;
3870         default:
3871                 die("Not a sort request");
3872         }
3874         qsort(view->line, view->lines, sizeof(*view->line), compare);
3875         redraw_view(view);
3878 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3880 /* Small author cache to reduce memory consumption. It uses binary
3881  * search to lookup or find place to position new entries. No entries
3882  * are ever freed. */
3883 static const char *
3884 get_author(const char *name)
3886         static const char **authors;
3887         static size_t authors_size;
3888         int from = 0, to = authors_size - 1;
3890         while (from <= to) {
3891                 size_t pos = (to + from) / 2;
3892                 int cmp = strcmp(name, authors[pos]);
3894                 if (!cmp)
3895                         return authors[pos];
3897                 if (cmp < 0)
3898                         to = pos - 1;
3899                 else
3900                         from = pos + 1;
3901         }
3903         if (!realloc_authors(&authors, authors_size, 1))
3904                 return NULL;
3905         name = strdup(name);
3906         if (!name)
3907                 return NULL;
3909         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3910         authors[from] = name;
3911         authors_size++;
3913         return name;
3916 static void
3917 parse_timesec(struct time *time, const char *sec)
3919         time->sec = (time_t) atol(sec);
3922 static void
3923 parse_timezone(struct time *time, const char *zone)
3925         long tz;
3927         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3928         tz += ('0' - zone[2]) * 60 * 60;
3929         tz += ('0' - zone[3]) * 60 * 10;
3930         tz += ('0' - zone[4]) * 60;
3932         if (zone[0] == '-')
3933                 tz = -tz;
3935         time->tz = tz;
3936         time->sec -= tz;
3939 /* Parse author lines where the name may be empty:
3940  *      author  <email@address.tld> 1138474660 +0100
3941  */
3942 static void
3943 parse_author_line(char *ident, const char **author, struct time *time)
3945         char *nameend = strchr(ident, '<');
3946         char *emailend = strchr(ident, '>');
3948         if (nameend && emailend)
3949                 *nameend = *emailend = 0;
3950         ident = chomp_string(ident);
3951         if (!*ident) {
3952                 if (nameend)
3953                         ident = chomp_string(nameend + 1);
3954                 if (!*ident)
3955                         ident = "Unknown";
3956         }
3958         *author = get_author(ident);
3960         /* Parse epoch and timezone */
3961         if (emailend && emailend[1] == ' ') {
3962                 char *secs = emailend + 2;
3963                 char *zone = strchr(secs, ' ');
3965                 parse_timesec(time, secs);
3967                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3968                         parse_timezone(time, zone + 1);
3969         }
3972 static bool
3973 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3975         char rev[SIZEOF_REV];
3976         const char *revlist_argv[] = {
3977                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3978         };
3979         struct menu_item *items;
3980         char text[SIZEOF_STR];
3981         bool ok = TRUE;
3982         int i;
3984         items = calloc(*parents + 1, sizeof(*items));
3985         if (!items)
3986                 return FALSE;
3988         for (i = 0; i < *parents; i++) {
3989                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3990                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3991                     !(items[i].text = strdup(text))) {
3992                         ok = FALSE;
3993                         break;
3994                 }
3995         }
3997         if (ok) {
3998                 *parents = 0;
3999                 ok = prompt_menu("Select parent", items, parents);
4000         }
4001         for (i = 0; items[i].text; i++)
4002                 free((char *) items[i].text);
4003         free(items);
4004         return ok;
4007 static bool
4008 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4010         char buf[SIZEOF_STR * 4];
4011         const char *revlist_argv[] = {
4012                 "git", "log", "--no-color", "-1",
4013                         "--pretty=format:%P", id, "--", path, NULL
4014         };
4015         int parents;
4017         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4018             (parents = strlen(buf) / 40) < 0) {
4019                 report("Failed to get parent information");
4020                 return FALSE;
4022         } else if (parents == 0) {
4023                 if (path)
4024                         report("Path '%s' does not exist in the parent", path);
4025                 else
4026                         report("The selected commit has no parents");
4027                 return FALSE;
4028         }
4030         if (parents == 1)
4031                 parents = 0;
4032         else if (!open_commit_parent_menu(buf, &parents))
4033                 return FALSE;
4035         string_copy_rev(rev, &buf[41 * parents]);
4036         return TRUE;
4039 /*
4040  * Pager backend
4041  */
4043 static bool
4044 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4046         char text[SIZEOF_STR];
4048         if (opt_line_number && draw_lineno(view, lineno))
4049                 return TRUE;
4051         string_expand(text, sizeof(text), line->data, opt_tab_size);
4052         draw_text(view, line->type, text, TRUE);
4053         return TRUE;
4056 static bool
4057 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4059         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4060         char ref[SIZEOF_STR];
4062         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4063                 return TRUE;
4065         /* This is the only fatal call, since it can "corrupt" the buffer. */
4066         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4067                 return FALSE;
4069         return TRUE;
4072 static void
4073 add_pager_refs(struct view *view, struct line *line)
4075         char buf[SIZEOF_STR];
4076         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4077         struct ref_list *list;
4078         size_t bufpos = 0, i;
4079         const char *sep = "Refs: ";
4080         bool is_tag = FALSE;
4082         assert(line->type == LINE_COMMIT);
4084         list = get_ref_list(commit_id);
4085         if (!list) {
4086                 if (view->type == VIEW_DIFF)
4087                         goto try_add_describe_ref;
4088                 return;
4089         }
4091         for (i = 0; i < list->size; i++) {
4092                 struct ref *ref = list->refs[i];
4093                 const char *fmt = ref->tag    ? "%s[%s]" :
4094                                   ref->remote ? "%s<%s>" : "%s%s";
4096                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4097                         return;
4098                 sep = ", ";
4099                 if (ref->tag)
4100                         is_tag = TRUE;
4101         }
4103         if (!is_tag && view->type == VIEW_DIFF) {
4104 try_add_describe_ref:
4105                 /* Add <tag>-g<commit_id> "fake" reference. */
4106                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4107                         return;
4108         }
4110         if (bufpos == 0)
4111                 return;
4113         add_line_text(view, buf, LINE_PP_REFS);
4116 static bool
4117 pager_read(struct view *view, char *data)
4119         struct line *line;
4121         if (!data)
4122                 return TRUE;
4124         line = add_line_text(view, data, get_line_type(data));
4125         if (!line)
4126                 return FALSE;
4128         if (line->type == LINE_COMMIT &&
4129             (view->type == VIEW_DIFF ||
4130              view->type == VIEW_LOG))
4131                 add_pager_refs(view, line);
4133         return TRUE;
4136 static enum request
4137 pager_request(struct view *view, enum request request, struct line *line)
4139         int split = 0;
4141         if (request != REQ_ENTER)
4142                 return request;
4144         if (line->type == LINE_COMMIT &&
4145            (view->type == VIEW_LOG ||
4146             view->type == VIEW_PAGER)) {
4147                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4148                 split = 1;
4149         }
4151         /* Always scroll the view even if it was split. That way
4152          * you can use Enter to scroll through the log view and
4153          * split open each commit diff. */
4154         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4156         /* FIXME: A minor workaround. Scrolling the view will call report("")
4157          * but if we are scrolling a non-current view this won't properly
4158          * update the view title. */
4159         if (split)
4160                 update_view_title(view);
4162         return REQ_NONE;
4165 static bool
4166 pager_grep(struct view *view, struct line *line)
4168         const char *text[] = { line->data, NULL };
4170         return grep_text(view, text);
4173 static void
4174 pager_select(struct view *view, struct line *line)
4176         if (line->type == LINE_COMMIT) {
4177                 char *text = (char *)line->data + STRING_SIZE("commit ");
4179                 if (view->type != VIEW_PAGER)
4180                         string_copy_rev(view->ref, text);
4181                 string_copy_rev(ref_commit, text);
4182         }
4185 static struct view_ops pager_ops = {
4186         "line",
4187         NULL,
4188         NULL,
4189         pager_read,
4190         pager_draw,
4191         pager_request,
4192         pager_grep,
4193         pager_select,
4194 };
4196 static const char *log_argv[SIZEOF_ARG] = {
4197         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4198 };
4200 static enum request
4201 log_request(struct view *view, enum request request, struct line *line)
4203         switch (request) {
4204         case REQ_REFRESH:
4205                 load_refs();
4206                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4207                 return REQ_NONE;
4208         default:
4209                 return pager_request(view, request, line);
4210         }
4213 static struct view_ops log_ops = {
4214         "line",
4215         log_argv,
4216         NULL,
4217         pager_read,
4218         pager_draw,
4219         log_request,
4220         pager_grep,
4221         pager_select,
4222 };
4224 static const char *diff_argv[SIZEOF_ARG] = {
4225         "git", "show", "--pretty=fuller", "--no-color", "--root",
4226                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4227 };
4229 static struct view_ops diff_ops = {
4230         "line",
4231         diff_argv,
4232         NULL,
4233         pager_read,
4234         pager_draw,
4235         pager_request,
4236         pager_grep,
4237         pager_select,
4238 };
4240 /*
4241  * Help backend
4242  */
4244 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4246 static bool
4247 help_open_keymap_title(struct view *view, enum keymap keymap)
4249         struct line *line;
4251         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4252                                help_keymap_hidden[keymap] ? '+' : '-',
4253                                enum_name(keymap_table[keymap]));
4254         if (line)
4255                 line->other = keymap;
4257         return help_keymap_hidden[keymap];
4260 static void
4261 help_open_keymap(struct view *view, enum keymap keymap)
4263         const char *group = NULL;
4264         char buf[SIZEOF_STR];
4265         size_t bufpos;
4266         bool add_title = TRUE;
4267         int i;
4269         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4270                 const char *key = NULL;
4272                 if (req_info[i].request == REQ_NONE)
4273                         continue;
4275                 if (!req_info[i].request) {
4276                         group = req_info[i].help;
4277                         continue;
4278                 }
4280                 key = get_keys(keymap, req_info[i].request, TRUE);
4281                 if (!key || !*key)
4282                         continue;
4284                 if (add_title && help_open_keymap_title(view, keymap))
4285                         return;
4286                 add_title = FALSE;
4288                 if (group) {
4289                         add_line_text(view, group, LINE_HELP_GROUP);
4290                         group = NULL;
4291                 }
4293                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4294                                 enum_name(req_info[i]), req_info[i].help);
4295         }
4297         group = "External commands:";
4299         for (i = 0; i < run_requests; i++) {
4300                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4301                 const char *key;
4302                 int argc;
4304                 if (!req || req->keymap != keymap)
4305                         continue;
4307                 key = get_key_name(req->key);
4308                 if (!*key)
4309                         key = "(no key defined)";
4311                 if (add_title && help_open_keymap_title(view, keymap))
4312                         return;
4313                 if (group) {
4314                         add_line_text(view, group, LINE_HELP_GROUP);
4315                         group = NULL;
4316                 }
4318                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4319                         if (!string_format_from(buf, &bufpos, "%s%s",
4320                                                 argc ? " " : "", req->argv[argc]))
4321                                 return;
4323                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4324         }
4327 static bool
4328 help_open(struct view *view)
4330         enum keymap keymap;
4332         reset_view(view);
4333         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4334         add_line_text(view, "", LINE_DEFAULT);
4336         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4337                 help_open_keymap(view, keymap);
4339         return TRUE;
4342 static enum request
4343 help_request(struct view *view, enum request request, struct line *line)
4345         switch (request) {
4346         case REQ_ENTER:
4347                 if (line->type == LINE_HELP_KEYMAP) {
4348                         help_keymap_hidden[line->other] =
4349                                 !help_keymap_hidden[line->other];
4350                         view->p_restore = TRUE;
4351                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4352                 }
4354                 return REQ_NONE;
4355         default:
4356                 return pager_request(view, request, line);
4357         }
4360 static struct view_ops help_ops = {
4361         "line",
4362         NULL,
4363         help_open,
4364         NULL,
4365         pager_draw,
4366         help_request,
4367         pager_grep,
4368         pager_select,
4369 };
4372 /*
4373  * Tree backend
4374  */
4376 struct tree_stack_entry {
4377         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4378         unsigned long lineno;           /* Line number to restore */
4379         char *name;                     /* Position of name in opt_path */
4380 };
4382 /* The top of the path stack. */
4383 static struct tree_stack_entry *tree_stack = NULL;
4384 unsigned long tree_lineno = 0;
4386 static void
4387 pop_tree_stack_entry(void)
4389         struct tree_stack_entry *entry = tree_stack;
4391         tree_lineno = entry->lineno;
4392         entry->name[0] = 0;
4393         tree_stack = entry->prev;
4394         free(entry);
4397 static void
4398 push_tree_stack_entry(const char *name, unsigned long lineno)
4400         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4401         size_t pathlen = strlen(opt_path);
4403         if (!entry)
4404                 return;
4406         entry->prev = tree_stack;
4407         entry->name = opt_path + pathlen;
4408         tree_stack = entry;
4410         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4411                 pop_tree_stack_entry();
4412                 return;
4413         }
4415         /* Move the current line to the first tree entry. */
4416         tree_lineno = 1;
4417         entry->lineno = lineno;
4420 /* Parse output from git-ls-tree(1):
4421  *
4422  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4423  */
4425 #define SIZEOF_TREE_ATTR \
4426         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4428 #define SIZEOF_TREE_MODE \
4429         STRING_SIZE("100644 ")
4431 #define TREE_ID_OFFSET \
4432         STRING_SIZE("100644 blob ")
4434 struct tree_entry {
4435         char id[SIZEOF_REV];
4436         mode_t mode;
4437         struct time time;               /* Date from the author ident. */
4438         const char *author;             /* Author of the commit. */
4439         char name[1];
4440 };
4442 static const char *
4443 tree_path(const struct line *line)
4445         return ((struct tree_entry *) line->data)->name;
4448 static int
4449 tree_compare_entry(const struct line *line1, const struct line *line2)
4451         if (line1->type != line2->type)
4452                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4453         return strcmp(tree_path(line1), tree_path(line2));
4456 static const enum sort_field tree_sort_fields[] = {
4457         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4458 };
4459 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4461 static int
4462 tree_compare(const void *l1, const void *l2)
4464         const struct line *line1 = (const struct line *) l1;
4465         const struct line *line2 = (const struct line *) l2;
4466         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4467         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4469         if (line1->type == LINE_TREE_HEAD)
4470                 return -1;
4471         if (line2->type == LINE_TREE_HEAD)
4472                 return 1;
4474         switch (get_sort_field(tree_sort_state)) {
4475         case ORDERBY_DATE:
4476                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4478         case ORDERBY_AUTHOR:
4479                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4481         case ORDERBY_NAME:
4482         default:
4483                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4484         }
4488 static struct line *
4489 tree_entry(struct view *view, enum line_type type, const char *path,
4490            const char *mode, const char *id)
4492         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4493         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4495         if (!entry || !line) {
4496                 free(entry);
4497                 return NULL;
4498         }
4500         strncpy(entry->name, path, strlen(path));
4501         if (mode)
4502                 entry->mode = strtoul(mode, NULL, 8);
4503         if (id)
4504                 string_copy_rev(entry->id, id);
4506         return line;
4509 static bool
4510 tree_read_date(struct view *view, char *text, bool *read_date)
4512         static const char *author_name;
4513         static struct time author_time;
4515         if (!text && *read_date) {
4516                 *read_date = FALSE;
4517                 return TRUE;
4519         } else if (!text) {
4520                 char *path = *opt_path ? opt_path : ".";
4521                 /* Find next entry to process */
4522                 const char *log_file[] = {
4523                         "git", "log", "--no-color", "--pretty=raw",
4524                                 "--cc", "--raw", view->id, "--", path, NULL
4525                 };
4526                 struct io io = {};
4528                 if (!view->lines) {
4529                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4530                         report("Tree is empty");
4531                         return TRUE;
4532                 }
4534                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4535                         report("Failed to load tree data");
4536                         return TRUE;
4537                 }
4539                 io_done(view->pipe);
4540                 view->io = io;
4541                 *read_date = TRUE;
4542                 return FALSE;
4544         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4545                 parse_author_line(text + STRING_SIZE("author "),
4546                                   &author_name, &author_time);
4548         } else if (*text == ':') {
4549                 char *pos;
4550                 size_t annotated = 1;
4551                 size_t i;
4553                 pos = strchr(text, '\t');
4554                 if (!pos)
4555                         return TRUE;
4556                 text = pos + 1;
4557                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4558                         text += strlen(opt_path);
4559                 pos = strchr(text, '/');
4560                 if (pos)
4561                         *pos = 0;
4563                 for (i = 1; i < view->lines; i++) {
4564                         struct line *line = &view->line[i];
4565                         struct tree_entry *entry = line->data;
4567                         annotated += !!entry->author;
4568                         if (entry->author || strcmp(entry->name, text))
4569                                 continue;
4571                         entry->author = author_name;
4572                         entry->time = author_time;
4573                         line->dirty = 1;
4574                         break;
4575                 }
4577                 if (annotated == view->lines)
4578                         io_kill(view->pipe);
4579         }
4580         return TRUE;
4583 static bool
4584 tree_read(struct view *view, char *text)
4586         static bool read_date = FALSE;
4587         struct tree_entry *data;
4588         struct line *entry, *line;
4589         enum line_type type;
4590         size_t textlen = text ? strlen(text) : 0;
4591         char *path = text + SIZEOF_TREE_ATTR;
4593         if (read_date || !text)
4594                 return tree_read_date(view, text, &read_date);
4596         if (textlen <= SIZEOF_TREE_ATTR)
4597                 return FALSE;
4598         if (view->lines == 0 &&
4599             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4600                 return FALSE;
4602         /* Strip the path part ... */
4603         if (*opt_path) {
4604                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4605                 size_t striplen = strlen(opt_path);
4607                 if (pathlen > striplen)
4608                         memmove(path, path + striplen,
4609                                 pathlen - striplen + 1);
4611                 /* Insert "link" to parent directory. */
4612                 if (view->lines == 1 &&
4613                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4614                         return FALSE;
4615         }
4617         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4618         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4619         if (!entry)
4620                 return FALSE;
4621         data = entry->data;
4623         /* Skip "Directory ..." and ".." line. */
4624         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4625                 if (tree_compare_entry(line, entry) <= 0)
4626                         continue;
4628                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4630                 line->data = data;
4631                 line->type = type;
4632                 for (; line <= entry; line++)
4633                         line->dirty = line->cleareol = 1;
4634                 return TRUE;
4635         }
4637         if (tree_lineno > view->lineno) {
4638                 view->lineno = tree_lineno;
4639                 tree_lineno = 0;
4640         }
4642         return TRUE;
4645 static bool
4646 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4648         struct tree_entry *entry = line->data;
4650         if (line->type == LINE_TREE_HEAD) {
4651                 if (draw_text(view, line->type, "Directory path /", TRUE))
4652                         return TRUE;
4653         } else {
4654                 if (draw_mode(view, entry->mode))
4655                         return TRUE;
4657                 if (opt_author && draw_author(view, entry->author))
4658                         return TRUE;
4660                 if (opt_date && draw_date(view, &entry->time))
4661                         return TRUE;
4662         }
4663         if (draw_text(view, line->type, entry->name, TRUE))
4664                 return TRUE;
4665         return TRUE;
4668 static void
4669 open_blob_editor(const char *id)
4671         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4672         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4673         int fd = mkstemp(file);
4675         if (fd == -1)
4676                 report("Failed to create temporary file");
4677         else if (!io_run_append(blob_argv, FORMAT_NONE, fd))
4678                 report("Failed to save blob data to file");
4679         else
4680                 open_editor(file);
4681         if (fd != -1)
4682                 unlink(file);
4685 static enum request
4686 tree_request(struct view *view, enum request request, struct line *line)
4688         enum open_flags flags;
4689         struct tree_entry *entry = line->data;
4691         switch (request) {
4692         case REQ_VIEW_BLAME:
4693                 if (line->type != LINE_TREE_FILE) {
4694                         report("Blame only supported for files");
4695                         return REQ_NONE;
4696                 }
4698                 string_copy(opt_ref, view->vid);
4699                 return request;
4701         case REQ_EDIT:
4702                 if (line->type != LINE_TREE_FILE) {
4703                         report("Edit only supported for files");
4704                 } else if (!is_head_commit(view->vid)) {
4705                         open_blob_editor(entry->id);
4706                 } else {
4707                         open_editor(opt_file);
4708                 }
4709                 return REQ_NONE;
4711         case REQ_TOGGLE_SORT_FIELD:
4712         case REQ_TOGGLE_SORT_ORDER:
4713                 sort_view(view, request, &tree_sort_state, tree_compare);
4714                 return REQ_NONE;
4716         case REQ_PARENT:
4717                 if (!*opt_path) {
4718                         /* quit view if at top of tree */
4719                         return REQ_VIEW_CLOSE;
4720                 }
4721                 /* fake 'cd  ..' */
4722                 line = &view->line[1];
4723                 break;
4725         case REQ_ENTER:
4726                 break;
4728         default:
4729                 return request;
4730         }
4732         /* Cleanup the stack if the tree view is at a different tree. */
4733         while (!*opt_path && tree_stack)
4734                 pop_tree_stack_entry();
4736         switch (line->type) {
4737         case LINE_TREE_DIR:
4738                 /* Depending on whether it is a subdirectory or parent link
4739                  * mangle the path buffer. */
4740                 if (line == &view->line[1] && *opt_path) {
4741                         pop_tree_stack_entry();
4743                 } else {
4744                         const char *basename = tree_path(line);
4746                         push_tree_stack_entry(basename, view->lineno);
4747                 }
4749                 /* Trees and subtrees share the same ID, so they are not not
4750                  * unique like blobs. */
4751                 flags = OPEN_RELOAD;
4752                 request = REQ_VIEW_TREE;
4753                 break;
4755         case LINE_TREE_FILE:
4756                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4757                 request = REQ_VIEW_BLOB;
4758                 break;
4760         default:
4761                 return REQ_NONE;
4762         }
4764         open_view(view, request, flags);
4765         if (request == REQ_VIEW_TREE)
4766                 view->lineno = tree_lineno;
4768         return REQ_NONE;
4771 static bool
4772 tree_grep(struct view *view, struct line *line)
4774         struct tree_entry *entry = line->data;
4775         const char *text[] = {
4776                 entry->name,
4777                 opt_author ? entry->author : "",
4778                 mkdate(&entry->time, opt_date),
4779                 NULL
4780         };
4782         return grep_text(view, text);
4785 static void
4786 tree_select(struct view *view, struct line *line)
4788         struct tree_entry *entry = line->data;
4790         if (line->type == LINE_TREE_FILE) {
4791                 string_copy_rev(ref_blob, entry->id);
4792                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4794         } else if (line->type != LINE_TREE_DIR) {
4795                 return;
4796         }
4798         string_copy_rev(view->ref, entry->id);
4801 static bool
4802 tree_prepare(struct view *view)
4804         if (view->lines == 0 && opt_prefix[0]) {
4805                 char *pos = opt_prefix;
4807                 while (pos && *pos) {
4808                         char *end = strchr(pos, '/');
4810                         if (end)
4811                                 *end = 0;
4812                         push_tree_stack_entry(pos, 0);
4813                         pos = end;
4814                         if (end) {
4815                                 *end = '/';
4816                                 pos++;
4817                         }
4818                 }
4820         } else if (strcmp(view->vid, view->id)) {
4821                 opt_path[0] = 0;
4822         }
4824         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4827 static const char *tree_argv[SIZEOF_ARG] = {
4828         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4829 };
4831 static struct view_ops tree_ops = {
4832         "file",
4833         tree_argv,
4834         NULL,
4835         tree_read,
4836         tree_draw,
4837         tree_request,
4838         tree_grep,
4839         tree_select,
4840         tree_prepare,
4841 };
4843 static bool
4844 blob_read(struct view *view, char *line)
4846         if (!line)
4847                 return TRUE;
4848         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4851 static enum request
4852 blob_request(struct view *view, enum request request, struct line *line)
4854         switch (request) {
4855         case REQ_EDIT:
4856                 open_blob_editor(view->vid);
4857                 return REQ_NONE;
4858         default:
4859                 return pager_request(view, request, line);
4860         }
4863 static const char *blob_argv[SIZEOF_ARG] = {
4864         "git", "cat-file", "blob", "%(blob)", NULL
4865 };
4867 static struct view_ops blob_ops = {
4868         "line",
4869         blob_argv,
4870         NULL,
4871         blob_read,
4872         pager_draw,
4873         blob_request,
4874         pager_grep,
4875         pager_select,
4876 };
4878 /*
4879  * Blame backend
4880  *
4881  * Loading the blame view is a two phase job:
4882  *
4883  *  1. File content is read either using opt_file from the
4884  *     filesystem or using git-cat-file.
4885  *  2. Then blame information is incrementally added by
4886  *     reading output from git-blame.
4887  */
4889 static const char *blame_head_argv[] = {
4890         "git", "blame", "--incremental", "--", "%(file)", NULL
4891 };
4893 static const char *blame_ref_argv[] = {
4894         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4895 };
4897 static const char *blame_cat_file_argv[] = {
4898         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4899 };
4901 struct blame_commit {
4902         char id[SIZEOF_REV];            /* SHA1 ID. */
4903         char title[128];                /* First line of the commit message. */
4904         const char *author;             /* Author of the commit. */
4905         struct time time;               /* Date from the author ident. */
4906         char filename[128];             /* Name of file. */
4907         bool has_previous;              /* Was a "previous" line detected. */
4908 };
4910 struct blame {
4911         struct blame_commit *commit;
4912         unsigned long lineno;
4913         char text[1];
4914 };
4916 static bool
4917 blame_open(struct view *view)
4919         char path[SIZEOF_STR];
4921         if (!view->prev && *opt_prefix) {
4922                 string_copy(path, opt_file);
4923                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4924                         return FALSE;
4925         }
4927         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4928                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4929                         return FALSE;
4930         }
4932         setup_update(view, opt_file);
4933         string_format(view->ref, "%s ...", opt_file);
4935         return TRUE;
4938 static struct blame_commit *
4939 get_blame_commit(struct view *view, const char *id)
4941         size_t i;
4943         for (i = 0; i < view->lines; i++) {
4944                 struct blame *blame = view->line[i].data;
4946                 if (!blame->commit)
4947                         continue;
4949                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4950                         return blame->commit;
4951         }
4953         {
4954                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4956                 if (commit)
4957                         string_ncopy(commit->id, id, SIZEOF_REV);
4958                 return commit;
4959         }
4962 static bool
4963 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4965         const char *pos = *posref;
4967         *posref = NULL;
4968         pos = strchr(pos + 1, ' ');
4969         if (!pos || !isdigit(pos[1]))
4970                 return FALSE;
4971         *number = atoi(pos + 1);
4972         if (*number < min || *number > max)
4973                 return FALSE;
4975         *posref = pos;
4976         return TRUE;
4979 static struct blame_commit *
4980 parse_blame_commit(struct view *view, const char *text, int *blamed)
4982         struct blame_commit *commit;
4983         struct blame *blame;
4984         const char *pos = text + SIZEOF_REV - 2;
4985         size_t orig_lineno = 0;
4986         size_t lineno;
4987         size_t group;
4989         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4990                 return NULL;
4992         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4993             !parse_number(&pos, &lineno, 1, view->lines) ||
4994             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4995                 return NULL;
4997         commit = get_blame_commit(view, text);
4998         if (!commit)
4999                 return NULL;
5001         *blamed += group;
5002         while (group--) {
5003                 struct line *line = &view->line[lineno + group - 1];
5005                 blame = line->data;
5006                 blame->commit = commit;
5007                 blame->lineno = orig_lineno + group - 1;
5008                 line->dirty = 1;
5009         }
5011         return commit;
5014 static bool
5015 blame_read_file(struct view *view, const char *line, bool *read_file)
5017         if (!line) {
5018                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
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, argv, opt_cdup, FORMAT_ALL)) {
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, FORMAT_NONE)) {
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;