Code

Internalize format flags in the IO layer
[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, int fd)
897         struct io io = {};
899         if (!io_format(&io, NULL, IO_AP, argv, FORMAT_NONE)) {
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)
911         return io_format(io, dir, IO_RD, argv, FORMAT_NONE) && 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) && io_read_buf(&io, buf, bufsize);
1051 static int
1052 io_load(struct io *io, const char *separators,
1053         int (*read_property)(char *, size_t, char *, size_t))
1055         char *name;
1056         int state = OK;
1058         if (!io_start(io))
1059                 return ERR;
1061         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1062                 char *value;
1063                 size_t namelen;
1064                 size_t valuelen;
1066                 name = chomp_string(name);
1067                 namelen = strcspn(name, separators);
1069                 if (name[namelen]) {
1070                         name[namelen] = 0;
1071                         value = chomp_string(name + namelen + 1);
1072                         valuelen = strlen(value);
1074                 } else {
1075                         value = "";
1076                         valuelen = 0;
1077                 }
1079                 state = read_property(name, namelen, value, valuelen);
1080         }
1082         if (state != ERR && io_error(io))
1083                 state = ERR;
1084         io_done(io);
1086         return state;
1089 static int
1090 io_run_load(const char **argv, const char *separators,
1091             int (*read_property)(char *, size_t, char *, size_t))
1093         struct io io = {};
1095         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1096                 ? io_load(&io, separators, read_property) : ERR;
1100 /*
1101  * User requests
1102  */
1104 #define REQ_INFO \
1105         /* XXX: Keep the view request first and in sync with views[]. */ \
1106         REQ_GROUP("View switching") \
1107         REQ_(VIEW_MAIN,         "Show main view"), \
1108         REQ_(VIEW_DIFF,         "Show diff view"), \
1109         REQ_(VIEW_LOG,          "Show log view"), \
1110         REQ_(VIEW_TREE,         "Show tree view"), \
1111         REQ_(VIEW_BLOB,         "Show blob view"), \
1112         REQ_(VIEW_BLAME,        "Show blame view"), \
1113         REQ_(VIEW_BRANCH,       "Show branch view"), \
1114         REQ_(VIEW_HELP,         "Show help page"), \
1115         REQ_(VIEW_PAGER,        "Show pager view"), \
1116         REQ_(VIEW_STATUS,       "Show status view"), \
1117         REQ_(VIEW_STAGE,        "Show stage view"), \
1118         \
1119         REQ_GROUP("View manipulation") \
1120         REQ_(ENTER,             "Enter current line and scroll"), \
1121         REQ_(NEXT,              "Move to next"), \
1122         REQ_(PREVIOUS,          "Move to previous"), \
1123         REQ_(PARENT,            "Move to parent"), \
1124         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1125         REQ_(REFRESH,           "Reload and refresh"), \
1126         REQ_(MAXIMIZE,          "Maximize the current view"), \
1127         REQ_(VIEW_CLOSE,        "Close the current view"), \
1128         REQ_(QUIT,              "Close all views and quit"), \
1129         \
1130         REQ_GROUP("View specific requests") \
1131         REQ_(STATUS_UPDATE,     "Update file status"), \
1132         REQ_(STATUS_REVERT,     "Revert file changes"), \
1133         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1134         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1135         \
1136         REQ_GROUP("Cursor navigation") \
1137         REQ_(MOVE_UP,           "Move cursor one line up"), \
1138         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1139         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1140         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1141         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1142         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1143         \
1144         REQ_GROUP("Scrolling") \
1145         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1146         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1147         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1148         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1149         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1150         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1151         \
1152         REQ_GROUP("Searching") \
1153         REQ_(SEARCH,            "Search the view"), \
1154         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1155         REQ_(FIND_NEXT,         "Find next search match"), \
1156         REQ_(FIND_PREV,         "Find previous search match"), \
1157         \
1158         REQ_GROUP("Option manipulation") \
1159         REQ_(OPTIONS,           "Open option menu"), \
1160         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1161         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1162         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1163         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1164         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1165         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1166         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1167         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1168         \
1169         REQ_GROUP("Misc") \
1170         REQ_(PROMPT,            "Bring up the prompt"), \
1171         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1172         REQ_(SHOW_VERSION,      "Show version information"), \
1173         REQ_(STOP_LOADING,      "Stop all loading views"), \
1174         REQ_(EDIT,              "Open in editor"), \
1175         REQ_(NONE,              "Do nothing")
1178 /* User action requests. */
1179 enum request {
1180 #define REQ_GROUP(help)
1181 #define REQ_(req, help) REQ_##req
1183         /* Offset all requests to avoid conflicts with ncurses getch values. */
1184         REQ_UNKNOWN = KEY_MAX + 1,
1185         REQ_OFFSET,
1186         REQ_INFO
1188 #undef  REQ_GROUP
1189 #undef  REQ_
1190 };
1192 struct request_info {
1193         enum request request;
1194         const char *name;
1195         int namelen;
1196         const char *help;
1197 };
1199 static const struct request_info req_info[] = {
1200 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1201 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1202         REQ_INFO
1203 #undef  REQ_GROUP
1204 #undef  REQ_
1205 };
1207 static enum request
1208 get_request(const char *name)
1210         int namelen = strlen(name);
1211         int i;
1213         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1214                 if (enum_equals(req_info[i], name, namelen))
1215                         return req_info[i].request;
1217         return REQ_UNKNOWN;
1221 /*
1222  * Options
1223  */
1225 /* Option and state variables. */
1226 static enum date opt_date               = DATE_DEFAULT;
1227 static enum author opt_author           = AUTHOR_DEFAULT;
1228 static bool opt_line_number             = FALSE;
1229 static bool opt_line_graphics           = TRUE;
1230 static bool opt_rev_graph               = FALSE;
1231 static bool opt_show_refs               = TRUE;
1232 static int opt_num_interval             = 5;
1233 static double opt_hscroll               = 0.50;
1234 static double opt_scale_split_view      = 2.0 / 3.0;
1235 static int opt_tab_size                 = 8;
1236 static int opt_author_cols              = AUTHOR_COLS;
1237 static char opt_path[SIZEOF_STR]        = "";
1238 static char opt_file[SIZEOF_STR]        = "";
1239 static char opt_ref[SIZEOF_REF]         = "";
1240 static char opt_head[SIZEOF_REF]        = "";
1241 static char opt_remote[SIZEOF_REF]      = "";
1242 static char opt_encoding[20]            = "UTF-8";
1243 static iconv_t opt_iconv_in             = ICONV_NONE;
1244 static iconv_t opt_iconv_out            = ICONV_NONE;
1245 static char opt_search[SIZEOF_STR]      = "";
1246 static char opt_cdup[SIZEOF_STR]        = "";
1247 static char opt_prefix[SIZEOF_STR]      = "";
1248 static char opt_git_dir[SIZEOF_STR]     = "";
1249 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1250 static char opt_editor[SIZEOF_STR]      = "";
1251 static FILE *opt_tty                    = NULL;
1253 #define is_initial_commit()     (!get_ref_head())
1254 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1257 /*
1258  * Line-oriented content detection.
1259  */
1261 #define LINE_INFO \
1262 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1263 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1264 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1265 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1266 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1267 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1268 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1269 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1270 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1272 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1273 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1274 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1275 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1276 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1277 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1278 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1279 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1280 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1281 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1282 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1283 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1284 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1285 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1286 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1287 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1288 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1289 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1290 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1291 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1292 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1293 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1294 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1295 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1296 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1297 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1298 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1299 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1300 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1301 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1302 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1303 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1304 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1305 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1306 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1307 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1308 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1309 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1310 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1311 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1312 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1313 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1314 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1315 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1316 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1317 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1318 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1319 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1320 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1322 enum line_type {
1323 #define LINE(type, line, fg, bg, attr) \
1324         LINE_##type
1325         LINE_INFO,
1326         LINE_NONE
1327 #undef  LINE
1328 };
1330 struct line_info {
1331         const char *name;       /* Option name. */
1332         int namelen;            /* Size of option name. */
1333         const char *line;       /* The start of line to match. */
1334         int linelen;            /* Size of string to match. */
1335         int fg, bg, attr;       /* Color and text attributes for the lines. */
1336 };
1338 static struct line_info line_info[] = {
1339 #define LINE(type, line, fg, bg, attr) \
1340         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1341         LINE_INFO
1342 #undef  LINE
1343 };
1345 static enum line_type
1346 get_line_type(const char *line)
1348         int linelen = strlen(line);
1349         enum line_type type;
1351         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1352                 /* Case insensitive search matches Signed-off-by lines better. */
1353                 if (linelen >= line_info[type].linelen &&
1354                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1355                         return type;
1357         return LINE_DEFAULT;
1360 static inline int
1361 get_line_attr(enum line_type type)
1363         assert(type < ARRAY_SIZE(line_info));
1364         return COLOR_PAIR(type) | line_info[type].attr;
1367 static struct line_info *
1368 get_line_info(const char *name)
1370         size_t namelen = strlen(name);
1371         enum line_type type;
1373         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1374                 if (enum_equals(line_info[type], name, namelen))
1375                         return &line_info[type];
1377         return NULL;
1380 static void
1381 init_colors(void)
1383         int default_bg = line_info[LINE_DEFAULT].bg;
1384         int default_fg = line_info[LINE_DEFAULT].fg;
1385         enum line_type type;
1387         start_color();
1389         if (assume_default_colors(default_fg, default_bg) == ERR) {
1390                 default_bg = COLOR_BLACK;
1391                 default_fg = COLOR_WHITE;
1392         }
1394         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1395                 struct line_info *info = &line_info[type];
1396                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1397                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1399                 init_pair(type, fg, bg);
1400         }
1403 struct line {
1404         enum line_type type;
1406         /* State flags */
1407         unsigned int selected:1;
1408         unsigned int dirty:1;
1409         unsigned int cleareol:1;
1410         unsigned int other:16;
1412         void *data;             /* User data */
1413 };
1416 /*
1417  * Keys
1418  */
1420 struct keybinding {
1421         int alias;
1422         enum request request;
1423 };
1425 static struct keybinding default_keybindings[] = {
1426         /* View switching */
1427         { 'm',          REQ_VIEW_MAIN },
1428         { 'd',          REQ_VIEW_DIFF },
1429         { 'l',          REQ_VIEW_LOG },
1430         { 't',          REQ_VIEW_TREE },
1431         { 'f',          REQ_VIEW_BLOB },
1432         { 'B',          REQ_VIEW_BLAME },
1433         { 'H',          REQ_VIEW_BRANCH },
1434         { 'p',          REQ_VIEW_PAGER },
1435         { 'h',          REQ_VIEW_HELP },
1436         { 'S',          REQ_VIEW_STATUS },
1437         { 'c',          REQ_VIEW_STAGE },
1439         /* View manipulation */
1440         { 'q',          REQ_VIEW_CLOSE },
1441         { KEY_TAB,      REQ_VIEW_NEXT },
1442         { KEY_RETURN,   REQ_ENTER },
1443         { KEY_UP,       REQ_PREVIOUS },
1444         { KEY_DOWN,     REQ_NEXT },
1445         { 'R',          REQ_REFRESH },
1446         { KEY_F(5),     REQ_REFRESH },
1447         { 'O',          REQ_MAXIMIZE },
1449         /* Cursor navigation */
1450         { 'k',          REQ_MOVE_UP },
1451         { 'j',          REQ_MOVE_DOWN },
1452         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1453         { KEY_END,      REQ_MOVE_LAST_LINE },
1454         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1455         { ' ',          REQ_MOVE_PAGE_DOWN },
1456         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1457         { 'b',          REQ_MOVE_PAGE_UP },
1458         { '-',          REQ_MOVE_PAGE_UP },
1460         /* Scrolling */
1461         { KEY_LEFT,     REQ_SCROLL_LEFT },
1462         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1463         { KEY_IC,       REQ_SCROLL_LINE_UP },
1464         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1465         { 'w',          REQ_SCROLL_PAGE_UP },
1466         { 's',          REQ_SCROLL_PAGE_DOWN },
1468         /* Searching */
1469         { '/',          REQ_SEARCH },
1470         { '?',          REQ_SEARCH_BACK },
1471         { 'n',          REQ_FIND_NEXT },
1472         { 'N',          REQ_FIND_PREV },
1474         /* Misc */
1475         { 'Q',          REQ_QUIT },
1476         { 'z',          REQ_STOP_LOADING },
1477         { 'v',          REQ_SHOW_VERSION },
1478         { 'r',          REQ_SCREEN_REDRAW },
1479         { 'o',          REQ_OPTIONS },
1480         { '.',          REQ_TOGGLE_LINENO },
1481         { 'D',          REQ_TOGGLE_DATE },
1482         { 'A',          REQ_TOGGLE_AUTHOR },
1483         { 'g',          REQ_TOGGLE_REV_GRAPH },
1484         { 'F',          REQ_TOGGLE_REFS },
1485         { 'I',          REQ_TOGGLE_SORT_ORDER },
1486         { 'i',          REQ_TOGGLE_SORT_FIELD },
1487         { ':',          REQ_PROMPT },
1488         { 'u',          REQ_STATUS_UPDATE },
1489         { '!',          REQ_STATUS_REVERT },
1490         { 'M',          REQ_STATUS_MERGE },
1491         { '@',          REQ_STAGE_NEXT },
1492         { ',',          REQ_PARENT },
1493         { 'e',          REQ_EDIT },
1494 };
1496 #define KEYMAP_INFO \
1497         KEYMAP_(GENERIC), \
1498         KEYMAP_(MAIN), \
1499         KEYMAP_(DIFF), \
1500         KEYMAP_(LOG), \
1501         KEYMAP_(TREE), \
1502         KEYMAP_(BLOB), \
1503         KEYMAP_(BLAME), \
1504         KEYMAP_(BRANCH), \
1505         KEYMAP_(PAGER), \
1506         KEYMAP_(HELP), \
1507         KEYMAP_(STATUS), \
1508         KEYMAP_(STAGE)
1510 enum keymap {
1511 #define KEYMAP_(name) KEYMAP_##name
1512         KEYMAP_INFO
1513 #undef  KEYMAP_
1514 };
1516 static const struct enum_map keymap_table[] = {
1517 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1518         KEYMAP_INFO
1519 #undef  KEYMAP_
1520 };
1522 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1524 struct keybinding_table {
1525         struct keybinding *data;
1526         size_t size;
1527 };
1529 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1531 static void
1532 add_keybinding(enum keymap keymap, enum request request, int key)
1534         struct keybinding_table *table = &keybindings[keymap];
1535         size_t i;
1537         for (i = 0; i < keybindings[keymap].size; i++) {
1538                 if (keybindings[keymap].data[i].alias == key) {
1539                         keybindings[keymap].data[i].request = request;
1540                         return;
1541                 }
1542         }
1544         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1545         if (!table->data)
1546                 die("Failed to allocate keybinding");
1547         table->data[table->size].alias = key;
1548         table->data[table->size++].request = request;
1550         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1551                 int i;
1553                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1554                         if (default_keybindings[i].alias == key)
1555                                 default_keybindings[i].request = REQ_NONE;
1556         }
1559 /* Looks for a key binding first in the given map, then in the generic map, and
1560  * lastly in the default keybindings. */
1561 static enum request
1562 get_keybinding(enum keymap keymap, int key)
1564         size_t i;
1566         for (i = 0; i < keybindings[keymap].size; i++)
1567                 if (keybindings[keymap].data[i].alias == key)
1568                         return keybindings[keymap].data[i].request;
1570         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1571                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1572                         return keybindings[KEYMAP_GENERIC].data[i].request;
1574         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1575                 if (default_keybindings[i].alias == key)
1576                         return default_keybindings[i].request;
1578         return (enum request) key;
1582 struct key {
1583         const char *name;
1584         int value;
1585 };
1587 static const struct key key_table[] = {
1588         { "Enter",      KEY_RETURN },
1589         { "Space",      ' ' },
1590         { "Backspace",  KEY_BACKSPACE },
1591         { "Tab",        KEY_TAB },
1592         { "Escape",     KEY_ESC },
1593         { "Left",       KEY_LEFT },
1594         { "Right",      KEY_RIGHT },
1595         { "Up",         KEY_UP },
1596         { "Down",       KEY_DOWN },
1597         { "Insert",     KEY_IC },
1598         { "Delete",     KEY_DC },
1599         { "Hash",       '#' },
1600         { "Home",       KEY_HOME },
1601         { "End",        KEY_END },
1602         { "PageUp",     KEY_PPAGE },
1603         { "PageDown",   KEY_NPAGE },
1604         { "F1",         KEY_F(1) },
1605         { "F2",         KEY_F(2) },
1606         { "F3",         KEY_F(3) },
1607         { "F4",         KEY_F(4) },
1608         { "F5",         KEY_F(5) },
1609         { "F6",         KEY_F(6) },
1610         { "F7",         KEY_F(7) },
1611         { "F8",         KEY_F(8) },
1612         { "F9",         KEY_F(9) },
1613         { "F10",        KEY_F(10) },
1614         { "F11",        KEY_F(11) },
1615         { "F12",        KEY_F(12) },
1616 };
1618 static int
1619 get_key_value(const char *name)
1621         int i;
1623         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1624                 if (!strcasecmp(key_table[i].name, name))
1625                         return key_table[i].value;
1627         if (strlen(name) == 1 && isprint(*name))
1628                 return (int) *name;
1630         return ERR;
1633 static const char *
1634 get_key_name(int key_value)
1636         static char key_char[] = "'X'";
1637         const char *seq = NULL;
1638         int key;
1640         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1641                 if (key_table[key].value == key_value)
1642                         seq = key_table[key].name;
1644         if (seq == NULL &&
1645             key_value < 127 &&
1646             isprint(key_value)) {
1647                 key_char[1] = (char) key_value;
1648                 seq = key_char;
1649         }
1651         return seq ? seq : "(no key)";
1654 static bool
1655 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1657         const char *sep = *pos > 0 ? ", " : "";
1658         const char *keyname = get_key_name(keybinding->alias);
1660         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1663 static bool
1664 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1665                            enum keymap keymap, bool all)
1667         int i;
1669         for (i = 0; i < keybindings[keymap].size; i++) {
1670                 if (keybindings[keymap].data[i].request == request) {
1671                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1672                                 return FALSE;
1673                         if (!all)
1674                                 break;
1675                 }
1676         }
1678         return TRUE;
1681 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1683 static const char *
1684 get_keys(enum keymap keymap, enum request request, bool all)
1686         static char buf[BUFSIZ];
1687         size_t pos = 0;
1688         int i;
1690         buf[pos] = 0;
1692         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1693                 return "Too many keybindings!";
1694         if (pos > 0 && !all)
1695                 return buf;
1697         if (keymap != KEYMAP_GENERIC) {
1698                 /* Only the generic keymap includes the default keybindings when
1699                  * listing all keys. */
1700                 if (all)
1701                         return buf;
1703                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1704                         return "Too many keybindings!";
1705                 if (pos)
1706                         return buf;
1707         }
1709         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1710                 if (default_keybindings[i].request == request) {
1711                         if (!append_key(buf, &pos, &default_keybindings[i]))
1712                                 return "Too many keybindings!";
1713                         if (!all)
1714                                 return buf;
1715                 }
1716         }
1718         return buf;
1721 struct run_request {
1722         enum keymap keymap;
1723         int key;
1724         const char *argv[SIZEOF_ARG];
1725 };
1727 static struct run_request *run_request;
1728 static size_t run_requests;
1730 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1732 static enum request
1733 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1735         struct run_request *req;
1737         if (argc >= ARRAY_SIZE(req->argv) - 1)
1738                 return REQ_NONE;
1740         if (!realloc_run_requests(&run_request, run_requests, 1))
1741                 return REQ_NONE;
1743         req = &run_request[run_requests];
1744         req->keymap = keymap;
1745         req->key = key;
1746         req->argv[0] = NULL;
1748         if (!format_argv(req->argv, argv, FORMAT_NONE))
1749                 return REQ_NONE;
1751         return REQ_NONE + ++run_requests;
1754 static struct run_request *
1755 get_run_request(enum request request)
1757         if (request <= REQ_NONE)
1758                 return NULL;
1759         return &run_request[request - REQ_NONE - 1];
1762 static void
1763 add_builtin_run_requests(void)
1765         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1766         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1767         const char *commit[] = { "git", "commit", NULL };
1768         const char *gc[] = { "git", "gc", NULL };
1769         struct {
1770                 enum keymap keymap;
1771                 int key;
1772                 int argc;
1773                 const char **argv;
1774         } reqs[] = {
1775                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1776                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1777                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1778                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1779         };
1780         int i;
1782         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1783                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1785                 if (req != reqs[i].key)
1786                         continue;
1787                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1788                 if (req != REQ_NONE)
1789                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1790         }
1793 /*
1794  * User config file handling.
1795  */
1797 static int   config_lineno;
1798 static bool  config_errors;
1799 static const char *config_msg;
1801 static const struct enum_map color_map[] = {
1802 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1803         COLOR_MAP(DEFAULT),
1804         COLOR_MAP(BLACK),
1805         COLOR_MAP(BLUE),
1806         COLOR_MAP(CYAN),
1807         COLOR_MAP(GREEN),
1808         COLOR_MAP(MAGENTA),
1809         COLOR_MAP(RED),
1810         COLOR_MAP(WHITE),
1811         COLOR_MAP(YELLOW),
1812 };
1814 static const struct enum_map attr_map[] = {
1815 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1816         ATTR_MAP(NORMAL),
1817         ATTR_MAP(BLINK),
1818         ATTR_MAP(BOLD),
1819         ATTR_MAP(DIM),
1820         ATTR_MAP(REVERSE),
1821         ATTR_MAP(STANDOUT),
1822         ATTR_MAP(UNDERLINE),
1823 };
1825 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1827 static int parse_step(double *opt, const char *arg)
1829         *opt = atoi(arg);
1830         if (!strchr(arg, '%'))
1831                 return OK;
1833         /* "Shift down" so 100% and 1 does not conflict. */
1834         *opt = (*opt - 1) / 100;
1835         if (*opt >= 1.0) {
1836                 *opt = 0.99;
1837                 config_msg = "Step value larger than 100%";
1838                 return ERR;
1839         }
1840         if (*opt < 0.0) {
1841                 *opt = 1;
1842                 config_msg = "Invalid step value";
1843                 return ERR;
1844         }
1845         return OK;
1848 static int
1849 parse_int(int *opt, const char *arg, int min, int max)
1851         int value = atoi(arg);
1853         if (min <= value && value <= max) {
1854                 *opt = value;
1855                 return OK;
1856         }
1858         config_msg = "Integer value out of bound";
1859         return ERR;
1862 static bool
1863 set_color(int *color, const char *name)
1865         if (map_enum(color, color_map, name))
1866                 return TRUE;
1867         if (!prefixcmp(name, "color"))
1868                 return parse_int(color, name + 5, 0, 255) == OK;
1869         return FALSE;
1872 /* Wants: object fgcolor bgcolor [attribute] */
1873 static int
1874 option_color_command(int argc, const char *argv[])
1876         struct line_info *info;
1878         if (argc < 3) {
1879                 config_msg = "Wrong number of arguments given to color command";
1880                 return ERR;
1881         }
1883         info = get_line_info(argv[0]);
1884         if (!info) {
1885                 static const struct enum_map obsolete[] = {
1886                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1887                         ENUM_MAP("main-date",   LINE_DATE),
1888                         ENUM_MAP("main-author", LINE_AUTHOR),
1889                 };
1890                 int index;
1892                 if (!map_enum(&index, obsolete, argv[0])) {
1893                         config_msg = "Unknown color name";
1894                         return ERR;
1895                 }
1896                 info = &line_info[index];
1897         }
1899         if (!set_color(&info->fg, argv[1]) ||
1900             !set_color(&info->bg, argv[2])) {
1901                 config_msg = "Unknown color";
1902                 return ERR;
1903         }
1905         info->attr = 0;
1906         while (argc-- > 3) {
1907                 int attr;
1909                 if (!set_attribute(&attr, argv[argc])) {
1910                         config_msg = "Unknown attribute";
1911                         return ERR;
1912                 }
1913                 info->attr |= attr;
1914         }
1916         return OK;
1919 static int parse_bool(bool *opt, const char *arg)
1921         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1922                 ? TRUE : FALSE;
1923         return OK;
1926 static int parse_enum_do(unsigned int *opt, const char *arg,
1927                          const struct enum_map *map, size_t map_size)
1929         bool is_true;
1931         assert(map_size > 1);
1933         if (map_enum_do(map, map_size, (int *) opt, arg))
1934                 return OK;
1936         if (parse_bool(&is_true, arg) != OK)
1937                 return ERR;
1939         *opt = is_true ? map[1].value : map[0].value;
1940         return OK;
1943 #define parse_enum(opt, arg, map) \
1944         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1946 static int
1947 parse_string(char *opt, const char *arg, size_t optsize)
1949         int arglen = strlen(arg);
1951         switch (arg[0]) {
1952         case '\"':
1953         case '\'':
1954                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1955                         config_msg = "Unmatched quotation";
1956                         return ERR;
1957                 }
1958                 arg += 1; arglen -= 2;
1959         default:
1960                 string_ncopy_do(opt, optsize, arg, arglen);
1961                 return OK;
1962         }
1965 /* Wants: name = value */
1966 static int
1967 option_set_command(int argc, const char *argv[])
1969         if (argc != 3) {
1970                 config_msg = "Wrong number of arguments given to set command";
1971                 return ERR;
1972         }
1974         if (strcmp(argv[1], "=")) {
1975                 config_msg = "No value assigned";
1976                 return ERR;
1977         }
1979         if (!strcmp(argv[0], "show-author"))
1980                 return parse_enum(&opt_author, argv[2], author_map);
1982         if (!strcmp(argv[0], "show-date"))
1983                 return parse_enum(&opt_date, argv[2], date_map);
1985         if (!strcmp(argv[0], "show-rev-graph"))
1986                 return parse_bool(&opt_rev_graph, argv[2]);
1988         if (!strcmp(argv[0], "show-refs"))
1989                 return parse_bool(&opt_show_refs, argv[2]);
1991         if (!strcmp(argv[0], "show-line-numbers"))
1992                 return parse_bool(&opt_line_number, argv[2]);
1994         if (!strcmp(argv[0], "line-graphics"))
1995                 return parse_bool(&opt_line_graphics, argv[2]);
1997         if (!strcmp(argv[0], "line-number-interval"))
1998                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2000         if (!strcmp(argv[0], "author-width"))
2001                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2003         if (!strcmp(argv[0], "horizontal-scroll"))
2004                 return parse_step(&opt_hscroll, argv[2]);
2006         if (!strcmp(argv[0], "split-view-height"))
2007                 return parse_step(&opt_scale_split_view, argv[2]);
2009         if (!strcmp(argv[0], "tab-size"))
2010                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2012         if (!strcmp(argv[0], "commit-encoding"))
2013                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2015         config_msg = "Unknown variable name";
2016         return ERR;
2019 /* Wants: mode request key */
2020 static int
2021 option_bind_command(int argc, const char *argv[])
2023         enum request request;
2024         int keymap = -1;
2025         int key;
2027         if (argc < 3) {
2028                 config_msg = "Wrong number of arguments given to bind command";
2029                 return ERR;
2030         }
2032         if (!set_keymap(&keymap, argv[0])) {
2033                 config_msg = "Unknown key map";
2034                 return ERR;
2035         }
2037         key = get_key_value(argv[1]);
2038         if (key == ERR) {
2039                 config_msg = "Unknown key";
2040                 return ERR;
2041         }
2043         request = get_request(argv[2]);
2044         if (request == REQ_UNKNOWN) {
2045                 static const struct enum_map obsolete[] = {
2046                         ENUM_MAP("cherry-pick",         REQ_NONE),
2047                         ENUM_MAP("screen-resize",       REQ_NONE),
2048                         ENUM_MAP("tree-parent",         REQ_PARENT),
2049                 };
2050                 int alias;
2052                 if (map_enum(&alias, obsolete, argv[2])) {
2053                         if (alias != REQ_NONE)
2054                                 add_keybinding(keymap, alias, key);
2055                         config_msg = "Obsolete request name";
2056                         return ERR;
2057                 }
2058         }
2059         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2060                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2061         if (request == REQ_UNKNOWN) {
2062                 config_msg = "Unknown request name";
2063                 return ERR;
2064         }
2066         add_keybinding(keymap, request, key);
2068         return OK;
2071 static int
2072 set_option(const char *opt, char *value)
2074         const char *argv[SIZEOF_ARG];
2075         int argc = 0;
2077         if (!argv_from_string(argv, &argc, value)) {
2078                 config_msg = "Too many option arguments";
2079                 return ERR;
2080         }
2082         if (!strcmp(opt, "color"))
2083                 return option_color_command(argc, argv);
2085         if (!strcmp(opt, "set"))
2086                 return option_set_command(argc, argv);
2088         if (!strcmp(opt, "bind"))
2089                 return option_bind_command(argc, argv);
2091         config_msg = "Unknown option command";
2092         return ERR;
2095 static int
2096 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2098         int status = OK;
2100         config_lineno++;
2101         config_msg = "Internal error";
2103         /* Check for comment markers, since read_properties() will
2104          * only ensure opt and value are split at first " \t". */
2105         optlen = strcspn(opt, "#");
2106         if (optlen == 0)
2107                 return OK;
2109         if (opt[optlen] != 0) {
2110                 config_msg = "No option value";
2111                 status = ERR;
2113         }  else {
2114                 /* Look for comment endings in the value. */
2115                 size_t len = strcspn(value, "#");
2117                 if (len < valuelen) {
2118                         valuelen = len;
2119                         value[valuelen] = 0;
2120                 }
2122                 status = set_option(opt, value);
2123         }
2125         if (status == ERR) {
2126                 warn("Error on line %d, near '%.*s': %s",
2127                      config_lineno, (int) optlen, opt, config_msg);
2128                 config_errors = TRUE;
2129         }
2131         /* Always keep going if errors are encountered. */
2132         return OK;
2135 static void
2136 load_option_file(const char *path)
2138         struct io io = {};
2140         /* It's OK that the file doesn't exist. */
2141         if (!io_open(&io, "%s", path))
2142                 return;
2144         config_lineno = 0;
2145         config_errors = FALSE;
2147         if (io_load(&io, " \t", read_option) == ERR ||
2148             config_errors == TRUE)
2149                 warn("Errors while loading %s.", path);
2152 static int
2153 load_options(void)
2155         const char *home = getenv("HOME");
2156         const char *tigrc_user = getenv("TIGRC_USER");
2157         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2158         char buf[SIZEOF_STR];
2160         if (!tigrc_system)
2161                 tigrc_system = SYSCONFDIR "/tigrc";
2162         load_option_file(tigrc_system);
2164         if (!tigrc_user) {
2165                 if (!home || !string_format(buf, "%s/.tigrc", home))
2166                         return ERR;
2167                 tigrc_user = buf;
2168         }
2169         load_option_file(tigrc_user);
2171         /* Add _after_ loading config files to avoid adding run requests
2172          * that conflict with keybindings. */
2173         add_builtin_run_requests();
2175         return OK;
2179 /*
2180  * The viewer
2181  */
2183 struct view;
2184 struct view_ops;
2186 /* The display array of active views and the index of the current view. */
2187 static struct view *display[2];
2188 static unsigned int current_view;
2190 #define foreach_displayed_view(view, i) \
2191         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2193 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2195 /* Current head and commit ID */
2196 static char ref_blob[SIZEOF_REF]        = "";
2197 static char ref_commit[SIZEOF_REF]      = "HEAD";
2198 static char ref_head[SIZEOF_REF]        = "HEAD";
2199 static char ref_branch[SIZEOF_REF]      = "";
2201 enum view_type {
2202         VIEW_MAIN,
2203         VIEW_DIFF,
2204         VIEW_LOG,
2205         VIEW_TREE,
2206         VIEW_BLOB,
2207         VIEW_BLAME,
2208         VIEW_BRANCH,
2209         VIEW_HELP,
2210         VIEW_PAGER,
2211         VIEW_STATUS,
2212         VIEW_STAGE,
2213 };
2215 struct view {
2216         enum view_type type;    /* View type */
2217         const char *name;       /* View name */
2218         const char *cmd_env;    /* Command line set via environment */
2219         const char *id;         /* Points to either of ref_{head,commit,blob} */
2221         struct view_ops *ops;   /* View operations */
2223         enum keymap keymap;     /* What keymap does this view have */
2224         bool git_dir;           /* Whether the view requires a git directory. */
2226         char ref[SIZEOF_REF];   /* Hovered commit reference */
2227         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2229         int height, width;      /* The width and height of the main window */
2230         WINDOW *win;            /* The main window */
2231         WINDOW *title;          /* The title window living below the main window */
2233         /* Navigation */
2234         unsigned long offset;   /* Offset of the window top */
2235         unsigned long yoffset;  /* Offset from the window side. */
2236         unsigned long lineno;   /* Current line number */
2237         unsigned long p_offset; /* Previous offset of the window top */
2238         unsigned long p_yoffset;/* Previous offset from the window side */
2239         unsigned long p_lineno; /* Previous current line number */
2240         bool p_restore;         /* Should the previous position be restored. */
2242         /* Searching */
2243         char grep[SIZEOF_STR];  /* Search string */
2244         regex_t *regex;         /* Pre-compiled regexp */
2246         /* If non-NULL, points to the view that opened this view. If this view
2247          * is closed tig will switch back to the parent view. */
2248         struct view *parent;
2249         struct view *prev;
2251         /* Buffering */
2252         size_t lines;           /* Total number of lines */
2253         struct line *line;      /* Line index */
2254         unsigned int digits;    /* Number of digits in the lines member. */
2256         /* Drawing */
2257         struct line *curline;   /* Line currently being drawn. */
2258         enum line_type curtype; /* Attribute currently used for drawing. */
2259         unsigned long col;      /* Column when drawing. */
2260         bool has_scrolled;      /* View was scrolled. */
2262         /* Loading */
2263         struct io io;
2264         struct io *pipe;
2265         time_t start_time;
2266         time_t update_secs;
2267 };
2269 struct view_ops {
2270         /* What type of content being displayed. Used in the title bar. */
2271         const char *type;
2272         /* Default command arguments. */
2273         const char **argv;
2274         /* Open and reads in all view content. */
2275         bool (*open)(struct view *view);
2276         /* Read one line; updates view->line. */
2277         bool (*read)(struct view *view, char *data);
2278         /* Draw one line; @lineno must be < view->height. */
2279         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2280         /* Depending on view handle a special requests. */
2281         enum request (*request)(struct view *view, enum request request, struct line *line);
2282         /* Search for regexp in a line. */
2283         bool (*grep)(struct view *view, struct line *line);
2284         /* Select line */
2285         void (*select)(struct view *view, struct line *line);
2286         /* Prepare view for loading */
2287         bool (*prepare)(struct view *view);
2288 };
2290 static struct view_ops blame_ops;
2291 static struct view_ops blob_ops;
2292 static struct view_ops diff_ops;
2293 static struct view_ops help_ops;
2294 static struct view_ops log_ops;
2295 static struct view_ops main_ops;
2296 static struct view_ops pager_ops;
2297 static struct view_ops stage_ops;
2298 static struct view_ops status_ops;
2299 static struct view_ops tree_ops;
2300 static struct view_ops branch_ops;
2302 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2303         { type, name, #env, ref, ops, map, git }
2305 #define VIEW_(id, name, ops, git, ref) \
2306         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2308 static struct view views[] = {
2309         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2310         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2311         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2312         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2313         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2314         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2315         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2316         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2317         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2318         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2319         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2320 };
2322 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2324 #define foreach_view(view, i) \
2325         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2327 #define view_is_displayed(view) \
2328         (view == display[0] || view == display[1])
2330 static enum request
2331 view_request(struct view *view, enum request request)
2333         if (!view || !view->lines)
2334                 return request;
2335         return view->ops->request(view, request, &view->line[view->lineno]);
2339 /*
2340  * View drawing.
2341  */
2343 static inline void
2344 set_view_attr(struct view *view, enum line_type type)
2346         if (!view->curline->selected && view->curtype != type) {
2347                 (void) wattrset(view->win, get_line_attr(type));
2348                 wchgat(view->win, -1, 0, type, NULL);
2349                 view->curtype = type;
2350         }
2353 static int
2354 draw_chars(struct view *view, enum line_type type, const char *string,
2355            int max_len, bool use_tilde)
2357         static char out_buffer[BUFSIZ * 2];
2358         int len = 0;
2359         int col = 0;
2360         int trimmed = FALSE;
2361         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2363         if (max_len <= 0)
2364                 return 0;
2366         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2368         set_view_attr(view, type);
2369         if (len > 0) {
2370                 if (opt_iconv_out != ICONV_NONE) {
2371                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2372                         size_t inlen = len + 1;
2374                         char *outbuf = out_buffer;
2375                         size_t outlen = sizeof(out_buffer);
2377                         size_t ret;
2379                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2380                         if (ret != (size_t) -1) {
2381                                 string = out_buffer;
2382                                 len = sizeof(out_buffer) - outlen;
2383                         }
2384                 }
2386                 waddnstr(view->win, string, len);
2387         }
2388         if (trimmed && use_tilde) {
2389                 set_view_attr(view, LINE_DELIMITER);
2390                 waddch(view->win, '~');
2391                 col++;
2392         }
2394         return col;
2397 static int
2398 draw_space(struct view *view, enum line_type type, int max, int spaces)
2400         static char space[] = "                    ";
2401         int col = 0;
2403         spaces = MIN(max, spaces);
2405         while (spaces > 0) {
2406                 int len = MIN(spaces, sizeof(space) - 1);
2408                 col += draw_chars(view, type, space, len, FALSE);
2409                 spaces -= len;
2410         }
2412         return col;
2415 static bool
2416 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2418         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2419         return view->width + view->yoffset <= view->col;
2422 static bool
2423 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2425         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2426         int max = view->width + view->yoffset - view->col;
2427         int i;
2429         if (max < size)
2430                 size = max;
2432         set_view_attr(view, type);
2433         /* Using waddch() instead of waddnstr() ensures that
2434          * they'll be rendered correctly for the cursor line. */
2435         for (i = skip; i < size; i++)
2436                 waddch(view->win, graphic[i]);
2438         view->col += size;
2439         if (size < max && skip <= size)
2440                 waddch(view->win, ' ');
2441         view->col++;
2443         return view->width + view->yoffset <= view->col;
2446 static bool
2447 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2449         int max = MIN(view->width + view->yoffset - view->col, len);
2450         int col;
2452         if (text)
2453                 col = draw_chars(view, type, text, max - 1, trim);
2454         else
2455                 col = draw_space(view, type, max - 1, max - 1);
2457         view->col += col;
2458         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2459         return view->width + view->yoffset <= view->col;
2462 static bool
2463 draw_date(struct view *view, struct time *time)
2465         const char *date = mkdate(time, opt_date);
2466         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2468         return draw_field(view, LINE_DATE, date, cols, FALSE);
2471 static bool
2472 draw_author(struct view *view, const char *author)
2474         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2475         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2477         if (abbreviate && author)
2478                 author = get_author_initials(author);
2480         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2483 static bool
2484 draw_mode(struct view *view, mode_t mode)
2486         const char *str;
2488         if (S_ISDIR(mode))
2489                 str = "drwxr-xr-x";
2490         else if (S_ISLNK(mode))
2491                 str = "lrwxrwxrwx";
2492         else if (S_ISGITLINK(mode))
2493                 str = "m---------";
2494         else if (S_ISREG(mode) && mode & S_IXUSR)
2495                 str = "-rwxr-xr-x";
2496         else if (S_ISREG(mode))
2497                 str = "-rw-r--r--";
2498         else
2499                 str = "----------";
2501         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2504 static bool
2505 draw_lineno(struct view *view, unsigned int lineno)
2507         char number[10];
2508         int digits3 = view->digits < 3 ? 3 : view->digits;
2509         int max = MIN(view->width + view->yoffset - view->col, digits3);
2510         char *text = NULL;
2511         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2513         lineno += view->offset + 1;
2514         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2515                 static char fmt[] = "%1ld";
2517                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2518                 if (string_format(number, fmt, lineno))
2519                         text = number;
2520         }
2521         if (text)
2522                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2523         else
2524                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2525         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2528 static bool
2529 draw_view_line(struct view *view, unsigned int lineno)
2531         struct line *line;
2532         bool selected = (view->offset + lineno == view->lineno);
2534         assert(view_is_displayed(view));
2536         if (view->offset + lineno >= view->lines)
2537                 return FALSE;
2539         line = &view->line[view->offset + lineno];
2541         wmove(view->win, lineno, 0);
2542         if (line->cleareol)
2543                 wclrtoeol(view->win);
2544         view->col = 0;
2545         view->curline = line;
2546         view->curtype = LINE_NONE;
2547         line->selected = FALSE;
2548         line->dirty = line->cleareol = 0;
2550         if (selected) {
2551                 set_view_attr(view, LINE_CURSOR);
2552                 line->selected = TRUE;
2553                 view->ops->select(view, line);
2554         }
2556         return view->ops->draw(view, line, lineno);
2559 static void
2560 redraw_view_dirty(struct view *view)
2562         bool dirty = FALSE;
2563         int lineno;
2565         for (lineno = 0; lineno < view->height; lineno++) {
2566                 if (view->offset + lineno >= view->lines)
2567                         break;
2568                 if (!view->line[view->offset + lineno].dirty)
2569                         continue;
2570                 dirty = TRUE;
2571                 if (!draw_view_line(view, lineno))
2572                         break;
2573         }
2575         if (!dirty)
2576                 return;
2577         wnoutrefresh(view->win);
2580 static void
2581 redraw_view_from(struct view *view, int lineno)
2583         assert(0 <= lineno && lineno < view->height);
2585         for (; lineno < view->height; lineno++) {
2586                 if (!draw_view_line(view, lineno))
2587                         break;
2588         }
2590         wnoutrefresh(view->win);
2593 static void
2594 redraw_view(struct view *view)
2596         werase(view->win);
2597         redraw_view_from(view, 0);
2601 static void
2602 update_view_title(struct view *view)
2604         char buf[SIZEOF_STR];
2605         char state[SIZEOF_STR];
2606         size_t bufpos = 0, statelen = 0;
2608         assert(view_is_displayed(view));
2610         if (view->type != VIEW_STATUS && view->lines) {
2611                 unsigned int view_lines = view->offset + view->height;
2612                 unsigned int lines = view->lines
2613                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2614                                    : 0;
2616                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2617                                    view->ops->type,
2618                                    view->lineno + 1,
2619                                    view->lines,
2620                                    lines);
2622         }
2624         if (view->pipe) {
2625                 time_t secs = time(NULL) - view->start_time;
2627                 /* Three git seconds are a long time ... */
2628                 if (secs > 2)
2629                         string_format_from(state, &statelen, " loading %lds", secs);
2630         }
2632         string_format_from(buf, &bufpos, "[%s]", view->name);
2633         if (*view->ref && bufpos < view->width) {
2634                 size_t refsize = strlen(view->ref);
2635                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2637                 if (minsize < view->width)
2638                         refsize = view->width - minsize + 7;
2639                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2640         }
2642         if (statelen && bufpos < view->width) {
2643                 string_format_from(buf, &bufpos, "%s", state);
2644         }
2646         if (view == display[current_view])
2647                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2648         else
2649                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2651         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2652         wclrtoeol(view->title);
2653         wnoutrefresh(view->title);
2656 static int
2657 apply_step(double step, int value)
2659         if (step >= 1)
2660                 return (int) step;
2661         value *= step + 0.01;
2662         return value ? value : 1;
2665 static void
2666 resize_display(void)
2668         int offset, i;
2669         struct view *base = display[0];
2670         struct view *view = display[1] ? display[1] : display[0];
2672         /* Setup window dimensions */
2674         getmaxyx(stdscr, base->height, base->width);
2676         /* Make room for the status window. */
2677         base->height -= 1;
2679         if (view != base) {
2680                 /* Horizontal split. */
2681                 view->width   = base->width;
2682                 view->height  = apply_step(opt_scale_split_view, base->height);
2683                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2684                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2685                 base->height -= view->height;
2687                 /* Make room for the title bar. */
2688                 view->height -= 1;
2689         }
2691         /* Make room for the title bar. */
2692         base->height -= 1;
2694         offset = 0;
2696         foreach_displayed_view (view, i) {
2697                 if (!view->win) {
2698                         view->win = newwin(view->height, 0, offset, 0);
2699                         if (!view->win)
2700                                 die("Failed to create %s view", view->name);
2702                         scrollok(view->win, FALSE);
2704                         view->title = newwin(1, 0, offset + view->height, 0);
2705                         if (!view->title)
2706                                 die("Failed to create title window");
2708                 } else {
2709                         wresize(view->win, view->height, view->width);
2710                         mvwin(view->win,   offset, 0);
2711                         mvwin(view->title, offset + view->height, 0);
2712                 }
2714                 offset += view->height + 1;
2715         }
2718 static void
2719 redraw_display(bool clear)
2721         struct view *view;
2722         int i;
2724         foreach_displayed_view (view, i) {
2725                 if (clear)
2726                         wclear(view->win);
2727                 redraw_view(view);
2728                 update_view_title(view);
2729         }
2733 /*
2734  * Option management
2735  */
2737 static void
2738 toggle_enum_option_do(unsigned int *opt, const char *help,
2739                       const struct enum_map *map, size_t size)
2741         *opt = (*opt + 1) % size;
2742         redraw_display(FALSE);
2743         report("Displaying %s %s", enum_name(map[*opt]), help);
2746 #define toggle_enum_option(opt, help, map) \
2747         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2749 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2750 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2752 static void
2753 toggle_view_option(bool *option, const char *help)
2755         *option = !*option;
2756         redraw_display(FALSE);
2757         report("%sabling %s", *option ? "En" : "Dis", help);
2760 static void
2761 open_option_menu(void)
2763         const struct menu_item menu[] = {
2764                 { '.', "line numbers", &opt_line_number },
2765                 { 'D', "date display", &opt_date },
2766                 { 'A', "author display", &opt_author },
2767                 { 'g', "revision graph display", &opt_rev_graph },
2768                 { 'F', "reference display", &opt_show_refs },
2769                 { 0 }
2770         };
2771         int selected = 0;
2773         if (prompt_menu("Toggle option", menu, &selected)) {
2774                 if (menu[selected].data == &opt_date)
2775                         toggle_date();
2776                 else if (menu[selected].data == &opt_author)
2777                         toggle_author();
2778                 else
2779                         toggle_view_option(menu[selected].data, menu[selected].text);
2780         }
2783 static void
2784 maximize_view(struct view *view)
2786         memset(display, 0, sizeof(display));
2787         current_view = 0;
2788         display[current_view] = view;
2789         resize_display();
2790         redraw_display(FALSE);
2791         report("");
2795 /*
2796  * Navigation
2797  */
2799 static bool
2800 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2802         if (lineno >= view->lines)
2803                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2805         if (offset > lineno || offset + view->height <= lineno) {
2806                 unsigned long half = view->height / 2;
2808                 if (lineno > half)
2809                         offset = lineno - half;
2810                 else
2811                         offset = 0;
2812         }
2814         if (offset != view->offset || lineno != view->lineno) {
2815                 view->offset = offset;
2816                 view->lineno = lineno;
2817                 return TRUE;
2818         }
2820         return FALSE;
2823 /* Scrolling backend */
2824 static void
2825 do_scroll_view(struct view *view, int lines)
2827         bool redraw_current_line = FALSE;
2829         /* The rendering expects the new offset. */
2830         view->offset += lines;
2832         assert(0 <= view->offset && view->offset < view->lines);
2833         assert(lines);
2835         /* Move current line into the view. */
2836         if (view->lineno < view->offset) {
2837                 view->lineno = view->offset;
2838                 redraw_current_line = TRUE;
2839         } else if (view->lineno >= view->offset + view->height) {
2840                 view->lineno = view->offset + view->height - 1;
2841                 redraw_current_line = TRUE;
2842         }
2844         assert(view->offset <= view->lineno && view->lineno < view->lines);
2846         /* Redraw the whole screen if scrolling is pointless. */
2847         if (view->height < ABS(lines)) {
2848                 redraw_view(view);
2850         } else {
2851                 int line = lines > 0 ? view->height - lines : 0;
2852                 int end = line + ABS(lines);
2854                 scrollok(view->win, TRUE);
2855                 wscrl(view->win, lines);
2856                 scrollok(view->win, FALSE);
2858                 while (line < end && draw_view_line(view, line))
2859                         line++;
2861                 if (redraw_current_line)
2862                         draw_view_line(view, view->lineno - view->offset);
2863                 wnoutrefresh(view->win);
2864         }
2866         view->has_scrolled = TRUE;
2867         report("");
2870 /* Scroll frontend */
2871 static void
2872 scroll_view(struct view *view, enum request request)
2874         int lines = 1;
2876         assert(view_is_displayed(view));
2878         switch (request) {
2879         case REQ_SCROLL_LEFT:
2880                 if (view->yoffset == 0) {
2881                         report("Cannot scroll beyond the first column");
2882                         return;
2883                 }
2884                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2885                         view->yoffset = 0;
2886                 else
2887                         view->yoffset -= apply_step(opt_hscroll, view->width);
2888                 redraw_view_from(view, 0);
2889                 report("");
2890                 return;
2891         case REQ_SCROLL_RIGHT:
2892                 view->yoffset += apply_step(opt_hscroll, view->width);
2893                 redraw_view(view);
2894                 report("");
2895                 return;
2896         case REQ_SCROLL_PAGE_DOWN:
2897                 lines = view->height;
2898         case REQ_SCROLL_LINE_DOWN:
2899                 if (view->offset + lines > view->lines)
2900                         lines = view->lines - view->offset;
2902                 if (lines == 0 || view->offset + view->height >= view->lines) {
2903                         report("Cannot scroll beyond the last line");
2904                         return;
2905                 }
2906                 break;
2908         case REQ_SCROLL_PAGE_UP:
2909                 lines = view->height;
2910         case REQ_SCROLL_LINE_UP:
2911                 if (lines > view->offset)
2912                         lines = view->offset;
2914                 if (lines == 0) {
2915                         report("Cannot scroll beyond the first line");
2916                         return;
2917                 }
2919                 lines = -lines;
2920                 break;
2922         default:
2923                 die("request %d not handled in switch", request);
2924         }
2926         do_scroll_view(view, lines);
2929 /* Cursor moving */
2930 static void
2931 move_view(struct view *view, enum request request)
2933         int scroll_steps = 0;
2934         int steps;
2936         switch (request) {
2937         case REQ_MOVE_FIRST_LINE:
2938                 steps = -view->lineno;
2939                 break;
2941         case REQ_MOVE_LAST_LINE:
2942                 steps = view->lines - view->lineno - 1;
2943                 break;
2945         case REQ_MOVE_PAGE_UP:
2946                 steps = view->height > view->lineno
2947                       ? -view->lineno : -view->height;
2948                 break;
2950         case REQ_MOVE_PAGE_DOWN:
2951                 steps = view->lineno + view->height >= view->lines
2952                       ? view->lines - view->lineno - 1 : view->height;
2953                 break;
2955         case REQ_MOVE_UP:
2956                 steps = -1;
2957                 break;
2959         case REQ_MOVE_DOWN:
2960                 steps = 1;
2961                 break;
2963         default:
2964                 die("request %d not handled in switch", request);
2965         }
2967         if (steps <= 0 && view->lineno == 0) {
2968                 report("Cannot move beyond the first line");
2969                 return;
2971         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2972                 report("Cannot move beyond the last line");
2973                 return;
2974         }
2976         /* Move the current line */
2977         view->lineno += steps;
2978         assert(0 <= view->lineno && view->lineno < view->lines);
2980         /* Check whether the view needs to be scrolled */
2981         if (view->lineno < view->offset ||
2982             view->lineno >= view->offset + view->height) {
2983                 scroll_steps = steps;
2984                 if (steps < 0 && -steps > view->offset) {
2985                         scroll_steps = -view->offset;
2987                 } else if (steps > 0) {
2988                         if (view->lineno == view->lines - 1 &&
2989                             view->lines > view->height) {
2990                                 scroll_steps = view->lines - view->offset - 1;
2991                                 if (scroll_steps >= view->height)
2992                                         scroll_steps -= view->height - 1;
2993                         }
2994                 }
2995         }
2997         if (!view_is_displayed(view)) {
2998                 view->offset += scroll_steps;
2999                 assert(0 <= view->offset && view->offset < view->lines);
3000                 view->ops->select(view, &view->line[view->lineno]);
3001                 return;
3002         }
3004         /* Repaint the old "current" line if we be scrolling */
3005         if (ABS(steps) < view->height)
3006                 draw_view_line(view, view->lineno - steps - view->offset);
3008         if (scroll_steps) {
3009                 do_scroll_view(view, scroll_steps);
3010                 return;
3011         }
3013         /* Draw the current line */
3014         draw_view_line(view, view->lineno - view->offset);
3016         wnoutrefresh(view->win);
3017         report("");
3021 /*
3022  * Searching
3023  */
3025 static void search_view(struct view *view, enum request request);
3027 static bool
3028 grep_text(struct view *view, const char *text[])
3030         regmatch_t pmatch;
3031         size_t i;
3033         for (i = 0; text[i]; i++)
3034                 if (*text[i] &&
3035                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3036                         return TRUE;
3037         return FALSE;
3040 static void
3041 select_view_line(struct view *view, unsigned long lineno)
3043         unsigned long old_lineno = view->lineno;
3044         unsigned long old_offset = view->offset;
3046         if (goto_view_line(view, view->offset, lineno)) {
3047                 if (view_is_displayed(view)) {
3048                         if (old_offset != view->offset) {
3049                                 redraw_view(view);
3050                         } else {
3051                                 draw_view_line(view, old_lineno - view->offset);
3052                                 draw_view_line(view, view->lineno - view->offset);
3053                                 wnoutrefresh(view->win);
3054                         }
3055                 } else {
3056                         view->ops->select(view, &view->line[view->lineno]);
3057                 }
3058         }
3061 static void
3062 find_next(struct view *view, enum request request)
3064         unsigned long lineno = view->lineno;
3065         int direction;
3067         if (!*view->grep) {
3068                 if (!*opt_search)
3069                         report("No previous search");
3070                 else
3071                         search_view(view, request);
3072                 return;
3073         }
3075         switch (request) {
3076         case REQ_SEARCH:
3077         case REQ_FIND_NEXT:
3078                 direction = 1;
3079                 break;
3081         case REQ_SEARCH_BACK:
3082         case REQ_FIND_PREV:
3083                 direction = -1;
3084                 break;
3086         default:
3087                 return;
3088         }
3090         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3091                 lineno += direction;
3093         /* Note, lineno is unsigned long so will wrap around in which case it
3094          * will become bigger than view->lines. */
3095         for (; lineno < view->lines; lineno += direction) {
3096                 if (view->ops->grep(view, &view->line[lineno])) {
3097                         select_view_line(view, lineno);
3098                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3099                         return;
3100                 }
3101         }
3103         report("No match found for '%s'", view->grep);
3106 static void
3107 search_view(struct view *view, enum request request)
3109         int regex_err;
3111         if (view->regex) {
3112                 regfree(view->regex);
3113                 *view->grep = 0;
3114         } else {
3115                 view->regex = calloc(1, sizeof(*view->regex));
3116                 if (!view->regex)
3117                         return;
3118         }
3120         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3121         if (regex_err != 0) {
3122                 char buf[SIZEOF_STR] = "unknown error";
3124                 regerror(regex_err, view->regex, buf, sizeof(buf));
3125                 report("Search failed: %s", buf);
3126                 return;
3127         }
3129         string_copy(view->grep, opt_search);
3131         find_next(view, request);
3134 /*
3135  * Incremental updating
3136  */
3138 static void
3139 reset_view(struct view *view)
3141         int i;
3143         for (i = 0; i < view->lines; i++)
3144                 free(view->line[i].data);
3145         free(view->line);
3147         view->p_offset = view->offset;
3148         view->p_yoffset = view->yoffset;
3149         view->p_lineno = view->lineno;
3151         view->line = NULL;
3152         view->offset = 0;
3153         view->yoffset = 0;
3154         view->lines  = 0;
3155         view->lineno = 0;
3156         view->vid[0] = 0;
3157         view->update_secs = 0;
3160 static const char *
3161 format_arg(const char *name)
3163         static struct {
3164                 const char *name;
3165                 size_t namelen;
3166                 const char *value;
3167                 const char *value_if_empty;
3168         } vars[] = {
3169 #define FORMAT_VAR(name, value, value_if_empty) \
3170         { name, STRING_SIZE(name), value, value_if_empty }
3171                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3172                 FORMAT_VAR("%(file)",           opt_file,       ""),
3173                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3174                 FORMAT_VAR("%(head)",           ref_head,       ""),
3175                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3176                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3177                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3178         };
3179         int i;
3181         for (i = 0; i < ARRAY_SIZE(vars); i++)
3182                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3183                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3185         report("Unknown replacement: `%s`", name);
3186         return NULL;
3189 static bool
3190 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3192         char buf[SIZEOF_STR];
3193         int argc;
3194         bool noreplace = flags == FORMAT_NONE;
3196         argv_free(dst_argv);
3198         for (argc = 0; src_argv[argc]; argc++) {
3199                 const char *arg = src_argv[argc];
3200                 size_t bufpos = 0;
3202                 while (arg) {
3203                         char *next = strstr(arg, "%(");
3204                         int len = next - arg;
3205                         const char *value;
3207                         if (!next || noreplace) {
3208                                 len = strlen(arg);
3209                                 value = "";
3211                         } else {
3212                                 value = format_arg(next);
3214                                 if (!value) {
3215                                         return FALSE;
3216                                 }
3217                         }
3219                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3220                                 return FALSE;
3222                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3223                 }
3225                 dst_argv[argc] = strdup(buf);
3226                 if (!dst_argv[argc])
3227                         break;
3228         }
3230         dst_argv[argc] = NULL;
3232         return src_argv[argc] == NULL;
3235 static bool
3236 restore_view_position(struct view *view)
3238         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3239                 return FALSE;
3241         /* Changing the view position cancels the restoring. */
3242         /* FIXME: Changing back to the first line is not detected. */
3243         if (view->offset != 0 || view->lineno != 0) {
3244                 view->p_restore = FALSE;
3245                 return FALSE;
3246         }
3248         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3249             view_is_displayed(view))
3250                 werase(view->win);
3252         view->yoffset = view->p_yoffset;
3253         view->p_restore = FALSE;
3255         return TRUE;
3258 static void
3259 end_update(struct view *view, bool force)
3261         if (!view->pipe)
3262                 return;
3263         while (!view->ops->read(view, NULL))
3264                 if (!force)
3265                         return;
3266         if (force)
3267                 io_kill(view->pipe);
3268         io_done(view->pipe);
3269         view->pipe = NULL;
3272 static void
3273 setup_update(struct view *view, const char *vid)
3275         reset_view(view);
3276         string_copy_rev(view->vid, vid);
3277         view->pipe = &view->io;
3278         view->start_time = time(NULL);
3281 static bool
3282 prepare_update(struct view *view, const char *argv[], const char *dir)
3284         if (view->pipe)
3285                 end_update(view, TRUE);
3286         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3289 static bool
3290 prepare_update_file(struct view *view, const char *name)
3292         if (view->pipe)
3293                 end_update(view, TRUE);
3294         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3297 static bool
3298 begin_update(struct view *view, bool refresh)
3300         if (view->pipe)
3301                 end_update(view, TRUE);
3303         if (!refresh) {
3304                 if (view->ops->prepare) {
3305                         if (!view->ops->prepare(view))
3306                                 return FALSE;
3307                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3308                         return FALSE;
3309                 }
3311                 /* Put the current ref_* value to the view title ref
3312                  * member. This is needed by the blob view. Most other
3313                  * views sets it automatically after loading because the
3314                  * first line is a commit line. */
3315                 string_copy_rev(view->ref, view->id);
3316         }
3318         if (!io_start(&view->io))
3319                 return FALSE;
3321         setup_update(view, view->id);
3323         return TRUE;
3326 static bool
3327 update_view(struct view *view)
3329         char out_buffer[BUFSIZ * 2];
3330         char *line;
3331         /* Clear the view and redraw everything since the tree sorting
3332          * might have rearranged things. */
3333         bool redraw = view->lines == 0;
3334         bool can_read = TRUE;
3336         if (!view->pipe)
3337                 return TRUE;
3339         if (!io_can_read(view->pipe)) {
3340                 if (view->lines == 0 && view_is_displayed(view)) {
3341                         time_t secs = time(NULL) - view->start_time;
3343                         if (secs > 1 && secs > view->update_secs) {
3344                                 if (view->update_secs == 0)
3345                                         redraw_view(view);
3346                                 update_view_title(view);
3347                                 view->update_secs = secs;
3348                         }
3349                 }
3350                 return TRUE;
3351         }
3353         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3354                 if (opt_iconv_in != ICONV_NONE) {
3355                         ICONV_CONST char *inbuf = line;
3356                         size_t inlen = strlen(line) + 1;
3358                         char *outbuf = out_buffer;
3359                         size_t outlen = sizeof(out_buffer);
3361                         size_t ret;
3363                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3364                         if (ret != (size_t) -1)
3365                                 line = out_buffer;
3366                 }
3368                 if (!view->ops->read(view, line)) {
3369                         report("Allocation failure");
3370                         end_update(view, TRUE);
3371                         return FALSE;
3372                 }
3373         }
3375         {
3376                 unsigned long lines = view->lines;
3377                 int digits;
3379                 for (digits = 0; lines; digits++)
3380                         lines /= 10;
3382                 /* Keep the displayed view in sync with line number scaling. */
3383                 if (digits != view->digits) {
3384                         view->digits = digits;
3385                         if (opt_line_number || view->type == VIEW_BLAME)
3386                                 redraw = TRUE;
3387                 }
3388         }
3390         if (io_error(view->pipe)) {
3391                 report("Failed to read: %s", io_strerror(view->pipe));
3392                 end_update(view, TRUE);
3394         } else if (io_eof(view->pipe)) {
3395                 if (view_is_displayed(view))
3396                         report("");
3397                 end_update(view, FALSE);
3398         }
3400         if (restore_view_position(view))
3401                 redraw = TRUE;
3403         if (!view_is_displayed(view))
3404                 return TRUE;
3406         if (redraw)
3407                 redraw_view_from(view, 0);
3408         else
3409                 redraw_view_dirty(view);
3411         /* Update the title _after_ the redraw so that if the redraw picks up a
3412          * commit reference in view->ref it'll be available here. */
3413         update_view_title(view);
3414         return TRUE;
3417 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3419 static struct line *
3420 add_line_data(struct view *view, void *data, enum line_type type)
3422         struct line *line;
3424         if (!realloc_lines(&view->line, view->lines, 1))
3425                 return NULL;
3427         line = &view->line[view->lines++];
3428         memset(line, 0, sizeof(*line));
3429         line->type = type;
3430         line->data = data;
3431         line->dirty = 1;
3433         return line;
3436 static struct line *
3437 add_line_text(struct view *view, const char *text, enum line_type type)
3439         char *data = text ? strdup(text) : NULL;
3441         return data ? add_line_data(view, data, type) : NULL;
3444 static struct line *
3445 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3447         char buf[SIZEOF_STR];
3448         va_list args;
3450         va_start(args, fmt);
3451         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3452                 buf[0] = 0;
3453         va_end(args);
3455         return buf[0] ? add_line_text(view, buf, type) : NULL;
3458 /*
3459  * View opening
3460  */
3462 enum open_flags {
3463         OPEN_DEFAULT = 0,       /* Use default view switching. */
3464         OPEN_SPLIT = 1,         /* Split current view. */
3465         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3466         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3467         OPEN_PREPARED = 32,     /* Open already prepared command. */
3468 };
3470 static void
3471 open_view(struct view *prev, enum request request, enum open_flags flags)
3473         bool split = !!(flags & OPEN_SPLIT);
3474         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3475         bool nomaximize = !!(flags & OPEN_REFRESH);
3476         struct view *view = VIEW(request);
3477         int nviews = displayed_views();
3478         struct view *base_view = display[0];
3480         if (view == prev && nviews == 1 && !reload) {
3481                 report("Already in %s view", view->name);
3482                 return;
3483         }
3485         if (view->git_dir && !opt_git_dir[0]) {
3486                 report("The %s view is disabled in pager view", view->name);
3487                 return;
3488         }
3490         if (split) {
3491                 display[1] = view;
3492                 current_view = 1;
3493                 view->parent = prev;
3494         } else if (!nomaximize) {
3495                 /* Maximize the current view. */
3496                 memset(display, 0, sizeof(display));
3497                 current_view = 0;
3498                 display[current_view] = view;
3499         }
3501         /* No prev signals that this is the first loaded view. */
3502         if (prev && view != prev) {
3503                 view->prev = prev;
3504         }
3506         /* Resize the view when switching between split- and full-screen,
3507          * or when switching between two different full-screen views. */
3508         if (nviews != displayed_views() ||
3509             (nviews == 1 && base_view != display[0]))
3510                 resize_display();
3512         if (view->ops->open) {
3513                 if (view->pipe)
3514                         end_update(view, TRUE);
3515                 if (!view->ops->open(view)) {
3516                         report("Failed to load %s view", view->name);
3517                         return;
3518                 }
3519                 restore_view_position(view);
3521         } else if ((reload || strcmp(view->vid, view->id)) &&
3522                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3523                 report("Failed to load %s view", view->name);
3524                 return;
3525         }
3527         if (split && prev->lineno - prev->offset >= prev->height) {
3528                 /* Take the title line into account. */
3529                 int lines = prev->lineno - prev->offset - prev->height + 1;
3531                 /* Scroll the view that was split if the current line is
3532                  * outside the new limited view. */
3533                 do_scroll_view(prev, lines);
3534         }
3536         if (prev && view != prev && split && view_is_displayed(prev)) {
3537                 /* "Blur" the previous view. */
3538                 update_view_title(prev);
3539         }
3541         if (view->pipe && view->lines == 0) {
3542                 /* Clear the old view and let the incremental updating refill
3543                  * the screen. */
3544                 werase(view->win);
3545                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3546                 report("");
3547         } else if (view_is_displayed(view)) {
3548                 redraw_view(view);
3549                 report("");
3550         }
3553 static void
3554 open_external_viewer(const char *argv[], const char *dir)
3556         def_prog_mode();           /* save current tty modes */
3557         endwin();                  /* restore original tty modes */
3558         io_run_fg(argv, dir);
3559         fprintf(stderr, "Press Enter to continue");
3560         getc(opt_tty);
3561         reset_prog_mode();
3562         redraw_display(TRUE);
3565 static void
3566 open_mergetool(const char *file)
3568         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3570         open_external_viewer(mergetool_argv, opt_cdup);
3573 static void
3574 open_editor(const char *file)
3576         const char *editor_argv[] = { "vi", file, NULL };
3577         const char *editor;
3579         editor = getenv("GIT_EDITOR");
3580         if (!editor && *opt_editor)
3581                 editor = opt_editor;
3582         if (!editor)
3583                 editor = getenv("VISUAL");
3584         if (!editor)
3585                 editor = getenv("EDITOR");
3586         if (!editor)
3587                 editor = "vi";
3589         editor_argv[0] = editor;
3590         open_external_viewer(editor_argv, opt_cdup);
3593 static void
3594 open_run_request(enum request request)
3596         struct run_request *req = get_run_request(request);
3597         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3599         if (!req) {
3600                 report("Unknown run request");
3601                 return;
3602         }
3604         if (format_argv(argv, req->argv, FORMAT_ALL))
3605                 open_external_viewer(argv, NULL);
3606         argv_free(argv);
3609 /*
3610  * User request switch noodle
3611  */
3613 static int
3614 view_driver(struct view *view, enum request request)
3616         int i;
3618         if (request == REQ_NONE)
3619                 return TRUE;
3621         if (request > REQ_NONE) {
3622                 open_run_request(request);
3623                 view_request(view, REQ_REFRESH);
3624                 return TRUE;
3625         }
3627         request = view_request(view, request);
3628         if (request == REQ_NONE)
3629                 return TRUE;
3631         switch (request) {
3632         case REQ_MOVE_UP:
3633         case REQ_MOVE_DOWN:
3634         case REQ_MOVE_PAGE_UP:
3635         case REQ_MOVE_PAGE_DOWN:
3636         case REQ_MOVE_FIRST_LINE:
3637         case REQ_MOVE_LAST_LINE:
3638                 move_view(view, request);
3639                 break;
3641         case REQ_SCROLL_LEFT:
3642         case REQ_SCROLL_RIGHT:
3643         case REQ_SCROLL_LINE_DOWN:
3644         case REQ_SCROLL_LINE_UP:
3645         case REQ_SCROLL_PAGE_DOWN:
3646         case REQ_SCROLL_PAGE_UP:
3647                 scroll_view(view, request);
3648                 break;
3650         case REQ_VIEW_BLAME:
3651                 if (!opt_file[0]) {
3652                         report("No file chosen, press %s to open tree view",
3653                                get_key(view->keymap, REQ_VIEW_TREE));
3654                         break;
3655                 }
3656                 open_view(view, request, OPEN_DEFAULT);
3657                 break;
3659         case REQ_VIEW_BLOB:
3660                 if (!ref_blob[0]) {
3661                         report("No file chosen, press %s to open tree view",
3662                                get_key(view->keymap, REQ_VIEW_TREE));
3663                         break;
3664                 }
3665                 open_view(view, request, OPEN_DEFAULT);
3666                 break;
3668         case REQ_VIEW_PAGER:
3669                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3670                         report("No pager content, press %s to run command from prompt",
3671                                get_key(view->keymap, REQ_PROMPT));
3672                         break;
3673                 }
3674                 open_view(view, request, OPEN_DEFAULT);
3675                 break;
3677         case REQ_VIEW_STAGE:
3678                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3679                         report("No stage content, press %s to open the status view and choose file",
3680                                get_key(view->keymap, REQ_VIEW_STATUS));
3681                         break;
3682                 }
3683                 open_view(view, request, OPEN_DEFAULT);
3684                 break;
3686         case REQ_VIEW_STATUS:
3687                 if (opt_is_inside_work_tree == FALSE) {
3688                         report("The status view requires a working tree");
3689                         break;
3690                 }
3691                 open_view(view, request, OPEN_DEFAULT);
3692                 break;
3694         case REQ_VIEW_MAIN:
3695         case REQ_VIEW_DIFF:
3696         case REQ_VIEW_LOG:
3697         case REQ_VIEW_TREE:
3698         case REQ_VIEW_HELP:
3699         case REQ_VIEW_BRANCH:
3700                 open_view(view, request, OPEN_DEFAULT);
3701                 break;
3703         case REQ_NEXT:
3704         case REQ_PREVIOUS:
3705                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3707                 if (view->parent) {
3708                         int line;
3710                         view = view->parent;
3711                         line = view->lineno;
3712                         move_view(view, request);
3713                         if (view_is_displayed(view))
3714                                 update_view_title(view);
3715                         if (line != view->lineno)
3716                                 view_request(view, REQ_ENTER);
3717                 } else {
3718                         move_view(view, request);
3719                 }
3720                 break;
3722         case REQ_VIEW_NEXT:
3723         {
3724                 int nviews = displayed_views();
3725                 int next_view = (current_view + 1) % nviews;
3727                 if (next_view == current_view) {
3728                         report("Only one view is displayed");
3729                         break;
3730                 }
3732                 current_view = next_view;
3733                 /* Blur out the title of the previous view. */
3734                 update_view_title(view);
3735                 report("");
3736                 break;
3737         }
3738         case REQ_REFRESH:
3739                 report("Refreshing is not yet supported for the %s view", view->name);
3740                 break;
3742         case REQ_MAXIMIZE:
3743                 if (displayed_views() == 2)
3744                         maximize_view(view);
3745                 break;
3747         case REQ_OPTIONS:
3748                 open_option_menu();
3749                 break;
3751         case REQ_TOGGLE_LINENO:
3752                 toggle_view_option(&opt_line_number, "line numbers");
3753                 break;
3755         case REQ_TOGGLE_DATE:
3756                 toggle_date();
3757                 break;
3759         case REQ_TOGGLE_AUTHOR:
3760                 toggle_author();
3761                 break;
3763         case REQ_TOGGLE_REV_GRAPH:
3764                 toggle_view_option(&opt_rev_graph, "revision graph display");
3765                 break;
3767         case REQ_TOGGLE_REFS:
3768                 toggle_view_option(&opt_show_refs, "reference display");
3769                 break;
3771         case REQ_TOGGLE_SORT_FIELD:
3772         case REQ_TOGGLE_SORT_ORDER:
3773                 report("Sorting is not yet supported for the %s view", view->name);
3774                 break;
3776         case REQ_SEARCH:
3777         case REQ_SEARCH_BACK:
3778                 search_view(view, request);
3779                 break;
3781         case REQ_FIND_NEXT:
3782         case REQ_FIND_PREV:
3783                 find_next(view, request);
3784                 break;
3786         case REQ_STOP_LOADING:
3787                 foreach_view(view, i) {
3788                         if (view->pipe)
3789                                 report("Stopped loading the %s view", view->name),
3790                         end_update(view, TRUE);
3791                 }
3792                 break;
3794         case REQ_SHOW_VERSION:
3795                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3796                 return TRUE;
3798         case REQ_SCREEN_REDRAW:
3799                 redraw_display(TRUE);
3800                 break;
3802         case REQ_EDIT:
3803                 report("Nothing to edit");
3804                 break;
3806         case REQ_ENTER:
3807                 report("Nothing to enter");
3808                 break;
3810         case REQ_VIEW_CLOSE:
3811                 /* XXX: Mark closed views by letting view->prev point to the
3812                  * view itself. Parents to closed view should never be
3813                  * followed. */
3814                 if (view->prev && view->prev != view) {
3815                         maximize_view(view->prev);
3816                         view->prev = view;
3817                         break;
3818                 }
3819                 /* Fall-through */
3820         case REQ_QUIT:
3821                 return FALSE;
3823         default:
3824                 report("Unknown key, press %s for help",
3825                        get_key(view->keymap, REQ_VIEW_HELP));
3826                 return TRUE;
3827         }
3829         return TRUE;
3833 /*
3834  * View backend utilities
3835  */
3837 enum sort_field {
3838         ORDERBY_NAME,
3839         ORDERBY_DATE,
3840         ORDERBY_AUTHOR,
3841 };
3843 struct sort_state {
3844         const enum sort_field *fields;
3845         size_t size, current;
3846         bool reverse;
3847 };
3849 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3850 #define get_sort_field(state) ((state).fields[(state).current])
3851 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3853 static void
3854 sort_view(struct view *view, enum request request, struct sort_state *state,
3855           int (*compare)(const void *, const void *))
3857         switch (request) {
3858         case REQ_TOGGLE_SORT_FIELD:
3859                 state->current = (state->current + 1) % state->size;
3860                 break;
3862         case REQ_TOGGLE_SORT_ORDER:
3863                 state->reverse = !state->reverse;
3864                 break;
3865         default:
3866                 die("Not a sort request");
3867         }
3869         qsort(view->line, view->lines, sizeof(*view->line), compare);
3870         redraw_view(view);
3873 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3875 /* Small author cache to reduce memory consumption. It uses binary
3876  * search to lookup or find place to position new entries. No entries
3877  * are ever freed. */
3878 static const char *
3879 get_author(const char *name)
3881         static const char **authors;
3882         static size_t authors_size;
3883         int from = 0, to = authors_size - 1;
3885         while (from <= to) {
3886                 size_t pos = (to + from) / 2;
3887                 int cmp = strcmp(name, authors[pos]);
3889                 if (!cmp)
3890                         return authors[pos];
3892                 if (cmp < 0)
3893                         to = pos - 1;
3894                 else
3895                         from = pos + 1;
3896         }
3898         if (!realloc_authors(&authors, authors_size, 1))
3899                 return NULL;
3900         name = strdup(name);
3901         if (!name)
3902                 return NULL;
3904         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3905         authors[from] = name;
3906         authors_size++;
3908         return name;
3911 static void
3912 parse_timesec(struct time *time, const char *sec)
3914         time->sec = (time_t) atol(sec);
3917 static void
3918 parse_timezone(struct time *time, const char *zone)
3920         long tz;
3922         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3923         tz += ('0' - zone[2]) * 60 * 60;
3924         tz += ('0' - zone[3]) * 60 * 10;
3925         tz += ('0' - zone[4]) * 60;
3927         if (zone[0] == '-')
3928                 tz = -tz;
3930         time->tz = tz;
3931         time->sec -= tz;
3934 /* Parse author lines where the name may be empty:
3935  *      author  <email@address.tld> 1138474660 +0100
3936  */
3937 static void
3938 parse_author_line(char *ident, const char **author, struct time *time)
3940         char *nameend = strchr(ident, '<');
3941         char *emailend = strchr(ident, '>');
3943         if (nameend && emailend)
3944                 *nameend = *emailend = 0;
3945         ident = chomp_string(ident);
3946         if (!*ident) {
3947                 if (nameend)
3948                         ident = chomp_string(nameend + 1);
3949                 if (!*ident)
3950                         ident = "Unknown";
3951         }
3953         *author = get_author(ident);
3955         /* Parse epoch and timezone */
3956         if (emailend && emailend[1] == ' ') {
3957                 char *secs = emailend + 2;
3958                 char *zone = strchr(secs, ' ');
3960                 parse_timesec(time, secs);
3962                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3963                         parse_timezone(time, zone + 1);
3964         }
3967 static bool
3968 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3970         char rev[SIZEOF_REV];
3971         const char *revlist_argv[] = {
3972                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3973         };
3974         struct menu_item *items;
3975         char text[SIZEOF_STR];
3976         bool ok = TRUE;
3977         int i;
3979         items = calloc(*parents + 1, sizeof(*items));
3980         if (!items)
3981                 return FALSE;
3983         for (i = 0; i < *parents; i++) {
3984                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3985                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3986                     !(items[i].text = strdup(text))) {
3987                         ok = FALSE;
3988                         break;
3989                 }
3990         }
3992         if (ok) {
3993                 *parents = 0;
3994                 ok = prompt_menu("Select parent", items, parents);
3995         }
3996         for (i = 0; items[i].text; i++)
3997                 free((char *) items[i].text);
3998         free(items);
3999         return ok;
4002 static bool
4003 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4005         char buf[SIZEOF_STR * 4];
4006         const char *revlist_argv[] = {
4007                 "git", "log", "--no-color", "-1",
4008                         "--pretty=format:%P", id, "--", path, NULL
4009         };
4010         int parents;
4012         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4013             (parents = strlen(buf) / 40) < 0) {
4014                 report("Failed to get parent information");
4015                 return FALSE;
4017         } else if (parents == 0) {
4018                 if (path)
4019                         report("Path '%s' does not exist in the parent", path);
4020                 else
4021                         report("The selected commit has no parents");
4022                 return FALSE;
4023         }
4025         if (parents == 1)
4026                 parents = 0;
4027         else if (!open_commit_parent_menu(buf, &parents))
4028                 return FALSE;
4030         string_copy_rev(rev, &buf[41 * parents]);
4031         return TRUE;
4034 /*
4035  * Pager backend
4036  */
4038 static bool
4039 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4041         char text[SIZEOF_STR];
4043         if (opt_line_number && draw_lineno(view, lineno))
4044                 return TRUE;
4046         string_expand(text, sizeof(text), line->data, opt_tab_size);
4047         draw_text(view, line->type, text, TRUE);
4048         return TRUE;
4051 static bool
4052 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4054         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4055         char ref[SIZEOF_STR];
4057         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4058                 return TRUE;
4060         /* This is the only fatal call, since it can "corrupt" the buffer. */
4061         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4062                 return FALSE;
4064         return TRUE;
4067 static void
4068 add_pager_refs(struct view *view, struct line *line)
4070         char buf[SIZEOF_STR];
4071         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4072         struct ref_list *list;
4073         size_t bufpos = 0, i;
4074         const char *sep = "Refs: ";
4075         bool is_tag = FALSE;
4077         assert(line->type == LINE_COMMIT);
4079         list = get_ref_list(commit_id);
4080         if (!list) {
4081                 if (view->type == VIEW_DIFF)
4082                         goto try_add_describe_ref;
4083                 return;
4084         }
4086         for (i = 0; i < list->size; i++) {
4087                 struct ref *ref = list->refs[i];
4088                 const char *fmt = ref->tag    ? "%s[%s]" :
4089                                   ref->remote ? "%s<%s>" : "%s%s";
4091                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4092                         return;
4093                 sep = ", ";
4094                 if (ref->tag)
4095                         is_tag = TRUE;
4096         }
4098         if (!is_tag && view->type == VIEW_DIFF) {
4099 try_add_describe_ref:
4100                 /* Add <tag>-g<commit_id> "fake" reference. */
4101                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4102                         return;
4103         }
4105         if (bufpos == 0)
4106                 return;
4108         add_line_text(view, buf, LINE_PP_REFS);
4111 static bool
4112 pager_read(struct view *view, char *data)
4114         struct line *line;
4116         if (!data)
4117                 return TRUE;
4119         line = add_line_text(view, data, get_line_type(data));
4120         if (!line)
4121                 return FALSE;
4123         if (line->type == LINE_COMMIT &&
4124             (view->type == VIEW_DIFF ||
4125              view->type == VIEW_LOG))
4126                 add_pager_refs(view, line);
4128         return TRUE;
4131 static enum request
4132 pager_request(struct view *view, enum request request, struct line *line)
4134         int split = 0;
4136         if (request != REQ_ENTER)
4137                 return request;
4139         if (line->type == LINE_COMMIT &&
4140            (view->type == VIEW_LOG ||
4141             view->type == VIEW_PAGER)) {
4142                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4143                 split = 1;
4144         }
4146         /* Always scroll the view even if it was split. That way
4147          * you can use Enter to scroll through the log view and
4148          * split open each commit diff. */
4149         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4151         /* FIXME: A minor workaround. Scrolling the view will call report("")
4152          * but if we are scrolling a non-current view this won't properly
4153          * update the view title. */
4154         if (split)
4155                 update_view_title(view);
4157         return REQ_NONE;
4160 static bool
4161 pager_grep(struct view *view, struct line *line)
4163         const char *text[] = { line->data, NULL };
4165         return grep_text(view, text);
4168 static void
4169 pager_select(struct view *view, struct line *line)
4171         if (line->type == LINE_COMMIT) {
4172                 char *text = (char *)line->data + STRING_SIZE("commit ");
4174                 if (view->type != VIEW_PAGER)
4175                         string_copy_rev(view->ref, text);
4176                 string_copy_rev(ref_commit, text);
4177         }
4180 static struct view_ops pager_ops = {
4181         "line",
4182         NULL,
4183         NULL,
4184         pager_read,
4185         pager_draw,
4186         pager_request,
4187         pager_grep,
4188         pager_select,
4189 };
4191 static const char *log_argv[SIZEOF_ARG] = {
4192         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4193 };
4195 static enum request
4196 log_request(struct view *view, enum request request, struct line *line)
4198         switch (request) {
4199         case REQ_REFRESH:
4200                 load_refs();
4201                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4202                 return REQ_NONE;
4203         default:
4204                 return pager_request(view, request, line);
4205         }
4208 static struct view_ops log_ops = {
4209         "line",
4210         log_argv,
4211         NULL,
4212         pager_read,
4213         pager_draw,
4214         log_request,
4215         pager_grep,
4216         pager_select,
4217 };
4219 static const char *diff_argv[SIZEOF_ARG] = {
4220         "git", "show", "--pretty=fuller", "--no-color", "--root",
4221                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4222 };
4224 static struct view_ops diff_ops = {
4225         "line",
4226         diff_argv,
4227         NULL,
4228         pager_read,
4229         pager_draw,
4230         pager_request,
4231         pager_grep,
4232         pager_select,
4233 };
4235 /*
4236  * Help backend
4237  */
4239 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4241 static bool
4242 help_open_keymap_title(struct view *view, enum keymap keymap)
4244         struct line *line;
4246         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4247                                help_keymap_hidden[keymap] ? '+' : '-',
4248                                enum_name(keymap_table[keymap]));
4249         if (line)
4250                 line->other = keymap;
4252         return help_keymap_hidden[keymap];
4255 static void
4256 help_open_keymap(struct view *view, enum keymap keymap)
4258         const char *group = NULL;
4259         char buf[SIZEOF_STR];
4260         size_t bufpos;
4261         bool add_title = TRUE;
4262         int i;
4264         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4265                 const char *key = NULL;
4267                 if (req_info[i].request == REQ_NONE)
4268                         continue;
4270                 if (!req_info[i].request) {
4271                         group = req_info[i].help;
4272                         continue;
4273                 }
4275                 key = get_keys(keymap, req_info[i].request, TRUE);
4276                 if (!key || !*key)
4277                         continue;
4279                 if (add_title && help_open_keymap_title(view, keymap))
4280                         return;
4281                 add_title = FALSE;
4283                 if (group) {
4284                         add_line_text(view, group, LINE_HELP_GROUP);
4285                         group = NULL;
4286                 }
4288                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4289                                 enum_name(req_info[i]), req_info[i].help);
4290         }
4292         group = "External commands:";
4294         for (i = 0; i < run_requests; i++) {
4295                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4296                 const char *key;
4297                 int argc;
4299                 if (!req || req->keymap != keymap)
4300                         continue;
4302                 key = get_key_name(req->key);
4303                 if (!*key)
4304                         key = "(no key defined)";
4306                 if (add_title && help_open_keymap_title(view, keymap))
4307                         return;
4308                 if (group) {
4309                         add_line_text(view, group, LINE_HELP_GROUP);
4310                         group = NULL;
4311                 }
4313                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4314                         if (!string_format_from(buf, &bufpos, "%s%s",
4315                                                 argc ? " " : "", req->argv[argc]))
4316                                 return;
4318                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4319         }
4322 static bool
4323 help_open(struct view *view)
4325         enum keymap keymap;
4327         reset_view(view);
4328         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4329         add_line_text(view, "", LINE_DEFAULT);
4331         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4332                 help_open_keymap(view, keymap);
4334         return TRUE;
4337 static enum request
4338 help_request(struct view *view, enum request request, struct line *line)
4340         switch (request) {
4341         case REQ_ENTER:
4342                 if (line->type == LINE_HELP_KEYMAP) {
4343                         help_keymap_hidden[line->other] =
4344                                 !help_keymap_hidden[line->other];
4345                         view->p_restore = TRUE;
4346                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4347                 }
4349                 return REQ_NONE;
4350         default:
4351                 return pager_request(view, request, line);
4352         }
4355 static struct view_ops help_ops = {
4356         "line",
4357         NULL,
4358         help_open,
4359         NULL,
4360         pager_draw,
4361         help_request,
4362         pager_grep,
4363         pager_select,
4364 };
4367 /*
4368  * Tree backend
4369  */
4371 struct tree_stack_entry {
4372         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4373         unsigned long lineno;           /* Line number to restore */
4374         char *name;                     /* Position of name in opt_path */
4375 };
4377 /* The top of the path stack. */
4378 static struct tree_stack_entry *tree_stack = NULL;
4379 unsigned long tree_lineno = 0;
4381 static void
4382 pop_tree_stack_entry(void)
4384         struct tree_stack_entry *entry = tree_stack;
4386         tree_lineno = entry->lineno;
4387         entry->name[0] = 0;
4388         tree_stack = entry->prev;
4389         free(entry);
4392 static void
4393 push_tree_stack_entry(const char *name, unsigned long lineno)
4395         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4396         size_t pathlen = strlen(opt_path);
4398         if (!entry)
4399                 return;
4401         entry->prev = tree_stack;
4402         entry->name = opt_path + pathlen;
4403         tree_stack = entry;
4405         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4406                 pop_tree_stack_entry();
4407                 return;
4408         }
4410         /* Move the current line to the first tree entry. */
4411         tree_lineno = 1;
4412         entry->lineno = lineno;
4415 /* Parse output from git-ls-tree(1):
4416  *
4417  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4418  */
4420 #define SIZEOF_TREE_ATTR \
4421         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4423 #define SIZEOF_TREE_MODE \
4424         STRING_SIZE("100644 ")
4426 #define TREE_ID_OFFSET \
4427         STRING_SIZE("100644 blob ")
4429 struct tree_entry {
4430         char id[SIZEOF_REV];
4431         mode_t mode;
4432         struct time time;               /* Date from the author ident. */
4433         const char *author;             /* Author of the commit. */
4434         char name[1];
4435 };
4437 static const char *
4438 tree_path(const struct line *line)
4440         return ((struct tree_entry *) line->data)->name;
4443 static int
4444 tree_compare_entry(const struct line *line1, const struct line *line2)
4446         if (line1->type != line2->type)
4447                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4448         return strcmp(tree_path(line1), tree_path(line2));
4451 static const enum sort_field tree_sort_fields[] = {
4452         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4453 };
4454 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4456 static int
4457 tree_compare(const void *l1, const void *l2)
4459         const struct line *line1 = (const struct line *) l1;
4460         const struct line *line2 = (const struct line *) l2;
4461         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4462         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4464         if (line1->type == LINE_TREE_HEAD)
4465                 return -1;
4466         if (line2->type == LINE_TREE_HEAD)
4467                 return 1;
4469         switch (get_sort_field(tree_sort_state)) {
4470         case ORDERBY_DATE:
4471                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4473         case ORDERBY_AUTHOR:
4474                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4476         case ORDERBY_NAME:
4477         default:
4478                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4479         }
4483 static struct line *
4484 tree_entry(struct view *view, enum line_type type, const char *path,
4485            const char *mode, const char *id)
4487         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4488         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4490         if (!entry || !line) {
4491                 free(entry);
4492                 return NULL;
4493         }
4495         strncpy(entry->name, path, strlen(path));
4496         if (mode)
4497                 entry->mode = strtoul(mode, NULL, 8);
4498         if (id)
4499                 string_copy_rev(entry->id, id);
4501         return line;
4504 static bool
4505 tree_read_date(struct view *view, char *text, bool *read_date)
4507         static const char *author_name;
4508         static struct time author_time;
4510         if (!text && *read_date) {
4511                 *read_date = FALSE;
4512                 return TRUE;
4514         } else if (!text) {
4515                 char *path = *opt_path ? opt_path : ".";
4516                 /* Find next entry to process */
4517                 const char *log_file[] = {
4518                         "git", "log", "--no-color", "--pretty=raw",
4519                                 "--cc", "--raw", view->id, "--", path, NULL
4520                 };
4521                 struct io io = {};
4523                 if (!view->lines) {
4524                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4525                         report("Tree is empty");
4526                         return TRUE;
4527                 }
4529                 if (!io_run_rd(&io, log_file, opt_cdup)) {
4530                         report("Failed to load tree data");
4531                         return TRUE;
4532                 }
4534                 io_done(view->pipe);
4535                 view->io = io;
4536                 *read_date = TRUE;
4537                 return FALSE;
4539         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4540                 parse_author_line(text + STRING_SIZE("author "),
4541                                   &author_name, &author_time);
4543         } else if (*text == ':') {
4544                 char *pos;
4545                 size_t annotated = 1;
4546                 size_t i;
4548                 pos = strchr(text, '\t');
4549                 if (!pos)
4550                         return TRUE;
4551                 text = pos + 1;
4552                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4553                         text += strlen(opt_path);
4554                 pos = strchr(text, '/');
4555                 if (pos)
4556                         *pos = 0;
4558                 for (i = 1; i < view->lines; i++) {
4559                         struct line *line = &view->line[i];
4560                         struct tree_entry *entry = line->data;
4562                         annotated += !!entry->author;
4563                         if (entry->author || strcmp(entry->name, text))
4564                                 continue;
4566                         entry->author = author_name;
4567                         entry->time = author_time;
4568                         line->dirty = 1;
4569                         break;
4570                 }
4572                 if (annotated == view->lines)
4573                         io_kill(view->pipe);
4574         }
4575         return TRUE;
4578 static bool
4579 tree_read(struct view *view, char *text)
4581         static bool read_date = FALSE;
4582         struct tree_entry *data;
4583         struct line *entry, *line;
4584         enum line_type type;
4585         size_t textlen = text ? strlen(text) : 0;
4586         char *path = text + SIZEOF_TREE_ATTR;
4588         if (read_date || !text)
4589                 return tree_read_date(view, text, &read_date);
4591         if (textlen <= SIZEOF_TREE_ATTR)
4592                 return FALSE;
4593         if (view->lines == 0 &&
4594             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4595                 return FALSE;
4597         /* Strip the path part ... */
4598         if (*opt_path) {
4599                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4600                 size_t striplen = strlen(opt_path);
4602                 if (pathlen > striplen)
4603                         memmove(path, path + striplen,
4604                                 pathlen - striplen + 1);
4606                 /* Insert "link" to parent directory. */
4607                 if (view->lines == 1 &&
4608                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4609                         return FALSE;
4610         }
4612         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4613         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4614         if (!entry)
4615                 return FALSE;
4616         data = entry->data;
4618         /* Skip "Directory ..." and ".." line. */
4619         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4620                 if (tree_compare_entry(line, entry) <= 0)
4621                         continue;
4623                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4625                 line->data = data;
4626                 line->type = type;
4627                 for (; line <= entry; line++)
4628                         line->dirty = line->cleareol = 1;
4629                 return TRUE;
4630         }
4632         if (tree_lineno > view->lineno) {
4633                 view->lineno = tree_lineno;
4634                 tree_lineno = 0;
4635         }
4637         return TRUE;
4640 static bool
4641 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4643         struct tree_entry *entry = line->data;
4645         if (line->type == LINE_TREE_HEAD) {
4646                 if (draw_text(view, line->type, "Directory path /", TRUE))
4647                         return TRUE;
4648         } else {
4649                 if (draw_mode(view, entry->mode))
4650                         return TRUE;
4652                 if (opt_author && draw_author(view, entry->author))
4653                         return TRUE;
4655                 if (opt_date && draw_date(view, &entry->time))
4656                         return TRUE;
4657         }
4658         if (draw_text(view, line->type, entry->name, TRUE))
4659                 return TRUE;
4660         return TRUE;
4663 static void
4664 open_blob_editor(const char *id)
4666         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4667         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4668         int fd = mkstemp(file);
4670         if (fd == -1)
4671                 report("Failed to create temporary file");
4672         else if (!io_run_append(blob_argv, fd))
4673                 report("Failed to save blob data to file");
4674         else
4675                 open_editor(file);
4676         if (fd != -1)
4677                 unlink(file);
4680 static enum request
4681 tree_request(struct view *view, enum request request, struct line *line)
4683         enum open_flags flags;
4684         struct tree_entry *entry = line->data;
4686         switch (request) {
4687         case REQ_VIEW_BLAME:
4688                 if (line->type != LINE_TREE_FILE) {
4689                         report("Blame only supported for files");
4690                         return REQ_NONE;
4691                 }
4693                 string_copy(opt_ref, view->vid);
4694                 return request;
4696         case REQ_EDIT:
4697                 if (line->type != LINE_TREE_FILE) {
4698                         report("Edit only supported for files");
4699                 } else if (!is_head_commit(view->vid)) {
4700                         open_blob_editor(entry->id);
4701                 } else {
4702                         open_editor(opt_file);
4703                 }
4704                 return REQ_NONE;
4706         case REQ_TOGGLE_SORT_FIELD:
4707         case REQ_TOGGLE_SORT_ORDER:
4708                 sort_view(view, request, &tree_sort_state, tree_compare);
4709                 return REQ_NONE;
4711         case REQ_PARENT:
4712                 if (!*opt_path) {
4713                         /* quit view if at top of tree */
4714                         return REQ_VIEW_CLOSE;
4715                 }
4716                 /* fake 'cd  ..' */
4717                 line = &view->line[1];
4718                 break;
4720         case REQ_ENTER:
4721                 break;
4723         default:
4724                 return request;
4725         }
4727         /* Cleanup the stack if the tree view is at a different tree. */
4728         while (!*opt_path && tree_stack)
4729                 pop_tree_stack_entry();
4731         switch (line->type) {
4732         case LINE_TREE_DIR:
4733                 /* Depending on whether it is a subdirectory or parent link
4734                  * mangle the path buffer. */
4735                 if (line == &view->line[1] && *opt_path) {
4736                         pop_tree_stack_entry();
4738                 } else {
4739                         const char *basename = tree_path(line);
4741                         push_tree_stack_entry(basename, view->lineno);
4742                 }
4744                 /* Trees and subtrees share the same ID, so they are not not
4745                  * unique like blobs. */
4746                 flags = OPEN_RELOAD;
4747                 request = REQ_VIEW_TREE;
4748                 break;
4750         case LINE_TREE_FILE:
4751                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4752                 request = REQ_VIEW_BLOB;
4753                 break;
4755         default:
4756                 return REQ_NONE;
4757         }
4759         open_view(view, request, flags);
4760         if (request == REQ_VIEW_TREE)
4761                 view->lineno = tree_lineno;
4763         return REQ_NONE;
4766 static bool
4767 tree_grep(struct view *view, struct line *line)
4769         struct tree_entry *entry = line->data;
4770         const char *text[] = {
4771                 entry->name,
4772                 opt_author ? entry->author : "",
4773                 mkdate(&entry->time, opt_date),
4774                 NULL
4775         };
4777         return grep_text(view, text);
4780 static void
4781 tree_select(struct view *view, struct line *line)
4783         struct tree_entry *entry = line->data;
4785         if (line->type == LINE_TREE_FILE) {
4786                 string_copy_rev(ref_blob, entry->id);
4787                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4789         } else if (line->type != LINE_TREE_DIR) {
4790                 return;
4791         }
4793         string_copy_rev(view->ref, entry->id);
4796 static bool
4797 tree_prepare(struct view *view)
4799         if (view->lines == 0 && opt_prefix[0]) {
4800                 char *pos = opt_prefix;
4802                 while (pos && *pos) {
4803                         char *end = strchr(pos, '/');
4805                         if (end)
4806                                 *end = 0;
4807                         push_tree_stack_entry(pos, 0);
4808                         pos = end;
4809                         if (end) {
4810                                 *end = '/';
4811                                 pos++;
4812                         }
4813                 }
4815         } else if (strcmp(view->vid, view->id)) {
4816                 opt_path[0] = 0;
4817         }
4819         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4822 static const char *tree_argv[SIZEOF_ARG] = {
4823         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4824 };
4826 static struct view_ops tree_ops = {
4827         "file",
4828         tree_argv,
4829         NULL,
4830         tree_read,
4831         tree_draw,
4832         tree_request,
4833         tree_grep,
4834         tree_select,
4835         tree_prepare,
4836 };
4838 static bool
4839 blob_read(struct view *view, char *line)
4841         if (!line)
4842                 return TRUE;
4843         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4846 static enum request
4847 blob_request(struct view *view, enum request request, struct line *line)
4849         switch (request) {
4850         case REQ_EDIT:
4851                 open_blob_editor(view->vid);
4852                 return REQ_NONE;
4853         default:
4854                 return pager_request(view, request, line);
4855         }
4858 static const char *blob_argv[SIZEOF_ARG] = {
4859         "git", "cat-file", "blob", "%(blob)", NULL
4860 };
4862 static struct view_ops blob_ops = {
4863         "line",
4864         blob_argv,
4865         NULL,
4866         blob_read,
4867         pager_draw,
4868         blob_request,
4869         pager_grep,
4870         pager_select,
4871 };
4873 /*
4874  * Blame backend
4875  *
4876  * Loading the blame view is a two phase job:
4877  *
4878  *  1. File content is read either using opt_file from the
4879  *     filesystem or using git-cat-file.
4880  *  2. Then blame information is incrementally added by
4881  *     reading output from git-blame.
4882  */
4884 struct blame_commit {
4885         char id[SIZEOF_REV];            /* SHA1 ID. */
4886         char title[128];                /* First line of the commit message. */
4887         const char *author;             /* Author of the commit. */
4888         struct time time;               /* Date from the author ident. */
4889         char filename[128];             /* Name of file. */
4890         bool has_previous;              /* Was a "previous" line detected. */
4891 };
4893 struct blame {
4894         struct blame_commit *commit;
4895         unsigned long lineno;
4896         char text[1];
4897 };
4899 static bool
4900 blame_open(struct view *view)
4902         char path[SIZEOF_STR];
4904         if (!view->prev && *opt_prefix) {
4905                 string_copy(path, opt_file);
4906                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4907                         return FALSE;
4908         }
4910         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4911                 const char *blame_cat_file_argv[] = {
4912                         "git", "cat-file", "blob", path, NULL
4913                 };
4915                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4916                     !io_run_rd(&view->io, blame_cat_file_argv, opt_cdup))
4917                         return FALSE;
4918         }
4920         setup_update(view, opt_file);
4921         string_format(view->ref, "%s ...", opt_file);
4923         return TRUE;
4926 static struct blame_commit *
4927 get_blame_commit(struct view *view, const char *id)
4929         size_t i;
4931         for (i = 0; i < view->lines; i++) {
4932                 struct blame *blame = view->line[i].data;
4934                 if (!blame->commit)
4935                         continue;
4937                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4938                         return blame->commit;
4939         }
4941         {
4942                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4944                 if (commit)
4945                         string_ncopy(commit->id, id, SIZEOF_REV);
4946                 return commit;
4947         }
4950 static bool
4951 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4953         const char *pos = *posref;
4955         *posref = NULL;
4956         pos = strchr(pos + 1, ' ');
4957         if (!pos || !isdigit(pos[1]))
4958                 return FALSE;
4959         *number = atoi(pos + 1);
4960         if (*number < min || *number > max)
4961                 return FALSE;
4963         *posref = pos;
4964         return TRUE;
4967 static struct blame_commit *
4968 parse_blame_commit(struct view *view, const char *text, int *blamed)
4970         struct blame_commit *commit;
4971         struct blame *blame;
4972         const char *pos = text + SIZEOF_REV - 2;
4973         size_t orig_lineno = 0;
4974         size_t lineno;
4975         size_t group;
4977         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4978                 return NULL;
4980         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4981             !parse_number(&pos, &lineno, 1, view->lines) ||
4982             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4983                 return NULL;
4985         commit = get_blame_commit(view, text);
4986         if (!commit)
4987                 return NULL;
4989         *blamed += group;
4990         while (group--) {
4991                 struct line *line = &view->line[lineno + group - 1];
4993                 blame = line->data;
4994                 blame->commit = commit;
4995                 blame->lineno = orig_lineno + group - 1;
4996                 line->dirty = 1;
4997         }
4999         return commit;
5002 static bool
5003 blame_read_file(struct view *view, const char *line, bool *read_file)
5005         if (!line) {
5006                 const char *blame_argv[] = {
5007                         "git", "blame", "--incremental",
5008                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5009                 };
5010                 struct io io = {};
5012                 if (view->lines == 0 && !view->prev)
5013                         die("No blame exist for %s", view->vid);
5015                 if (view->lines == 0 || !io_run_rd(&io, blame_argv, opt_cdup)) {
5016                         report("Failed to load blame data");
5017                         return TRUE;
5018                 }
5020                 io_done(view->pipe);
5021                 view->io = io;
5022                 *read_file = FALSE;
5023                 return FALSE;
5025         } else {
5026                 size_t linelen = strlen(line);
5027                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5029                 if (!blame)
5030                         return FALSE;
5032                 blame->commit = NULL;
5033                 strncpy(blame->text, line, linelen);
5034                 blame->text[linelen] = 0;
5035                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5036         }
5039 static bool
5040 match_blame_header(const char *name, char **line)
5042         size_t namelen = strlen(name);
5043         bool matched = !strncmp(name, *line, namelen);
5045         if (matched)
5046                 *line += namelen;
5048         return matched;
5051 static bool
5052 blame_read(struct view *view, char *line)
5054         static struct blame_commit *commit = NULL;
5055         static int blamed = 0;
5056         static bool read_file = TRUE;
5058         if (read_file)
5059                 return blame_read_file(view, line, &read_file);
5061         if (!line) {
5062                 /* Reset all! */
5063                 commit = NULL;
5064                 blamed = 0;
5065                 read_file = TRUE;
5066                 string_format(view->ref, "%s", view->vid);
5067                 if (view_is_displayed(view)) {
5068                         update_view_title(view);
5069                         redraw_view_from(view, 0);
5070                 }
5071                 return TRUE;
5072         }
5074         if (!commit) {
5075                 commit = parse_blame_commit(view, line, &blamed);
5076                 string_format(view->ref, "%s %2d%%", view->vid,
5077                               view->lines ? blamed * 100 / view->lines : 0);
5079         } else if (match_blame_header("author ", &line)) {
5080                 commit->author = get_author(line);
5082         } else if (match_blame_header("author-time ", &line)) {
5083                 parse_timesec(&commit->time, line);
5085         } else if (match_blame_header("author-tz ", &line)) {
5086                 parse_timezone(&commit->time, line);
5088         } else if (match_blame_header("summary ", &line)) {
5089                 string_ncopy(commit->title, line, strlen(line));
5091         } else if (match_blame_header("previous ", &line)) {
5092                 commit->has_previous = TRUE;
5094         } else if (match_blame_header("filename ", &line)) {
5095                 string_ncopy(commit->filename, line, strlen(line));
5096                 commit = NULL;
5097         }
5099         return TRUE;
5102 static bool
5103 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5105         struct blame *blame = line->data;
5106         struct time *time = NULL;
5107         const char *id = NULL, *author = NULL;
5108         char text[SIZEOF_STR];
5110         if (blame->commit && *blame->commit->filename) {
5111                 id = blame->commit->id;
5112                 author = blame->commit->author;
5113                 time = &blame->commit->time;
5114         }
5116         if (opt_date && draw_date(view, time))
5117                 return TRUE;
5119         if (opt_author && draw_author(view, author))
5120                 return TRUE;
5122         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5123                 return TRUE;
5125         if (draw_lineno(view, lineno))
5126                 return TRUE;
5128         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5129         draw_text(view, LINE_DEFAULT, text, TRUE);
5130         return TRUE;
5133 static bool
5134 check_blame_commit(struct blame *blame, bool check_null_id)
5136         if (!blame->commit)
5137                 report("Commit data not loaded yet");
5138         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5139                 report("No commit exist for the selected line");
5140         else
5141                 return TRUE;
5142         return FALSE;
5145 static void
5146 setup_blame_parent_line(struct view *view, struct blame *blame)
5148         const char *diff_tree_argv[] = {
5149                 "git", "diff-tree", "-U0", blame->commit->id,
5150                         "--", blame->commit->filename, NULL
5151         };
5152         struct io io = {};
5153         int parent_lineno = -1;
5154         int blamed_lineno = -1;
5155         char *line;
5157         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5158                 return;
5160         while ((line = io_get(&io, '\n', TRUE))) {
5161                 if (*line == '@') {
5162                         char *pos = strchr(line, '+');
5164                         parent_lineno = atoi(line + 4);
5165                         if (pos)
5166                                 blamed_lineno = atoi(pos + 1);
5168                 } else if (*line == '+' && parent_lineno != -1) {
5169                         if (blame->lineno == blamed_lineno - 1 &&
5170                             !strcmp(blame->text, line + 1)) {
5171                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5172                                 break;
5173                         }
5174                         blamed_lineno++;
5175                 }
5176         }
5178         io_done(&io);
5181 static enum request
5182 blame_request(struct view *view, enum request request, struct line *line)
5184         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5185         struct blame *blame = line->data;
5187         switch (request) {
5188         case REQ_VIEW_BLAME:
5189                 if (check_blame_commit(blame, TRUE)) {
5190                         string_copy(opt_ref, blame->commit->id);
5191                         string_copy(opt_file, blame->commit->filename);
5192                         if (blame->lineno)
5193                                 view->lineno = blame->lineno;
5194                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5195                 }
5196                 break;
5198         case REQ_PARENT:
5199                 if (check_blame_commit(blame, TRUE) &&
5200                     select_commit_parent(blame->commit->id, opt_ref,
5201                                          blame->commit->filename)) {
5202                         string_copy(opt_file, blame->commit->filename);
5203                         setup_blame_parent_line(view, blame);
5204                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5205                 }
5206                 break;
5208         case REQ_ENTER:
5209                 if (!check_blame_commit(blame, FALSE))
5210                         break;
5212                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5213                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5214                         break;
5216                 if (!strcmp(blame->commit->id, NULL_ID)) {
5217                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5218                         const char *diff_index_argv[] = {
5219                                 "git", "diff-index", "--root", "--patch-with-stat",
5220                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5221                         };
5223                         if (!blame->commit->has_previous) {
5224                                 diff_index_argv[1] = "diff";
5225                                 diff_index_argv[2] = "--no-color";
5226                                 diff_index_argv[6] = "--";
5227                                 diff_index_argv[7] = "/dev/null";
5228                         }
5230                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5231                                 report("Failed to allocate diff command");
5232                                 break;
5233                         }
5234                         flags |= OPEN_PREPARED;
5235                 }
5237                 open_view(view, REQ_VIEW_DIFF, flags);
5238                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5239                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5240                 break;
5242         default:
5243                 return request;
5244         }
5246         return REQ_NONE;
5249 static bool
5250 blame_grep(struct view *view, struct line *line)
5252         struct blame *blame = line->data;
5253         struct blame_commit *commit = blame->commit;
5254         const char *text[] = {
5255                 blame->text,
5256                 commit ? commit->title : "",
5257                 commit ? commit->id : "",
5258                 commit && opt_author ? commit->author : "",
5259                 commit ? mkdate(&commit->time, opt_date) : "",
5260                 NULL
5261         };
5263         return grep_text(view, text);
5266 static void
5267 blame_select(struct view *view, struct line *line)
5269         struct blame *blame = line->data;
5270         struct blame_commit *commit = blame->commit;
5272         if (!commit)
5273                 return;
5275         if (!strcmp(commit->id, NULL_ID))
5276                 string_ncopy(ref_commit, "HEAD", 4);
5277         else
5278                 string_copy_rev(ref_commit, commit->id);
5281 static struct view_ops blame_ops = {
5282         "line",
5283         NULL,
5284         blame_open,
5285         blame_read,
5286         blame_draw,
5287         blame_request,
5288         blame_grep,
5289         blame_select,
5290 };
5292 /*
5293  * Branch backend
5294  */
5296 struct branch {
5297         const char *author;             /* Author of the last commit. */
5298         struct time time;               /* Date of the last activity. */
5299         const struct ref *ref;          /* Name and commit ID information. */
5300 };
5302 static const struct ref branch_all;
5304 static const enum sort_field branch_sort_fields[] = {
5305         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5306 };
5307 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5309 static int
5310 branch_compare(const void *l1, const void *l2)
5312         const struct branch *branch1 = ((const struct line *) l1)->data;
5313         const struct branch *branch2 = ((const struct line *) l2)->data;
5315         switch (get_sort_field(branch_sort_state)) {
5316         case ORDERBY_DATE:
5317                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5319         case ORDERBY_AUTHOR:
5320                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5322         case ORDERBY_NAME:
5323         default:
5324                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5325         }
5328 static bool
5329 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5331         struct branch *branch = line->data;
5332         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5334         if (opt_date && draw_date(view, &branch->time))
5335                 return TRUE;
5337         if (opt_author && draw_author(view, branch->author))
5338                 return TRUE;
5340         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5341         return TRUE;
5344 static enum request
5345 branch_request(struct view *view, enum request request, struct line *line)
5347         struct branch *branch = line->data;
5349         switch (request) {
5350         case REQ_REFRESH:
5351                 load_refs();
5352                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5353                 return REQ_NONE;
5355         case REQ_TOGGLE_SORT_FIELD:
5356         case REQ_TOGGLE_SORT_ORDER:
5357                 sort_view(view, request, &branch_sort_state, branch_compare);
5358                 return REQ_NONE;
5360         case REQ_ENTER:
5361                 if (branch->ref == &branch_all) {
5362                         const char *all_branches_argv[] = {
5363                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5364                                       "--topo-order", "--all", NULL
5365                         };
5366                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5368                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5369                                 report("Failed to load view of all branches");
5370                                 return REQ_NONE;
5371                         }
5372                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5373                 } else {
5374                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5375                 }
5376                 return REQ_NONE;
5378         default:
5379                 return request;
5380         }
5383 static bool
5384 branch_read(struct view *view, char *line)
5386         static char id[SIZEOF_REV];
5387         struct branch *reference;
5388         size_t i;
5390         if (!line)
5391                 return TRUE;
5393         switch (get_line_type(line)) {
5394         case LINE_COMMIT:
5395                 string_copy_rev(id, line + STRING_SIZE("commit "));
5396                 return TRUE;
5398         case LINE_AUTHOR:
5399                 for (i = 0, reference = NULL; i < view->lines; i++) {
5400                         struct branch *branch = view->line[i].data;
5402                         if (strcmp(branch->ref->id, id))
5403                                 continue;
5405                         view->line[i].dirty = TRUE;
5406                         if (reference) {
5407                                 branch->author = reference->author;
5408                                 branch->time = reference->time;
5409                                 continue;
5410                         }
5412                         parse_author_line(line + STRING_SIZE("author "),
5413                                           &branch->author, &branch->time);
5414                         reference = branch;
5415                 }
5416                 return TRUE;
5418         default:
5419                 return TRUE;
5420         }
5424 static bool
5425 branch_open_visitor(void *data, const struct ref *ref)
5427         struct view *view = data;
5428         struct branch *branch;
5430         if (ref->tag || ref->ltag || ref->remote)
5431                 return TRUE;
5433         branch = calloc(1, sizeof(*branch));
5434         if (!branch)
5435                 return FALSE;
5437         branch->ref = ref;
5438         return !!add_line_data(view, branch, LINE_DEFAULT);
5441 static bool
5442 branch_open(struct view *view)
5444         const char *branch_log[] = {
5445                 "git", "log", "--no-color", "--pretty=raw",
5446                         "--simplify-by-decoration", "--all", NULL
5447         };
5449         if (!io_run_rd(&view->io, branch_log, NULL)) {
5450                 report("Failed to load branch data");
5451                 return TRUE;
5452         }
5454         setup_update(view, view->id);
5455         branch_open_visitor(view, &branch_all);
5456         foreach_ref(branch_open_visitor, view);
5457         view->p_restore = TRUE;
5459         return TRUE;
5462 static bool
5463 branch_grep(struct view *view, struct line *line)
5465         struct branch *branch = line->data;
5466         const char *text[] = {
5467                 branch->ref->name,
5468                 branch->author,
5469                 NULL
5470         };
5472         return grep_text(view, text);
5475 static void
5476 branch_select(struct view *view, struct line *line)
5478         struct branch *branch = line->data;
5480         string_copy_rev(view->ref, branch->ref->id);
5481         string_copy_rev(ref_commit, branch->ref->id);
5482         string_copy_rev(ref_head, branch->ref->id);
5483         string_copy_rev(ref_branch, branch->ref->name);
5486 static struct view_ops branch_ops = {
5487         "branch",
5488         NULL,
5489         branch_open,
5490         branch_read,
5491         branch_draw,
5492         branch_request,
5493         branch_grep,
5494         branch_select,
5495 };
5497 /*
5498  * Status backend
5499  */
5501 struct status {
5502         char status;
5503         struct {
5504                 mode_t mode;
5505                 char rev[SIZEOF_REV];
5506                 char name[SIZEOF_STR];
5507         } old;
5508         struct {
5509                 mode_t mode;
5510                 char rev[SIZEOF_REV];
5511                 char name[SIZEOF_STR];
5512         } new;
5513 };
5515 static char status_onbranch[SIZEOF_STR];
5516 static struct status stage_status;
5517 static enum line_type stage_line_type;
5518 static size_t stage_chunks;
5519 static int *stage_chunk;
5521 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5523 /* This should work even for the "On branch" line. */
5524 static inline bool
5525 status_has_none(struct view *view, struct line *line)
5527         return line < view->line + view->lines && !line[1].data;
5530 /* Get fields from the diff line:
5531  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5532  */
5533 static inline bool
5534 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5536         const char *old_mode = buf +  1;
5537         const char *new_mode = buf +  8;
5538         const char *old_rev  = buf + 15;
5539         const char *new_rev  = buf + 56;
5540         const char *status   = buf + 97;
5542         if (bufsize < 98 ||
5543             old_mode[-1] != ':' ||
5544             new_mode[-1] != ' ' ||
5545             old_rev[-1]  != ' ' ||
5546             new_rev[-1]  != ' ' ||
5547             status[-1]   != ' ')
5548                 return FALSE;
5550         file->status = *status;
5552         string_copy_rev(file->old.rev, old_rev);
5553         string_copy_rev(file->new.rev, new_rev);
5555         file->old.mode = strtoul(old_mode, NULL, 8);
5556         file->new.mode = strtoul(new_mode, NULL, 8);
5558         file->old.name[0] = file->new.name[0] = 0;
5560         return TRUE;
5563 static bool
5564 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5566         struct status *unmerged = NULL;
5567         char *buf;
5568         struct io io = {};
5570         if (!io_run(&io, argv, opt_cdup, IO_RD))
5571                 return FALSE;
5573         add_line_data(view, NULL, type);
5575         while ((buf = io_get(&io, 0, TRUE))) {
5576                 struct status *file = unmerged;
5578                 if (!file) {
5579                         file = calloc(1, sizeof(*file));
5580                         if (!file || !add_line_data(view, file, type))
5581                                 goto error_out;
5582                 }
5584                 /* Parse diff info part. */
5585                 if (status) {
5586                         file->status = status;
5587                         if (status == 'A')
5588                                 string_copy(file->old.rev, NULL_ID);
5590                 } else if (!file->status || file == unmerged) {
5591                         if (!status_get_diff(file, buf, strlen(buf)))
5592                                 goto error_out;
5594                         buf = io_get(&io, 0, TRUE);
5595                         if (!buf)
5596                                 break;
5598                         /* Collapse all modified entries that follow an
5599                          * associated unmerged entry. */
5600                         if (unmerged == file) {
5601                                 unmerged->status = 'U';
5602                                 unmerged = NULL;
5603                         } else if (file->status == 'U') {
5604                                 unmerged = file;
5605                         }
5606                 }
5608                 /* Grab the old name for rename/copy. */
5609                 if (!*file->old.name &&
5610                     (file->status == 'R' || file->status == 'C')) {
5611                         string_ncopy(file->old.name, buf, strlen(buf));
5613                         buf = io_get(&io, 0, TRUE);
5614                         if (!buf)
5615                                 break;
5616                 }
5618                 /* git-ls-files just delivers a NUL separated list of
5619                  * file names similar to the second half of the
5620                  * git-diff-* output. */
5621                 string_ncopy(file->new.name, buf, strlen(buf));
5622                 if (!*file->old.name)
5623                         string_copy(file->old.name, file->new.name);
5624                 file = NULL;
5625         }
5627         if (io_error(&io)) {
5628 error_out:
5629                 io_done(&io);
5630                 return FALSE;
5631         }
5633         if (!view->line[view->lines - 1].data)
5634                 add_line_data(view, NULL, LINE_STAT_NONE);
5636         io_done(&io);
5637         return TRUE;
5640 /* Don't show unmerged entries in the staged section. */
5641 static const char *status_diff_index_argv[] = {
5642         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5643                              "--cached", "-M", "HEAD", NULL
5644 };
5646 static const char *status_diff_files_argv[] = {
5647         "git", "diff-files", "-z", NULL
5648 };
5650 static const char *status_list_other_argv[] = {
5651         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5652 };
5654 static const char *status_list_no_head_argv[] = {
5655         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5656 };
5658 static const char *update_index_argv[] = {
5659         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5660 };
5662 /* Restore the previous line number to stay in the context or select a
5663  * line with something that can be updated. */
5664 static void
5665 status_restore(struct view *view)
5667         if (view->p_lineno >= view->lines)
5668                 view->p_lineno = view->lines - 1;
5669         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5670                 view->p_lineno++;
5671         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5672                 view->p_lineno--;
5674         /* If the above fails, always skip the "On branch" line. */
5675         if (view->p_lineno < view->lines)
5676                 view->lineno = view->p_lineno;
5677         else
5678                 view->lineno = 1;
5680         if (view->lineno < view->offset)
5681                 view->offset = view->lineno;
5682         else if (view->offset + view->height <= view->lineno)
5683                 view->offset = view->lineno - view->height + 1;
5685         view->p_restore = FALSE;
5688 static void
5689 status_update_onbranch(void)
5691         static const char *paths[][2] = {
5692                 { "rebase-apply/rebasing",      "Rebasing" },
5693                 { "rebase-apply/applying",      "Applying mailbox" },
5694                 { "rebase-apply/",              "Rebasing mailbox" },
5695                 { "rebase-merge/interactive",   "Interactive rebase" },
5696                 { "rebase-merge/",              "Rebase merge" },
5697                 { "MERGE_HEAD",                 "Merging" },
5698                 { "BISECT_LOG",                 "Bisecting" },
5699                 { "HEAD",                       "On branch" },
5700         };
5701         char buf[SIZEOF_STR];
5702         struct stat stat;
5703         int i;
5705         if (is_initial_commit()) {
5706                 string_copy(status_onbranch, "Initial commit");
5707                 return;
5708         }
5710         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5711                 char *head = opt_head;
5713                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5714                     lstat(buf, &stat) < 0)
5715                         continue;
5717                 if (!*opt_head) {
5718                         struct io io = {};
5720                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5721                             io_read_buf(&io, buf, sizeof(buf))) {
5722                                 head = buf;
5723                                 if (!prefixcmp(head, "refs/heads/"))
5724                                         head += STRING_SIZE("refs/heads/");
5725                         }
5726                 }
5728                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5729                         string_copy(status_onbranch, opt_head);
5730                 return;
5731         }
5733         string_copy(status_onbranch, "Not currently on any branch");
5736 /* First parse staged info using git-diff-index(1), then parse unstaged
5737  * info using git-diff-files(1), and finally untracked files using
5738  * git-ls-files(1). */
5739 static bool
5740 status_open(struct view *view)
5742         reset_view(view);
5744         add_line_data(view, NULL, LINE_STAT_HEAD);
5745         status_update_onbranch();
5747         io_run_bg(update_index_argv);
5749         if (is_initial_commit()) {
5750                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5751                         return FALSE;
5752         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5753                 return FALSE;
5754         }
5756         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5757             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5758                 return FALSE;
5760         /* Restore the exact position or use the specialized restore
5761          * mode? */
5762         if (!view->p_restore)
5763                 status_restore(view);
5764         return TRUE;
5767 static bool
5768 status_draw(struct view *view, struct line *line, unsigned int lineno)
5770         struct status *status = line->data;
5771         enum line_type type;
5772         const char *text;
5774         if (!status) {
5775                 switch (line->type) {
5776                 case LINE_STAT_STAGED:
5777                         type = LINE_STAT_SECTION;
5778                         text = "Changes to be committed:";
5779                         break;
5781                 case LINE_STAT_UNSTAGED:
5782                         type = LINE_STAT_SECTION;
5783                         text = "Changed but not updated:";
5784                         break;
5786                 case LINE_STAT_UNTRACKED:
5787                         type = LINE_STAT_SECTION;
5788                         text = "Untracked files:";
5789                         break;
5791                 case LINE_STAT_NONE:
5792                         type = LINE_DEFAULT;
5793                         text = "  (no files)";
5794                         break;
5796                 case LINE_STAT_HEAD:
5797                         type = LINE_STAT_HEAD;
5798                         text = status_onbranch;
5799                         break;
5801                 default:
5802                         return FALSE;
5803                 }
5804         } else {
5805                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5807                 buf[0] = status->status;
5808                 if (draw_text(view, line->type, buf, TRUE))
5809                         return TRUE;
5810                 type = LINE_DEFAULT;
5811                 text = status->new.name;
5812         }
5814         draw_text(view, type, text, TRUE);
5815         return TRUE;
5818 static enum request
5819 status_load_error(struct view *view, struct view *stage, const char *path)
5821         if (displayed_views() == 2 || display[current_view] != view)
5822                 maximize_view(view);
5823         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5824         return REQ_NONE;
5827 static enum request
5828 status_enter(struct view *view, struct line *line)
5830         struct status *status = line->data;
5831         const char *oldpath = status ? status->old.name : NULL;
5832         /* Diffs for unmerged entries are empty when passing the new
5833          * path, so leave it empty. */
5834         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5835         const char *info;
5836         enum open_flags split;
5837         struct view *stage = VIEW(REQ_VIEW_STAGE);
5839         if (line->type == LINE_STAT_NONE ||
5840             (!status && line[1].type == LINE_STAT_NONE)) {
5841                 report("No file to diff");
5842                 return REQ_NONE;
5843         }
5845         switch (line->type) {
5846         case LINE_STAT_STAGED:
5847                 if (is_initial_commit()) {
5848                         const char *no_head_diff_argv[] = {
5849                                 "git", "diff", "--no-color", "--patch-with-stat",
5850                                         "--", "/dev/null", newpath, NULL
5851                         };
5853                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5854                                 return status_load_error(view, stage, newpath);
5855                 } else {
5856                         const char *index_show_argv[] = {
5857                                 "git", "diff-index", "--root", "--patch-with-stat",
5858                                         "-C", "-M", "--cached", "HEAD", "--",
5859                                         oldpath, newpath, NULL
5860                         };
5862                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5863                                 return status_load_error(view, stage, newpath);
5864                 }
5866                 if (status)
5867                         info = "Staged changes to %s";
5868                 else
5869                         info = "Staged changes";
5870                 break;
5872         case LINE_STAT_UNSTAGED:
5873         {
5874                 const char *files_show_argv[] = {
5875                         "git", "diff-files", "--root", "--patch-with-stat",
5876                                 "-C", "-M", "--", oldpath, newpath, NULL
5877                 };
5879                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5880                         return status_load_error(view, stage, newpath);
5881                 if (status)
5882                         info = "Unstaged changes to %s";
5883                 else
5884                         info = "Unstaged changes";
5885                 break;
5886         }
5887         case LINE_STAT_UNTRACKED:
5888                 if (!newpath) {
5889                         report("No file to show");
5890                         return REQ_NONE;
5891                 }
5893                 if (!suffixcmp(status->new.name, -1, "/")) {
5894                         report("Cannot display a directory");
5895                         return REQ_NONE;
5896                 }
5898                 if (!prepare_update_file(stage, newpath))
5899                         return status_load_error(view, stage, newpath);
5900                 info = "Untracked file %s";
5901                 break;
5903         case LINE_STAT_HEAD:
5904                 return REQ_NONE;
5906         default:
5907                 die("line type %d not handled in switch", line->type);
5908         }
5910         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5911         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5912         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5913                 if (status) {
5914                         stage_status = *status;
5915                 } else {
5916                         memset(&stage_status, 0, sizeof(stage_status));
5917                 }
5919                 stage_line_type = line->type;
5920                 stage_chunks = 0;
5921                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5922         }
5924         return REQ_NONE;
5927 static bool
5928 status_exists(struct status *status, enum line_type type)
5930         struct view *view = VIEW(REQ_VIEW_STATUS);
5931         unsigned long lineno;
5933         for (lineno = 0; lineno < view->lines; lineno++) {
5934                 struct line *line = &view->line[lineno];
5935                 struct status *pos = line->data;
5937                 if (line->type != type)
5938                         continue;
5939                 if (!pos && (!status || !status->status) && line[1].data) {
5940                         select_view_line(view, lineno);
5941                         return TRUE;
5942                 }
5943                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5944                         select_view_line(view, lineno);
5945                         return TRUE;
5946                 }
5947         }
5949         return FALSE;
5953 static bool
5954 status_update_prepare(struct io *io, enum line_type type)
5956         const char *staged_argv[] = {
5957                 "git", "update-index", "-z", "--index-info", NULL
5958         };
5959         const char *others_argv[] = {
5960                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5961         };
5963         switch (type) {
5964         case LINE_STAT_STAGED:
5965                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5967         case LINE_STAT_UNSTAGED:
5968         case LINE_STAT_UNTRACKED:
5969                 return io_run(io, others_argv, opt_cdup, IO_WR);
5971         default:
5972                 die("line type %d not handled in switch", type);
5973                 return FALSE;
5974         }
5977 static bool
5978 status_update_write(struct io *io, struct status *status, enum line_type type)
5980         char buf[SIZEOF_STR];
5981         size_t bufsize = 0;
5983         switch (type) {
5984         case LINE_STAT_STAGED:
5985                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5986                                         status->old.mode,
5987                                         status->old.rev,
5988                                         status->old.name, 0))
5989                         return FALSE;
5990                 break;
5992         case LINE_STAT_UNSTAGED:
5993         case LINE_STAT_UNTRACKED:
5994                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5995                         return FALSE;
5996                 break;
5998         default:
5999                 die("line type %d not handled in switch", type);
6000         }
6002         return io_write(io, buf, bufsize);
6005 static bool
6006 status_update_file(struct status *status, enum line_type type)
6008         struct io io = {};
6009         bool result;
6011         if (!status_update_prepare(&io, type))
6012                 return FALSE;
6014         result = status_update_write(&io, status, type);
6015         return io_done(&io) && result;
6018 static bool
6019 status_update_files(struct view *view, struct line *line)
6021         char buf[sizeof(view->ref)];
6022         struct io io = {};
6023         bool result = TRUE;
6024         struct line *pos = view->line + view->lines;
6025         int files = 0;
6026         int file, done;
6027         int cursor_y = -1, cursor_x = -1;
6029         if (!status_update_prepare(&io, line->type))
6030                 return FALSE;
6032         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6033                 files++;
6035         string_copy(buf, view->ref);
6036         getsyx(cursor_y, cursor_x);
6037         for (file = 0, done = 5; result && file < files; line++, file++) {
6038                 int almost_done = file * 100 / files;
6040                 if (almost_done > done) {
6041                         done = almost_done;
6042                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6043                                       file, files, done);
6044                         update_view_title(view);
6045                         setsyx(cursor_y, cursor_x);
6046                         doupdate();
6047                 }
6048                 result = status_update_write(&io, line->data, line->type);
6049         }
6050         string_copy(view->ref, buf);
6052         return io_done(&io) && result;
6055 static bool
6056 status_update(struct view *view)
6058         struct line *line = &view->line[view->lineno];
6060         assert(view->lines);
6062         if (!line->data) {
6063                 /* This should work even for the "On branch" line. */
6064                 if (line < view->line + view->lines && !line[1].data) {
6065                         report("Nothing to update");
6066                         return FALSE;
6067                 }
6069                 if (!status_update_files(view, line + 1)) {
6070                         report("Failed to update file status");
6071                         return FALSE;
6072                 }
6074         } else if (!status_update_file(line->data, line->type)) {
6075                 report("Failed to update file status");
6076                 return FALSE;
6077         }
6079         return TRUE;
6082 static bool
6083 status_revert(struct status *status, enum line_type type, bool has_none)
6085         if (!status || type != LINE_STAT_UNSTAGED) {
6086                 if (type == LINE_STAT_STAGED) {
6087                         report("Cannot revert changes to staged files");
6088                 } else if (type == LINE_STAT_UNTRACKED) {
6089                         report("Cannot revert changes to untracked files");
6090                 } else if (has_none) {
6091                         report("Nothing to revert");
6092                 } else {
6093                         report("Cannot revert changes to multiple files");
6094                 }
6096         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6097                 char mode[10] = "100644";
6098                 const char *reset_argv[] = {
6099                         "git", "update-index", "--cacheinfo", mode,
6100                                 status->old.rev, status->old.name, NULL
6101                 };
6102                 const char *checkout_argv[] = {
6103                         "git", "checkout", "--", status->old.name, NULL
6104                 };
6106                 if (status->status == 'U') {
6107                         string_format(mode, "%5o", status->old.mode);
6109                         if (status->old.mode == 0 && status->new.mode == 0) {
6110                                 reset_argv[2] = "--force-remove";
6111                                 reset_argv[3] = status->old.name;
6112                                 reset_argv[4] = NULL;
6113                         }
6115                         if (!io_run_fg(reset_argv, opt_cdup))
6116                                 return FALSE;
6117                         if (status->old.mode == 0 && status->new.mode == 0)
6118                                 return TRUE;
6119                 }
6121                 return io_run_fg(checkout_argv, opt_cdup);
6122         }
6124         return FALSE;
6127 static enum request
6128 status_request(struct view *view, enum request request, struct line *line)
6130         struct status *status = line->data;
6132         switch (request) {
6133         case REQ_STATUS_UPDATE:
6134                 if (!status_update(view))
6135                         return REQ_NONE;
6136                 break;
6138         case REQ_STATUS_REVERT:
6139                 if (!status_revert(status, line->type, status_has_none(view, line)))
6140                         return REQ_NONE;
6141                 break;
6143         case REQ_STATUS_MERGE:
6144                 if (!status || status->status != 'U') {
6145                         report("Merging only possible for files with unmerged status ('U').");
6146                         return REQ_NONE;
6147                 }
6148                 open_mergetool(status->new.name);
6149                 break;
6151         case REQ_EDIT:
6152                 if (!status)
6153                         return request;
6154                 if (status->status == 'D') {
6155                         report("File has been deleted.");
6156                         return REQ_NONE;
6157                 }
6159                 open_editor(status->new.name);
6160                 break;
6162         case REQ_VIEW_BLAME:
6163                 if (status)
6164                         opt_ref[0] = 0;
6165                 return request;
6167         case REQ_ENTER:
6168                 /* After returning the status view has been split to
6169                  * show the stage view. No further reloading is
6170                  * necessary. */
6171                 return status_enter(view, line);
6173         case REQ_REFRESH:
6174                 /* Simply reload the view. */
6175                 break;
6177         default:
6178                 return request;
6179         }
6181         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6183         return REQ_NONE;
6186 static void
6187 status_select(struct view *view, struct line *line)
6189         struct status *status = line->data;
6190         char file[SIZEOF_STR] = "all files";
6191         const char *text;
6192         const char *key;
6194         if (status && !string_format(file, "'%s'", status->new.name))
6195                 return;
6197         if (!status && line[1].type == LINE_STAT_NONE)
6198                 line++;
6200         switch (line->type) {
6201         case LINE_STAT_STAGED:
6202                 text = "Press %s to unstage %s for commit";
6203                 break;
6205         case LINE_STAT_UNSTAGED:
6206                 text = "Press %s to stage %s for commit";
6207                 break;
6209         case LINE_STAT_UNTRACKED:
6210                 text = "Press %s to stage %s for addition";
6211                 break;
6213         case LINE_STAT_HEAD:
6214         case LINE_STAT_NONE:
6215                 text = "Nothing to update";
6216                 break;
6218         default:
6219                 die("line type %d not handled in switch", line->type);
6220         }
6222         if (status && status->status == 'U') {
6223                 text = "Press %s to resolve conflict in %s";
6224                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6226         } else {
6227                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6228         }
6230         string_format(view->ref, text, key, file);
6231         if (status)
6232                 string_copy(opt_file, status->new.name);
6235 static bool
6236 status_grep(struct view *view, struct line *line)
6238         struct status *status = line->data;
6240         if (status) {
6241                 const char buf[2] = { status->status, 0 };
6242                 const char *text[] = { status->new.name, buf, NULL };
6244                 return grep_text(view, text);
6245         }
6247         return FALSE;
6250 static struct view_ops status_ops = {
6251         "file",
6252         NULL,
6253         status_open,
6254         NULL,
6255         status_draw,
6256         status_request,
6257         status_grep,
6258         status_select,
6259 };
6262 static bool
6263 stage_diff_write(struct io *io, struct line *line, struct line *end)
6265         while (line < end) {
6266                 if (!io_write(io, line->data, strlen(line->data)) ||
6267                     !io_write(io, "\n", 1))
6268                         return FALSE;
6269                 line++;
6270                 if (line->type == LINE_DIFF_CHUNK ||
6271                     line->type == LINE_DIFF_HEADER)
6272                         break;
6273         }
6275         return TRUE;
6278 static struct line *
6279 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6281         for (; view->line < line; line--)
6282                 if (line->type == type)
6283                         return line;
6285         return NULL;
6288 static bool
6289 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6291         const char *apply_argv[SIZEOF_ARG] = {
6292                 "git", "apply", "--whitespace=nowarn", NULL
6293         };
6294         struct line *diff_hdr;
6295         struct io io = {};
6296         int argc = 3;
6298         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6299         if (!diff_hdr)
6300                 return FALSE;
6302         if (!revert)
6303                 apply_argv[argc++] = "--cached";
6304         if (revert || stage_line_type == LINE_STAT_STAGED)
6305                 apply_argv[argc++] = "-R";
6306         apply_argv[argc++] = "-";
6307         apply_argv[argc++] = NULL;
6308         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6309                 return FALSE;
6311         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6312             !stage_diff_write(&io, chunk, view->line + view->lines))
6313                 chunk = NULL;
6315         io_done(&io);
6316         io_run_bg(update_index_argv);
6318         return chunk ? TRUE : FALSE;
6321 static bool
6322 stage_update(struct view *view, struct line *line)
6324         struct line *chunk = NULL;
6326         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6327                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6329         if (chunk) {
6330                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6331                         report("Failed to apply chunk");
6332                         return FALSE;
6333                 }
6335         } else if (!stage_status.status) {
6336                 view = VIEW(REQ_VIEW_STATUS);
6338                 for (line = view->line; line < view->line + view->lines; line++)
6339                         if (line->type == stage_line_type)
6340                                 break;
6342                 if (!status_update_files(view, line + 1)) {
6343                         report("Failed to update files");
6344                         return FALSE;
6345                 }
6347         } else if (!status_update_file(&stage_status, stage_line_type)) {
6348                 report("Failed to update file");
6349                 return FALSE;
6350         }
6352         return TRUE;
6355 static bool
6356 stage_revert(struct view *view, struct line *line)
6358         struct line *chunk = NULL;
6360         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6361                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6363         if (chunk) {
6364                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6365                         return FALSE;
6367                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6368                         report("Failed to revert chunk");
6369                         return FALSE;
6370                 }
6371                 return TRUE;
6373         } else {
6374                 return status_revert(stage_status.status ? &stage_status : NULL,
6375                                      stage_line_type, FALSE);
6376         }
6380 static void
6381 stage_next(struct view *view, struct line *line)
6383         int i;
6385         if (!stage_chunks) {
6386                 for (line = view->line; line < view->line + view->lines; line++) {
6387                         if (line->type != LINE_DIFF_CHUNK)
6388                                 continue;
6390                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6391                                 report("Allocation failure");
6392                                 return;
6393                         }
6395                         stage_chunk[stage_chunks++] = line - view->line;
6396                 }
6397         }
6399         for (i = 0; i < stage_chunks; i++) {
6400                 if (stage_chunk[i] > view->lineno) {
6401                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6402                         report("Chunk %d of %d", i + 1, stage_chunks);
6403                         return;
6404                 }
6405         }
6407         report("No next chunk found");
6410 static enum request
6411 stage_request(struct view *view, enum request request, struct line *line)
6413         switch (request) {
6414         case REQ_STATUS_UPDATE:
6415                 if (!stage_update(view, line))
6416                         return REQ_NONE;
6417                 break;
6419         case REQ_STATUS_REVERT:
6420                 if (!stage_revert(view, line))
6421                         return REQ_NONE;
6422                 break;
6424         case REQ_STAGE_NEXT:
6425                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6426                         report("File is untracked; press %s to add",
6427                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6428                         return REQ_NONE;
6429                 }
6430                 stage_next(view, line);
6431                 return REQ_NONE;
6433         case REQ_EDIT:
6434                 if (!stage_status.new.name[0])
6435                         return request;
6436                 if (stage_status.status == 'D') {
6437                         report("File has been deleted.");
6438                         return REQ_NONE;
6439                 }
6441                 open_editor(stage_status.new.name);
6442                 break;
6444         case REQ_REFRESH:
6445                 /* Reload everything ... */
6446                 break;
6448         case REQ_VIEW_BLAME:
6449                 if (stage_status.new.name[0]) {
6450                         string_copy(opt_file, stage_status.new.name);
6451                         opt_ref[0] = 0;
6452                 }
6453                 return request;
6455         case REQ_ENTER:
6456                 return pager_request(view, request, line);
6458         default:
6459                 return request;
6460         }
6462         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6463         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6465         /* Check whether the staged entry still exists, and close the
6466          * stage view if it doesn't. */
6467         if (!status_exists(&stage_status, stage_line_type)) {
6468                 status_restore(VIEW(REQ_VIEW_STATUS));
6469                 return REQ_VIEW_CLOSE;
6470         }
6472         if (stage_line_type == LINE_STAT_UNTRACKED) {
6473                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6474                         report("Cannot display a directory");
6475                         return REQ_NONE;
6476                 }
6478                 if (!prepare_update_file(view, stage_status.new.name)) {
6479                         report("Failed to open file: %s", strerror(errno));
6480                         return REQ_NONE;
6481                 }
6482         }
6483         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6485         return REQ_NONE;
6488 static struct view_ops stage_ops = {
6489         "line",
6490         NULL,
6491         NULL,
6492         pager_read,
6493         pager_draw,
6494         stage_request,
6495         pager_grep,
6496         pager_select,
6497 };
6500 /*
6501  * Revision graph
6502  */
6504 struct commit {
6505         char id[SIZEOF_REV];            /* SHA1 ID. */
6506         char title[128];                /* First line of the commit message. */
6507         const char *author;             /* Author of the commit. */
6508         struct time time;               /* Date from the author ident. */
6509         struct ref_list *refs;          /* Repository references. */
6510         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6511         size_t graph_size;              /* The width of the graph array. */
6512         bool has_parents;               /* Rewritten --parents seen. */
6513 };
6515 /* Size of rev graph with no  "padding" columns */
6516 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6518 struct rev_graph {
6519         struct rev_graph *prev, *next, *parents;
6520         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6521         size_t size;
6522         struct commit *commit;
6523         size_t pos;
6524         unsigned int boundary:1;
6525 };
6527 /* Parents of the commit being visualized. */
6528 static struct rev_graph graph_parents[4];
6530 /* The current stack of revisions on the graph. */
6531 static struct rev_graph graph_stacks[4] = {
6532         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6533         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6534         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6535         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6536 };
6538 static inline bool
6539 graph_parent_is_merge(struct rev_graph *graph)
6541         return graph->parents->size > 1;
6544 static inline void
6545 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6547         struct commit *commit = graph->commit;
6549         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6550                 commit->graph[commit->graph_size++] = symbol;
6553 static void
6554 clear_rev_graph(struct rev_graph *graph)
6556         graph->boundary = 0;
6557         graph->size = graph->pos = 0;
6558         graph->commit = NULL;
6559         memset(graph->parents, 0, sizeof(*graph->parents));
6562 static void
6563 done_rev_graph(struct rev_graph *graph)
6565         if (graph_parent_is_merge(graph) &&
6566             graph->pos < graph->size - 1 &&
6567             graph->next->size == graph->size + graph->parents->size - 1) {
6568                 size_t i = graph->pos + graph->parents->size - 1;
6570                 graph->commit->graph_size = i * 2;
6571                 while (i < graph->next->size - 1) {
6572                         append_to_rev_graph(graph, ' ');
6573                         append_to_rev_graph(graph, '\\');
6574                         i++;
6575                 }
6576         }
6578         clear_rev_graph(graph);
6581 static void
6582 push_rev_graph(struct rev_graph *graph, const char *parent)
6584         int i;
6586         /* "Collapse" duplicate parents lines.
6587          *
6588          * FIXME: This needs to also update update the drawn graph but
6589          * for now it just serves as a method for pruning graph lines. */
6590         for (i = 0; i < graph->size; i++)
6591                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6592                         return;
6594         if (graph->size < SIZEOF_REVITEMS) {
6595                 string_copy_rev(graph->rev[graph->size++], parent);
6596         }
6599 static chtype
6600 get_rev_graph_symbol(struct rev_graph *graph)
6602         chtype symbol;
6604         if (graph->boundary)
6605                 symbol = REVGRAPH_BOUND;
6606         else if (graph->parents->size == 0)
6607                 symbol = REVGRAPH_INIT;
6608         else if (graph_parent_is_merge(graph))
6609                 symbol = REVGRAPH_MERGE;
6610         else if (graph->pos >= graph->size)
6611                 symbol = REVGRAPH_BRANCH;
6612         else
6613                 symbol = REVGRAPH_COMMIT;
6615         return symbol;
6618 static void
6619 draw_rev_graph(struct rev_graph *graph)
6621         struct rev_filler {
6622                 chtype separator, line;
6623         };
6624         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6625         static struct rev_filler fillers[] = {
6626                 { ' ',  '|' },
6627                 { '`',  '.' },
6628                 { '\'', ' ' },
6629                 { '/',  ' ' },
6630         };
6631         chtype symbol = get_rev_graph_symbol(graph);
6632         struct rev_filler *filler;
6633         size_t i;
6635         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6636         filler = &fillers[DEFAULT];
6638         for (i = 0; i < graph->pos; i++) {
6639                 append_to_rev_graph(graph, filler->line);
6640                 if (graph_parent_is_merge(graph->prev) &&
6641                     graph->prev->pos == i)
6642                         filler = &fillers[RSHARP];
6644                 append_to_rev_graph(graph, filler->separator);
6645         }
6647         /* Place the symbol for this revision. */
6648         append_to_rev_graph(graph, symbol);
6650         if (graph->prev->size > graph->size)
6651                 filler = &fillers[RDIAG];
6652         else
6653                 filler = &fillers[DEFAULT];
6655         i++;
6657         for (; i < graph->size; i++) {
6658                 append_to_rev_graph(graph, filler->separator);
6659                 append_to_rev_graph(graph, filler->line);
6660                 if (graph_parent_is_merge(graph->prev) &&
6661                     i < graph->prev->pos + graph->parents->size)
6662                         filler = &fillers[RSHARP];
6663                 if (graph->prev->size > graph->size)
6664                         filler = &fillers[LDIAG];
6665         }
6667         if (graph->prev->size > graph->size) {
6668                 append_to_rev_graph(graph, filler->separator);
6669                 if (filler->line != ' ')
6670                         append_to_rev_graph(graph, filler->line);
6671         }
6674 /* Prepare the next rev graph */
6675 static void
6676 prepare_rev_graph(struct rev_graph *graph)
6678         size_t i;
6680         /* First, traverse all lines of revisions up to the active one. */
6681         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6682                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6683                         break;
6685                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6686         }
6688         /* Interleave the new revision parent(s). */
6689         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6690                 push_rev_graph(graph->next, graph->parents->rev[i]);
6692         /* Lastly, put any remaining revisions. */
6693         for (i = graph->pos + 1; i < graph->size; i++)
6694                 push_rev_graph(graph->next, graph->rev[i]);
6697 static void
6698 update_rev_graph(struct view *view, struct rev_graph *graph)
6700         /* If this is the finalizing update ... */
6701         if (graph->commit)
6702                 prepare_rev_graph(graph);
6704         /* Graph visualization needs a one rev look-ahead,
6705          * so the first update doesn't visualize anything. */
6706         if (!graph->prev->commit)
6707                 return;
6709         if (view->lines > 2)
6710                 view->line[view->lines - 3].dirty = 1;
6711         if (view->lines > 1)
6712                 view->line[view->lines - 2].dirty = 1;
6713         draw_rev_graph(graph->prev);
6714         done_rev_graph(graph->prev->prev);
6718 /*
6719  * Main view backend
6720  */
6722 static const char *main_argv[SIZEOF_ARG] = {
6723         "git", "log", "--no-color", "--pretty=raw", "--parents",
6724                       "--topo-order", "%(head)", NULL
6725 };
6727 static bool
6728 main_draw(struct view *view, struct line *line, unsigned int lineno)
6730         struct commit *commit = line->data;
6732         if (!commit->author)
6733                 return FALSE;
6735         if (opt_date && draw_date(view, &commit->time))
6736                 return TRUE;
6738         if (opt_author && draw_author(view, commit->author))
6739                 return TRUE;
6741         if (opt_rev_graph && commit->graph_size &&
6742             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6743                 return TRUE;
6745         if (opt_show_refs && commit->refs) {
6746                 size_t i;
6748                 for (i = 0; i < commit->refs->size; i++) {
6749                         struct ref *ref = commit->refs->refs[i];
6750                         enum line_type type;
6752                         if (ref->head)
6753                                 type = LINE_MAIN_HEAD;
6754                         else if (ref->ltag)
6755                                 type = LINE_MAIN_LOCAL_TAG;
6756                         else if (ref->tag)
6757                                 type = LINE_MAIN_TAG;
6758                         else if (ref->tracked)
6759                                 type = LINE_MAIN_TRACKED;
6760                         else if (ref->remote)
6761                                 type = LINE_MAIN_REMOTE;
6762                         else
6763                                 type = LINE_MAIN_REF;
6765                         if (draw_text(view, type, "[", TRUE) ||
6766                             draw_text(view, type, ref->name, TRUE) ||
6767                             draw_text(view, type, "]", TRUE))
6768                                 return TRUE;
6770                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6771                                 return TRUE;
6772                 }
6773         }
6775         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6776         return TRUE;
6779 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6780 static bool
6781 main_read(struct view *view, char *line)
6783         static struct rev_graph *graph = graph_stacks;
6784         enum line_type type;
6785         struct commit *commit;
6787         if (!line) {
6788                 int i;
6790                 if (!view->lines && !view->prev)
6791                         die("No revisions match the given arguments.");
6792                 if (view->lines > 0) {
6793                         commit = view->line[view->lines - 1].data;
6794                         view->line[view->lines - 1].dirty = 1;
6795                         if (!commit->author) {
6796                                 view->lines--;
6797                                 free(commit);
6798                                 graph->commit = NULL;
6799                         }
6800                 }
6801                 update_rev_graph(view, graph);
6803                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6804                         clear_rev_graph(&graph_stacks[i]);
6805                 return TRUE;
6806         }
6808         type = get_line_type(line);
6809         if (type == LINE_COMMIT) {
6810                 commit = calloc(1, sizeof(struct commit));
6811                 if (!commit)
6812                         return FALSE;
6814                 line += STRING_SIZE("commit ");
6815                 if (*line == '-') {
6816                         graph->boundary = 1;
6817                         line++;
6818                 }
6820                 string_copy_rev(commit->id, line);
6821                 commit->refs = get_ref_list(commit->id);
6822                 graph->commit = commit;
6823                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6825                 while ((line = strchr(line, ' '))) {
6826                         line++;
6827                         push_rev_graph(graph->parents, line);
6828                         commit->has_parents = TRUE;
6829                 }
6830                 return TRUE;
6831         }
6833         if (!view->lines)
6834                 return TRUE;
6835         commit = view->line[view->lines - 1].data;
6837         switch (type) {
6838         case LINE_PARENT:
6839                 if (commit->has_parents)
6840                         break;
6841                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6842                 break;
6844         case LINE_AUTHOR:
6845                 parse_author_line(line + STRING_SIZE("author "),
6846                                   &commit->author, &commit->time);
6847                 update_rev_graph(view, graph);
6848                 graph = graph->next;
6849                 break;
6851         default:
6852                 /* Fill in the commit title if it has not already been set. */
6853                 if (commit->title[0])
6854                         break;
6856                 /* Require titles to start with a non-space character at the
6857                  * offset used by git log. */
6858                 if (strncmp(line, "    ", 4))
6859                         break;
6860                 line += 4;
6861                 /* Well, if the title starts with a whitespace character,
6862                  * try to be forgiving.  Otherwise we end up with no title. */
6863                 while (isspace(*line))
6864                         line++;
6865                 if (*line == '\0')
6866                         break;
6867                 /* FIXME: More graceful handling of titles; append "..." to
6868                  * shortened titles, etc. */
6870                 string_expand(commit->title, sizeof(commit->title), line, 1);
6871                 view->line[view->lines - 1].dirty = 1;
6872         }
6874         return TRUE;
6877 static enum request
6878 main_request(struct view *view, enum request request, struct line *line)
6880         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6882         switch (request) {
6883         case REQ_ENTER:
6884                 open_view(view, REQ_VIEW_DIFF, flags);
6885                 break;
6886         case REQ_REFRESH:
6887                 load_refs();
6888                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6889                 break;
6890         default:
6891                 return request;
6892         }
6894         return REQ_NONE;
6897 static bool
6898 grep_refs(struct ref_list *list, regex_t *regex)
6900         regmatch_t pmatch;
6901         size_t i;
6903         if (!opt_show_refs || !list)
6904                 return FALSE;
6906         for (i = 0; i < list->size; i++) {
6907                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6908                         return TRUE;
6909         }
6911         return FALSE;
6914 static bool
6915 main_grep(struct view *view, struct line *line)
6917         struct commit *commit = line->data;
6918         const char *text[] = {
6919                 commit->title,
6920                 opt_author ? commit->author : "",
6921                 mkdate(&commit->time, opt_date),
6922                 NULL
6923         };
6925         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6928 static void
6929 main_select(struct view *view, struct line *line)
6931         struct commit *commit = line->data;
6933         string_copy_rev(view->ref, commit->id);
6934         string_copy_rev(ref_commit, view->ref);
6937 static struct view_ops main_ops = {
6938         "commit",
6939         main_argv,
6940         NULL,
6941         main_read,
6942         main_draw,
6943         main_request,
6944         main_grep,
6945         main_select,
6946 };
6949 /*
6950  * Status management
6951  */
6953 /* Whether or not the curses interface has been initialized. */
6954 static bool cursed = FALSE;
6956 /* Terminal hacks and workarounds. */
6957 static bool use_scroll_redrawwin;
6958 static bool use_scroll_status_wclear;
6960 /* The status window is used for polling keystrokes. */
6961 static WINDOW *status_win;
6963 /* Reading from the prompt? */
6964 static bool input_mode = FALSE;
6966 static bool status_empty = FALSE;
6968 /* Update status and title window. */
6969 static void
6970 report(const char *msg, ...)
6972         struct view *view = display[current_view];
6974         if (input_mode)
6975                 return;
6977         if (!view) {
6978                 char buf[SIZEOF_STR];
6979                 va_list args;
6981                 va_start(args, msg);
6982                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6983                         buf[sizeof(buf) - 1] = 0;
6984                         buf[sizeof(buf) - 2] = '.';
6985                         buf[sizeof(buf) - 3] = '.';
6986                         buf[sizeof(buf) - 4] = '.';
6987                 }
6988                 va_end(args);
6989                 die("%s", buf);
6990         }
6992         if (!status_empty || *msg) {
6993                 va_list args;
6995                 va_start(args, msg);
6997                 wmove(status_win, 0, 0);
6998                 if (view->has_scrolled && use_scroll_status_wclear)
6999                         wclear(status_win);
7000                 if (*msg) {
7001                         vwprintw(status_win, msg, args);
7002                         status_empty = FALSE;
7003                 } else {
7004                         status_empty = TRUE;
7005                 }
7006                 wclrtoeol(status_win);
7007                 wnoutrefresh(status_win);
7009                 va_end(args);
7010         }
7012         update_view_title(view);
7015 static void
7016 init_display(void)
7018         const char *term;
7019         int x, y;
7021         /* Initialize the curses library */
7022         if (isatty(STDIN_FILENO)) {
7023                 cursed = !!initscr();
7024                 opt_tty = stdin;
7025         } else {
7026                 /* Leave stdin and stdout alone when acting as a pager. */
7027                 opt_tty = fopen("/dev/tty", "r+");
7028                 if (!opt_tty)
7029                         die("Failed to open /dev/tty");
7030                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7031         }
7033         if (!cursed)
7034                 die("Failed to initialize curses");
7036         nonl();         /* Disable conversion and detect newlines from input. */
7037         cbreak();       /* Take input chars one at a time, no wait for \n */
7038         noecho();       /* Don't echo input */
7039         leaveok(stdscr, FALSE);
7041         if (has_colors())
7042                 init_colors();
7044         getmaxyx(stdscr, y, x);
7045         status_win = newwin(1, 0, y - 1, 0);
7046         if (!status_win)
7047                 die("Failed to create status window");
7049         /* Enable keyboard mapping */
7050         keypad(status_win, TRUE);
7051         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7053         TABSIZE = opt_tab_size;
7055         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7056         if (term && !strcmp(term, "gnome-terminal")) {
7057                 /* In the gnome-terminal-emulator, the message from
7058                  * scrolling up one line when impossible followed by
7059                  * scrolling down one line causes corruption of the
7060                  * status line. This is fixed by calling wclear. */
7061                 use_scroll_status_wclear = TRUE;
7062                 use_scroll_redrawwin = FALSE;
7064         } else if (term && !strcmp(term, "xrvt-xpm")) {
7065                 /* No problems with full optimizations in xrvt-(unicode)
7066                  * and aterm. */
7067                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7069         } else {
7070                 /* When scrolling in (u)xterm the last line in the
7071                  * scrolling direction will update slowly. */
7072                 use_scroll_redrawwin = TRUE;
7073                 use_scroll_status_wclear = FALSE;
7074         }
7077 static int
7078 get_input(int prompt_position)
7080         struct view *view;
7081         int i, key, cursor_y, cursor_x;
7082         bool loading = FALSE;
7084         if (prompt_position)
7085                 input_mode = TRUE;
7087         while (TRUE) {
7088                 foreach_view (view, i) {
7089                         update_view(view);
7090                         if (view_is_displayed(view) && view->has_scrolled &&
7091                             use_scroll_redrawwin)
7092                                 redrawwin(view->win);
7093                         view->has_scrolled = FALSE;
7094                         if (view->pipe)
7095                                 loading = TRUE;
7096                 }
7098                 /* Update the cursor position. */
7099                 if (prompt_position) {
7100                         getbegyx(status_win, cursor_y, cursor_x);
7101                         cursor_x = prompt_position;
7102                 } else {
7103                         view = display[current_view];
7104                         getbegyx(view->win, cursor_y, cursor_x);
7105                         cursor_x = view->width - 1;
7106                         cursor_y += view->lineno - view->offset;
7107                 }
7108                 setsyx(cursor_y, cursor_x);
7110                 /* Refresh, accept single keystroke of input */
7111                 doupdate();
7112                 nodelay(status_win, loading);
7113                 key = wgetch(status_win);
7115                 /* wgetch() with nodelay() enabled returns ERR when
7116                  * there's no input. */
7117                 if (key == ERR) {
7119                 } else if (key == KEY_RESIZE) {
7120                         int height, width;
7122                         getmaxyx(stdscr, height, width);
7124                         wresize(status_win, 1, width);
7125                         mvwin(status_win, height - 1, 0);
7126                         wnoutrefresh(status_win);
7127                         resize_display();
7128                         redraw_display(TRUE);
7130                 } else {
7131                         input_mode = FALSE;
7132                         return key;
7133                 }
7134         }
7137 static char *
7138 prompt_input(const char *prompt, input_handler handler, void *data)
7140         enum input_status status = INPUT_OK;
7141         static char buf[SIZEOF_STR];
7142         size_t pos = 0;
7144         buf[pos] = 0;
7146         while (status == INPUT_OK || status == INPUT_SKIP) {
7147                 int key;
7149                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7150                 wclrtoeol(status_win);
7152                 key = get_input(pos + 1);
7153                 switch (key) {
7154                 case KEY_RETURN:
7155                 case KEY_ENTER:
7156                 case '\n':
7157                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7158                         break;
7160                 case KEY_BACKSPACE:
7161                         if (pos > 0)
7162                                 buf[--pos] = 0;
7163                         else
7164                                 status = INPUT_CANCEL;
7165                         break;
7167                 case KEY_ESC:
7168                         status = INPUT_CANCEL;
7169                         break;
7171                 default:
7172                         if (pos >= sizeof(buf)) {
7173                                 report("Input string too long");
7174                                 return NULL;
7175                         }
7177                         status = handler(data, buf, key);
7178                         if (status == INPUT_OK)
7179                                 buf[pos++] = (char) key;
7180                 }
7181         }
7183         /* Clear the status window */
7184         status_empty = FALSE;
7185         report("");
7187         if (status == INPUT_CANCEL)
7188                 return NULL;
7190         buf[pos++] = 0;
7192         return buf;
7195 static enum input_status
7196 prompt_yesno_handler(void *data, char *buf, int c)
7198         if (c == 'y' || c == 'Y')
7199                 return INPUT_STOP;
7200         if (c == 'n' || c == 'N')
7201                 return INPUT_CANCEL;
7202         return INPUT_SKIP;
7205 static bool
7206 prompt_yesno(const char *prompt)
7208         char prompt2[SIZEOF_STR];
7210         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7211                 return FALSE;
7213         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7216 static enum input_status
7217 read_prompt_handler(void *data, char *buf, int c)
7219         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7222 static char *
7223 read_prompt(const char *prompt)
7225         return prompt_input(prompt, read_prompt_handler, NULL);
7228 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7230         enum input_status status = INPUT_OK;
7231         int size = 0;
7233         while (items[size].text)
7234                 size++;
7236         while (status == INPUT_OK) {
7237                 const struct menu_item *item = &items[*selected];
7238                 int key;
7239                 int i;
7241                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7242                           prompt, *selected + 1, size);
7243                 if (item->hotkey)
7244                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7245                 wprintw(status_win, "%s", item->text);
7246                 wclrtoeol(status_win);
7248                 key = get_input(COLS - 1);
7249                 switch (key) {
7250                 case KEY_RETURN:
7251                 case KEY_ENTER:
7252                 case '\n':
7253                         status = INPUT_STOP;
7254                         break;
7256                 case KEY_LEFT:
7257                 case KEY_UP:
7258                         *selected = *selected - 1;
7259                         if (*selected < 0)
7260                                 *selected = size - 1;
7261                         break;
7263                 case KEY_RIGHT:
7264                 case KEY_DOWN:
7265                         *selected = (*selected + 1) % size;
7266                         break;
7268                 case KEY_ESC:
7269                         status = INPUT_CANCEL;
7270                         break;
7272                 default:
7273                         for (i = 0; items[i].text; i++)
7274                                 if (items[i].hotkey == key) {
7275                                         *selected = i;
7276                                         status = INPUT_STOP;
7277                                         break;
7278                                 }
7279                 }
7280         }
7282         /* Clear the status window */
7283         status_empty = FALSE;
7284         report("");
7286         return status != INPUT_CANCEL;
7289 /*
7290  * Repository properties
7291  */
7293 static struct ref **refs = NULL;
7294 static size_t refs_size = 0;
7295 static struct ref *refs_head = NULL;
7297 static struct ref_list **ref_lists = NULL;
7298 static size_t ref_lists_size = 0;
7300 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7301 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7302 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7304 static int
7305 compare_refs(const void *ref1_, const void *ref2_)
7307         const struct ref *ref1 = *(const struct ref **)ref1_;
7308         const struct ref *ref2 = *(const struct ref **)ref2_;
7310         if (ref1->tag != ref2->tag)
7311                 return ref2->tag - ref1->tag;
7312         if (ref1->ltag != ref2->ltag)
7313                 return ref2->ltag - ref2->ltag;
7314         if (ref1->head != ref2->head)
7315                 return ref2->head - ref1->head;
7316         if (ref1->tracked != ref2->tracked)
7317                 return ref2->tracked - ref1->tracked;
7318         if (ref1->remote != ref2->remote)
7319                 return ref2->remote - ref1->remote;
7320         return strcmp(ref1->name, ref2->name);
7323 static void
7324 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7326         size_t i;
7328         for (i = 0; i < refs_size; i++)
7329                 if (!visitor(data, refs[i]))
7330                         break;
7333 static struct ref *
7334 get_ref_head()
7336         return refs_head;
7339 static struct ref_list *
7340 get_ref_list(const char *id)
7342         struct ref_list *list;
7343         size_t i;
7345         for (i = 0; i < ref_lists_size; i++)
7346                 if (!strcmp(id, ref_lists[i]->id))
7347                         return ref_lists[i];
7349         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7350                 return NULL;
7351         list = calloc(1, sizeof(*list));
7352         if (!list)
7353                 return NULL;
7355         for (i = 0; i < refs_size; i++) {
7356                 if (!strcmp(id, refs[i]->id) &&
7357                     realloc_refs_list(&list->refs, list->size, 1))
7358                         list->refs[list->size++] = refs[i];
7359         }
7361         if (!list->refs) {
7362                 free(list);
7363                 return NULL;
7364         }
7366         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7367         ref_lists[ref_lists_size++] = list;
7368         return list;
7371 static int
7372 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7374         struct ref *ref = NULL;
7375         bool tag = FALSE;
7376         bool ltag = FALSE;
7377         bool remote = FALSE;
7378         bool tracked = FALSE;
7379         bool head = FALSE;
7380         int from = 0, to = refs_size - 1;
7382         if (!prefixcmp(name, "refs/tags/")) {
7383                 if (!suffixcmp(name, namelen, "^{}")) {
7384                         namelen -= 3;
7385                         name[namelen] = 0;
7386                 } else {
7387                         ltag = TRUE;
7388                 }
7390                 tag = TRUE;
7391                 namelen -= STRING_SIZE("refs/tags/");
7392                 name    += STRING_SIZE("refs/tags/");
7394         } else if (!prefixcmp(name, "refs/remotes/")) {
7395                 remote = TRUE;
7396                 namelen -= STRING_SIZE("refs/remotes/");
7397                 name    += STRING_SIZE("refs/remotes/");
7398                 tracked  = !strcmp(opt_remote, name);
7400         } else if (!prefixcmp(name, "refs/heads/")) {
7401                 namelen -= STRING_SIZE("refs/heads/");
7402                 name    += STRING_SIZE("refs/heads/");
7403                 if (!strncmp(opt_head, name, namelen))
7404                         return OK;
7406         } else if (!strcmp(name, "HEAD")) {
7407                 head     = TRUE;
7408                 if (*opt_head) {
7409                         namelen  = strlen(opt_head);
7410                         name     = opt_head;
7411                 }
7412         }
7414         /* If we are reloading or it's an annotated tag, replace the
7415          * previous SHA1 with the resolved commit id; relies on the fact
7416          * git-ls-remote lists the commit id of an annotated tag right
7417          * before the commit id it points to. */
7418         while (from <= to) {
7419                 size_t pos = (to + from) / 2;
7420                 int cmp = strcmp(name, refs[pos]->name);
7422                 if (!cmp) {
7423                         ref = refs[pos];
7424                         break;
7425                 }
7427                 if (cmp < 0)
7428                         to = pos - 1;
7429                 else
7430                         from = pos + 1;
7431         }
7433         if (!ref) {
7434                 if (!realloc_refs(&refs, refs_size, 1))
7435                         return ERR;
7436                 ref = calloc(1, sizeof(*ref) + namelen);
7437                 if (!ref)
7438                         return ERR;
7439                 memmove(refs + from + 1, refs + from,
7440                         (refs_size - from) * sizeof(*refs));
7441                 refs[from] = ref;
7442                 strncpy(ref->name, name, namelen);
7443                 refs_size++;
7444         }
7446         ref->head = head;
7447         ref->tag = tag;
7448         ref->ltag = ltag;
7449         ref->remote = remote;
7450         ref->tracked = tracked;
7451         string_copy_rev(ref->id, id);
7453         if (head)
7454                 refs_head = ref;
7455         return OK;
7458 static int
7459 load_refs(void)
7461         const char *head_argv[] = {
7462                 "git", "symbolic-ref", "HEAD", NULL
7463         };
7464         static const char *ls_remote_argv[SIZEOF_ARG] = {
7465                 "git", "ls-remote", opt_git_dir, NULL
7466         };
7467         static bool init = FALSE;
7468         size_t i;
7470         if (!init) {
7471                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7472                         die("TIG_LS_REMOTE contains too many arguments");
7473                 init = TRUE;
7474         }
7476         if (!*opt_git_dir)
7477                 return OK;
7479         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7480             !prefixcmp(opt_head, "refs/heads/")) {
7481                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7483                 memmove(opt_head, offset, strlen(offset) + 1);
7484         }
7486         refs_head = NULL;
7487         for (i = 0; i < refs_size; i++)
7488                 refs[i]->id[0] = 0;
7490         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7491                 return ERR;
7493         /* Update the ref lists to reflect changes. */
7494         for (i = 0; i < ref_lists_size; i++) {
7495                 struct ref_list *list = ref_lists[i];
7496                 size_t old, new;
7498                 for (old = new = 0; old < list->size; old++)
7499                         if (!strcmp(list->id, list->refs[old]->id))
7500                                 list->refs[new++] = list->refs[old];
7501                 list->size = new;
7502         }
7504         return OK;
7507 static void
7508 set_remote_branch(const char *name, const char *value, size_t valuelen)
7510         if (!strcmp(name, ".remote")) {
7511                 string_ncopy(opt_remote, value, valuelen);
7513         } else if (*opt_remote && !strcmp(name, ".merge")) {
7514                 size_t from = strlen(opt_remote);
7516                 if (!prefixcmp(value, "refs/heads/"))
7517                         value += STRING_SIZE("refs/heads/");
7519                 if (!string_format_from(opt_remote, &from, "/%s", value))
7520                         opt_remote[0] = 0;
7521         }
7524 static void
7525 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7527         const char *argv[SIZEOF_ARG] = { name, "=" };
7528         int argc = 1 + (cmd == option_set_command);
7529         int error = ERR;
7531         if (!argv_from_string(argv, &argc, value))
7532                 config_msg = "Too many option arguments";
7533         else
7534                 error = cmd(argc, argv);
7536         if (error == ERR)
7537                 warn("Option 'tig.%s': %s", name, config_msg);
7540 static bool
7541 set_environment_variable(const char *name, const char *value)
7543         size_t len = strlen(name) + 1 + strlen(value) + 1;
7544         char *env = malloc(len);
7546         if (env &&
7547             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7548             putenv(env) == 0)
7549                 return TRUE;
7550         free(env);
7551         return FALSE;
7554 static void
7555 set_work_tree(const char *value)
7557         char cwd[SIZEOF_STR];
7559         if (!getcwd(cwd, sizeof(cwd)))
7560                 die("Failed to get cwd path: %s", strerror(errno));
7561         if (chdir(opt_git_dir) < 0)
7562                 die("Failed to chdir(%s): %s", strerror(errno));
7563         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7564                 die("Failed to get git path: %s", strerror(errno));
7565         if (chdir(cwd) < 0)
7566                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7567         if (chdir(value) < 0)
7568                 die("Failed to chdir(%s): %s", value, strerror(errno));
7569         if (!getcwd(cwd, sizeof(cwd)))
7570                 die("Failed to get cwd path: %s", strerror(errno));
7571         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7572                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7573         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7574                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7575         opt_is_inside_work_tree = TRUE;
7578 static int
7579 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7581         if (!strcmp(name, "i18n.commitencoding"))
7582                 string_ncopy(opt_encoding, value, valuelen);
7584         else if (!strcmp(name, "core.editor"))
7585                 string_ncopy(opt_editor, value, valuelen);
7587         else if (!strcmp(name, "core.worktree"))
7588                 set_work_tree(value);
7590         else if (!prefixcmp(name, "tig.color."))
7591                 set_repo_config_option(name + 10, value, option_color_command);
7593         else if (!prefixcmp(name, "tig.bind."))
7594                 set_repo_config_option(name + 9, value, option_bind_command);
7596         else if (!prefixcmp(name, "tig."))
7597                 set_repo_config_option(name + 4, value, option_set_command);
7599         else if (*opt_head && !prefixcmp(name, "branch.") &&
7600                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7601                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7603         return OK;
7606 static int
7607 load_git_config(void)
7609         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7611         return io_run_load(config_list_argv, "=", read_repo_config_option);
7614 static int
7615 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7617         if (!opt_git_dir[0]) {
7618                 string_ncopy(opt_git_dir, name, namelen);
7620         } else if (opt_is_inside_work_tree == -1) {
7621                 /* This can be 3 different values depending on the
7622                  * version of git being used. If git-rev-parse does not
7623                  * understand --is-inside-work-tree it will simply echo
7624                  * the option else either "true" or "false" is printed.
7625                  * Default to true for the unknown case. */
7626                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7628         } else if (*name == '.') {
7629                 string_ncopy(opt_cdup, name, namelen);
7631         } else {
7632                 string_ncopy(opt_prefix, name, namelen);
7633         }
7635         return OK;
7638 static int
7639 load_repo_info(void)
7641         const char *rev_parse_argv[] = {
7642                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7643                         "--show-cdup", "--show-prefix", NULL
7644         };
7646         return io_run_load(rev_parse_argv, "=", read_repo_info);
7650 /*
7651  * Main
7652  */
7654 static const char usage[] =
7655 "tig " TIG_VERSION " (" __DATE__ ")\n"
7656 "\n"
7657 "Usage: tig        [options] [revs] [--] [paths]\n"
7658 "   or: tig show   [options] [revs] [--] [paths]\n"
7659 "   or: tig blame  [rev] path\n"
7660 "   or: tig status\n"
7661 "   or: tig <      [git command output]\n"
7662 "\n"
7663 "Options:\n"
7664 "  -v, --version   Show version and exit\n"
7665 "  -h, --help      Show help message and exit";
7667 static void __NORETURN
7668 quit(int sig)
7670         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7671         if (cursed)
7672                 endwin();
7673         exit(0);
7676 static void __NORETURN
7677 die(const char *err, ...)
7679         va_list args;
7681         endwin();
7683         va_start(args, err);
7684         fputs("tig: ", stderr);
7685         vfprintf(stderr, err, args);
7686         fputs("\n", stderr);
7687         va_end(args);
7689         exit(1);
7692 static void
7693 warn(const char *msg, ...)
7695         va_list args;
7697         va_start(args, msg);
7698         fputs("tig warning: ", stderr);
7699         vfprintf(stderr, msg, args);
7700         fputs("\n", stderr);
7701         va_end(args);
7704 static enum request
7705 parse_options(int argc, const char *argv[])
7707         enum request request = REQ_VIEW_MAIN;
7708         const char *subcommand;
7709         bool seen_dashdash = FALSE;
7710         /* XXX: This is vulnerable to the user overriding options
7711          * required for the main view parser. */
7712         const char *custom_argv[SIZEOF_ARG] = {
7713                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7714                         "--topo-order", NULL
7715         };
7716         int i, j = 6;
7718         if (!isatty(STDIN_FILENO)) {
7719                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7720                 return REQ_VIEW_PAGER;
7721         }
7723         if (argc <= 1)
7724                 return REQ_NONE;
7726         subcommand = argv[1];
7727         if (!strcmp(subcommand, "status")) {
7728                 if (argc > 2)
7729                         warn("ignoring arguments after `%s'", subcommand);
7730                 return REQ_VIEW_STATUS;
7732         } else if (!strcmp(subcommand, "blame")) {
7733                 if (argc <= 2 || argc > 4)
7734                         die("invalid number of options to blame\n\n%s", usage);
7736                 i = 2;
7737                 if (argc == 4) {
7738                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7739                         i++;
7740                 }
7742                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7743                 return REQ_VIEW_BLAME;
7745         } else if (!strcmp(subcommand, "show")) {
7746                 request = REQ_VIEW_DIFF;
7748         } else {
7749                 subcommand = NULL;
7750         }
7752         if (subcommand) {
7753                 custom_argv[1] = subcommand;
7754                 j = 2;
7755         }
7757         for (i = 1 + !!subcommand; i < argc; i++) {
7758                 const char *opt = argv[i];
7760                 if (seen_dashdash || !strcmp(opt, "--")) {
7761                         seen_dashdash = TRUE;
7763                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7764                         printf("tig version %s\n", TIG_VERSION);
7765                         quit(0);
7767                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7768                         printf("%s\n", usage);
7769                         quit(0);
7770                 }
7772                 custom_argv[j++] = opt;
7773                 if (j >= ARRAY_SIZE(custom_argv))
7774                         die("command too long");
7775         }
7777         if (!prepare_update(VIEW(request), custom_argv, NULL))
7778                 die("Failed to format arguments");
7780         return request;
7783 int
7784 main(int argc, const char *argv[])
7786         const char *codeset = "UTF-8";
7787         enum request request = parse_options(argc, argv);
7788         struct view *view;
7789         size_t i;
7791         signal(SIGINT, quit);
7792         signal(SIGPIPE, SIG_IGN);
7794         if (setlocale(LC_ALL, "")) {
7795                 codeset = nl_langinfo(CODESET);
7796         }
7798         if (load_repo_info() == ERR)
7799                 die("Failed to load repo info.");
7801         if (load_options() == ERR)
7802                 die("Failed to load user config.");
7804         if (load_git_config() == ERR)
7805                 die("Failed to load repo config.");
7807         /* Require a git repository unless when running in pager mode. */
7808         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7809                 die("Not a git repository");
7811         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7812                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7813                 if (opt_iconv_in == ICONV_NONE)
7814                         die("Failed to initialize character set conversion");
7815         }
7817         if (codeset && strcmp(codeset, "UTF-8")) {
7818                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7819                 if (opt_iconv_out == ICONV_NONE)
7820                         die("Failed to initialize character set conversion");
7821         }
7823         if (load_refs() == ERR)
7824                 die("Failed to load refs.");
7826         foreach_view (view, i)
7827                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7828                         die("Too many arguments in the `%s` environment variable",
7829                             view->cmd_env);
7831         init_display();
7833         if (request != REQ_NONE)
7834                 open_view(NULL, request, OPEN_PREPARED);
7835         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7837         while (view_driver(display[current_view], request)) {
7838                 int key = get_input(0);
7840                 view = display[current_view];
7841                 request = get_keybinding(view->keymap, key);
7843                 /* Some low-level request handling. This keeps access to
7844                  * status_win restricted. */
7845                 switch (request) {
7846                 case REQ_NONE:
7847                         report("Unknown key, press %s for help",
7848                                get_key(view->keymap, REQ_VIEW_HELP));
7849                         break;
7850                 case REQ_PROMPT:
7851                 {
7852                         char *cmd = read_prompt(":");
7854                         if (cmd && isdigit(*cmd)) {
7855                                 int lineno = view->lineno + 1;
7857                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7858                                         select_view_line(view, lineno - 1);
7859                                         report("");
7860                                 } else {
7861                                         report("Unable to parse '%s' as a line number", cmd);
7862                                 }
7864                         } else if (cmd) {
7865                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7866                                 const char *argv[SIZEOF_ARG] = { "git" };
7867                                 int argc = 1;
7869                                 /* When running random commands, initially show the
7870                                  * command in the title. However, it maybe later be
7871                                  * overwritten if a commit line is selected. */
7872                                 string_ncopy(next->ref, cmd, strlen(cmd));
7874                                 if (!argv_from_string(argv, &argc, cmd)) {
7875                                         report("Too many arguments");
7876                                 } else if (!prepare_update(next, argv, NULL)) {
7877                                         report("Failed to format command");
7878                                 } else {
7879                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7880                                 }
7881                         }
7883                         request = REQ_NONE;
7884                         break;
7885                 }
7886                 case REQ_SEARCH:
7887                 case REQ_SEARCH_BACK:
7888                 {
7889                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7890                         char *search = read_prompt(prompt);
7892                         if (search)
7893                                 string_ncopy(opt_search, search, strlen(search));
7894                         else if (*opt_search)
7895                                 request = request == REQ_SEARCH ?
7896                                         REQ_FIND_NEXT :
7897                                         REQ_FIND_PREV;
7898                         else
7899                                 request = REQ_NONE;
7900                         break;
7901                 }
7902                 default:
7903                         break;
7904                 }
7905         }
7907         quit(0);
7909         return 0;