Code

argv: move report call to format_arg method
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int head:1;    /* Is it the current HEAD? */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int ltag:1;    /* If so, is the tag local? */
127         unsigned int remote:1;  /* Is it a remote ref? */
128         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129         char name[1];           /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         size_t size;            /* Number of refs. */
135         struct ref **refs;      /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152         INPUT_OK,
153         INPUT_SKIP,
154         INPUT_STOP,
155         INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164         int hotkey;
165         const char *text;
166         void *data;
167 };
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 /*
172  * Allocation helpers ... Entering macro hell to never be seen again.
173  */
175 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
176 static type *                                                                   \
177 name(type **mem, size_t size, size_t increase)                                  \
178 {                                                                               \
179         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
180         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181         type *tmp = *mem;                                                       \
182                                                                                 \
183         if (mem == NULL || num_chunks != num_chunks_new) {                      \
184                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185                 if (tmp)                                                        \
186                         *mem = tmp;                                             \
187         }                                                                       \
188                                                                                 \
189         return tmp;                                                             \
192 /*
193  * String helpers
194  */
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199         if (srclen > dstlen - 1)
200                 srclen = dstlen - 1;
202         strncpy(dst, src, srclen);
203         dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212         string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223         size_t size, pos;
225         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226                 if (src[pos] == '\t') {
227                         size_t expanded = tabsize - (size % tabsize);
229                         if (expanded + size >= dstlen - 1)
230                                 expanded = dstlen - size - 1;
231                         memcpy(dst + size, "        ", expanded);
232                         size += expanded;
233                 } else {
234                         dst[size++] = src[pos];
235                 }
236         }
238         dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244         int namelen;
246         while (isspace(*name))
247                 name++;
249         namelen = strlen(name) - 1;
250         while (namelen > 0 && isspace(name[namelen]))
251                 name[namelen--] = 0;
253         return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259         va_list args;
260         size_t pos = bufpos ? *bufpos : 0;
262         va_start(args, fmt);
263         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264         va_end(args);
266         if (bufpos)
267                 *bufpos = pos;
269         return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273         string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276         string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281         size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285         /* Diff-Header == DIFF_HEADER */
286         for (i = 0; i < len; i++) {
287                 if (toupper(str1[i]) == toupper(str2[i]))
288                         continue;
290                 if (string_enum_sep(str1[i]) &&
291                     string_enum_sep(str2[i]))
292                         continue;
294                 return str1[i] - str2[i];
295         }
297         return 0;
300 #define enum_equals(entry, str, len) \
301         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
303 struct enum_map {
304         const char *name;
305         int namelen;
306         int value;
307 };
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static char *
312 enum_map_name(const char *name, size_t namelen)
314         static char buf[SIZEOF_STR];
315         int bufpos;
317         for (bufpos = 0; bufpos <= namelen; bufpos++) {
318                 buf[bufpos] = tolower(name[bufpos]);
319                 if (buf[bufpos] == '_')
320                         buf[bufpos] = '-';
321         }
323         buf[bufpos] = 0;
324         return buf;
327 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
329 static bool
330 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
332         size_t namelen = strlen(name);
333         int i;
335         for (i = 0; i < map_size; i++)
336                 if (enum_equals(map[i], name, namelen)) {
337                         *value = map[i].value;
338                         return TRUE;
339                 }
341         return FALSE;
344 #define map_enum(attr, map, name) \
345         map_enum_do(map, ARRAY_SIZE(map), attr, name)
347 #define prefixcmp(str1, str2) \
348         strncmp(str1, str2, STRING_SIZE(str2))
350 static inline int
351 suffixcmp(const char *str, int slen, const char *suffix)
353         size_t len = slen >= 0 ? slen : strlen(str);
354         size_t suffixlen = strlen(suffix);
356         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
360 /*
361  * Unicode / UTF-8 handling
362  *
363  * NOTE: Much of the following code for dealing with Unicode is derived from
364  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
365  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
366  */
368 static inline int
369 unicode_width(unsigned long c, int tab_size)
371         if (c >= 0x1100 &&
372            (c <= 0x115f                         /* Hangul Jamo */
373             || c == 0x2329
374             || c == 0x232a
375             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
376                                                 /* CJK ... Yi */
377             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
378             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
379             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
380             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
381             || (c >= 0xffe0  && c <= 0xffe6)
382             || (c >= 0x20000 && c <= 0x2fffd)
383             || (c >= 0x30000 && c <= 0x3fffd)))
384                 return 2;
386         if (c == '\t')
387                 return tab_size;
389         return 1;
392 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
393  * Illegal bytes are set one. */
394 static const unsigned char utf8_bytes[256] = {
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         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,
401         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,
402         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,
403 };
405 static inline unsigned char
406 utf8_char_length(const char *string, const char *end)
408         int c = *(unsigned char *) string;
410         return utf8_bytes[c];
413 /* Decode UTF-8 multi-byte representation into a Unicode character. */
414 static inline unsigned long
415 utf8_to_unicode(const char *string, size_t length)
417         unsigned long unicode;
419         switch (length) {
420         case 1:
421                 unicode  =   string[0];
422                 break;
423         case 2:
424                 unicode  =  (string[0] & 0x1f) << 6;
425                 unicode +=  (string[1] & 0x3f);
426                 break;
427         case 3:
428                 unicode  =  (string[0] & 0x0f) << 12;
429                 unicode += ((string[1] & 0x3f) << 6);
430                 unicode +=  (string[2] & 0x3f);
431                 break;
432         case 4:
433                 unicode  =  (string[0] & 0x0f) << 18;
434                 unicode += ((string[1] & 0x3f) << 12);
435                 unicode += ((string[2] & 0x3f) << 6);
436                 unicode +=  (string[3] & 0x3f);
437                 break;
438         case 5:
439                 unicode  =  (string[0] & 0x0f) << 24;
440                 unicode += ((string[1] & 0x3f) << 18);
441                 unicode += ((string[2] & 0x3f) << 12);
442                 unicode += ((string[3] & 0x3f) << 6);
443                 unicode +=  (string[4] & 0x3f);
444                 break;
445         case 6:
446                 unicode  =  (string[0] & 0x01) << 30;
447                 unicode += ((string[1] & 0x3f) << 24);
448                 unicode += ((string[2] & 0x3f) << 18);
449                 unicode += ((string[3] & 0x3f) << 12);
450                 unicode += ((string[4] & 0x3f) << 6);
451                 unicode +=  (string[5] & 0x3f);
452                 break;
453         default:
454                 return 0;
455         }
457         /* Invalid characters could return the special 0xfffd value but NUL
458          * should be just as good. */
459         return unicode > 0xffff ? 0 : unicode;
462 /* Calculates how much of string can be shown within the given maximum width
463  * and sets trimmed parameter to non-zero value if all of string could not be
464  * shown. If the reserve flag is TRUE, it will reserve at least one
465  * trailing character, which can be useful when drawing a delimiter.
466  *
467  * Returns the number of bytes to output from string to satisfy max_width. */
468 static size_t
469 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
471         const char *string = *start;
472         const char *end = strchr(string, '\0');
473         unsigned char last_bytes = 0;
474         size_t last_ucwidth = 0;
476         *width = 0;
477         *trimmed = 0;
479         while (string < end) {
480                 unsigned char bytes = utf8_char_length(string, end);
481                 size_t ucwidth;
482                 unsigned long unicode;
484                 if (string + bytes > end)
485                         break;
487                 /* Change representation to figure out whether
488                  * it is a single- or double-width character. */
490                 unicode = utf8_to_unicode(string, bytes);
491                 /* FIXME: Graceful handling of invalid Unicode character. */
492                 if (!unicode)
493                         break;
495                 ucwidth = unicode_width(unicode, tab_size);
496                 if (skip > 0) {
497                         skip -= ucwidth <= skip ? ucwidth : skip;
498                         *start += bytes;
499                 }
500                 *width  += ucwidth;
501                 if (*width > max_width) {
502                         *trimmed = 1;
503                         *width -= ucwidth;
504                         if (reserve && *width == max_width) {
505                                 string -= last_bytes;
506                                 *width -= last_ucwidth;
507                         }
508                         break;
509                 }
511                 string  += bytes;
512                 last_bytes = ucwidth ? bytes : 0;
513                 last_ucwidth = ucwidth;
514         }
516         return string - *start;
520 #define DATE_INFO \
521         DATE_(NO), \
522         DATE_(DEFAULT), \
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         gmtime_r(&time->sec, &tm);
588         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
592 #define AUTHOR_VALUES \
593         AUTHOR_(NO), \
594         AUTHOR_(FULL), \
595         AUTHOR_(ABBREVIATED)
597 enum author {
598 #define AUTHOR_(name) AUTHOR_##name
599         AUTHOR_VALUES,
600 #undef  AUTHOR_
601         AUTHOR_DEFAULT = AUTHOR_FULL
602 };
604 static const struct enum_map author_map[] = {
605 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
606         AUTHOR_VALUES
607 #undef  AUTHOR_
608 };
610 static const char *
611 get_author_initials(const char *author)
613         static char initials[AUTHOR_COLS * 6 + 1];
614         size_t pos = 0;
615         const char *end = strchr(author, '\0');
617 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
619         memset(initials, 0, sizeof(initials));
620         while (author < end) {
621                 unsigned char bytes;
622                 size_t i;
624                 while (is_initial_sep(*author))
625                         author++;
627                 bytes = utf8_char_length(author, end);
628                 if (bytes < sizeof(initials) - 1 - pos) {
629                         while (bytes--) {
630                                 initials[pos++] = *author++;
631                         }
632                 }
634                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
635                         if (i < sizeof(initials) - 1)
636                                 initials[i++] = *author;
637                 }
639                 initials[i++] = 0;
640         }
642         return initials;
646 static bool
647 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
649         int valuelen;
651         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
652                 bool advance = cmd[valuelen] != 0;
654                 cmd[valuelen] = 0;
655                 argv[(*argc)++] = chomp_string(cmd);
656                 cmd = chomp_string(cmd + valuelen + advance);
657         }
659         if (*argc < SIZEOF_ARG)
660                 argv[*argc] = NULL;
661         return *argc < SIZEOF_ARG;
664 static bool
665 argv_from_env(const char **argv, const char *name)
667         char *env = argv ? getenv(name) : NULL;
668         int argc = 0;
670         if (env && *env)
671                 env = strdup(env);
672         return !env || argv_from_string(argv, &argc, env);
676 /*
677  * Executing external commands.
678  */
680 enum io_type {
681         IO_FD,                  /* File descriptor based IO. */
682         IO_BG,                  /* Execute command in the background. */
683         IO_FG,                  /* Execute command with same std{in,out,err}. */
684         IO_RD,                  /* Read only fork+exec IO. */
685         IO_WR,                  /* Write only fork+exec IO. */
686         IO_AP,                  /* Append fork+exec output to file. */
687 };
689 struct io {
690         enum io_type type;      /* The requested type of pipe. */
691         const char *dir;        /* Directory from which to execute. */
692         pid_t pid;              /* PID of spawned process. */
693         int pipe;               /* Pipe end for reading or writing. */
694         int error;              /* Error status. */
695         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
696         char *buf;              /* Read buffer. */
697         size_t bufalloc;        /* Allocated buffer size. */
698         size_t bufsize;         /* Buffer content size. */
699         char *bufpos;           /* Current buffer position. */
700         unsigned int eof:1;     /* Has end of file been reached. */
701 };
703 static void
704 io_reset(struct io *io)
706         io->pipe = -1;
707         io->pid = 0;
708         io->buf = io->bufpos = NULL;
709         io->bufalloc = io->bufsize = 0;
710         io->error = 0;
711         io->eof = 0;
714 static void
715 io_init(struct io *io, const char *dir, enum io_type type)
717         io_reset(io);
718         io->type = type;
719         io->dir = dir;
722 static bool
723 io_format(struct io *io, const char *dir, enum io_type type,
724           const char *argv[], enum format_flags flags)
726         io_init(io, dir, type);
727         return format_argv(io->argv, argv, flags);
730 static bool
731 io_open(struct io *io, const char *fmt, ...)
733         char name[SIZEOF_STR] = "";
734         bool fits;
735         va_list args;
737         io_init(io, NULL, IO_FD);
739         va_start(args, fmt);
740         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
741         va_end(args);
743         if (!fits) {
744                 io->error = ENAMETOOLONG;
745                 return FALSE;
746         }
747         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
748         if (io->pipe == -1)
749                 io->error = errno;
750         return io->pipe != -1;
753 static bool
754 io_kill(struct io *io)
756         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
759 static bool
760 io_done(struct io *io)
762         pid_t pid = io->pid;
764         if (io->pipe != -1)
765                 close(io->pipe);
766         free(io->buf);
767         io_reset(io);
769         while (pid > 0) {
770                 int status;
771                 pid_t waiting = waitpid(pid, &status, 0);
773                 if (waiting < 0) {
774                         if (errno == EINTR)
775                                 continue;
776                         io->error = errno;
777                         return FALSE;
778                 }
780                 return waiting == pid &&
781                        !WIFSIGNALED(status) &&
782                        WIFEXITED(status) &&
783                        !WEXITSTATUS(status);
784         }
786         return TRUE;
789 static bool
790 io_start(struct io *io)
792         int pipefds[2] = { -1, -1 };
794         if (io->type == IO_FD)
795                 return TRUE;
797         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
798                 io->error = errno;
799                 return FALSE;
800         } else if (io->type == IO_AP) {
801                 pipefds[1] = io->pipe;
802         }
804         if ((io->pid = fork())) {
805                 if (io->pid == -1)
806                         io->error = errno;
807                 if (pipefds[!(io->type == IO_WR)] != -1)
808                         close(pipefds[!(io->type == IO_WR)]);
809                 if (io->pid != -1) {
810                         io->pipe = pipefds[!!(io->type == IO_WR)];
811                         return TRUE;
812                 }
814         } else {
815                 if (io->type != IO_FG) {
816                         int devnull = open("/dev/null", O_RDWR);
817                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
818                         int writefd = (io->type == IO_RD || io->type == IO_AP)
819                                                         ? pipefds[1] : devnull;
821                         dup2(readfd,  STDIN_FILENO);
822                         dup2(writefd, STDOUT_FILENO);
823                         dup2(devnull, STDERR_FILENO);
825                         close(devnull);
826                         if (pipefds[0] != -1)
827                                 close(pipefds[0]);
828                         if (pipefds[1] != -1)
829                                 close(pipefds[1]);
830                 }
832                 if (io->dir && *io->dir && chdir(io->dir) == -1)
833                         exit(errno);
835                 execvp(io->argv[0], (char *const*) io->argv);
836                 exit(errno);
837         }
839         if (pipefds[!!(io->type == IO_WR)] != -1)
840                 close(pipefds[!!(io->type == IO_WR)]);
841         return FALSE;
844 static bool
845 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
847         io_init(io, dir, type);
848         if (!format_argv(io->argv, argv, FORMAT_NONE))
849                 return FALSE;
850         return io_start(io);
853 static int
854 io_complete(struct io *io)
856         return io_start(io) && io_done(io);
859 static int
860 io_run_bg(const char **argv)
862         struct io io = {};
864         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
865                 return FALSE;
866         return io_complete(&io);
869 static bool
870 io_run_fg(const char **argv, const char *dir)
872         struct io io = {};
874         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
875                 return FALSE;
876         return io_complete(&io);
879 static bool
880 io_run_append(const char **argv, enum format_flags flags, int fd)
882         struct io io = {};
884         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
885                 close(fd);
886                 return FALSE;
887         }
889         io.pipe = fd;
890         return io_complete(&io);
893 static bool
894 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
896         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
899 static bool
900 io_eof(struct io *io)
902         return io->eof;
905 static int
906 io_error(struct io *io)
908         return io->error;
911 static char *
912 io_strerror(struct io *io)
914         return strerror(io->error);
917 static bool
918 io_can_read(struct io *io)
920         struct timeval tv = { 0, 500 };
921         fd_set fds;
923         FD_ZERO(&fds);
924         FD_SET(io->pipe, &fds);
926         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
929 static ssize_t
930 io_read(struct io *io, void *buf, size_t bufsize)
932         do {
933                 ssize_t readsize = read(io->pipe, buf, bufsize);
935                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
936                         continue;
937                 else if (readsize == -1)
938                         io->error = errno;
939                 else if (readsize == 0)
940                         io->eof = 1;
941                 return readsize;
942         } while (1);
945 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
947 static char *
948 io_get(struct io *io, int c, bool can_read)
950         char *eol;
951         ssize_t readsize;
953         while (TRUE) {
954                 if (io->bufsize > 0) {
955                         eol = memchr(io->bufpos, c, io->bufsize);
956                         if (eol) {
957                                 char *line = io->bufpos;
959                                 *eol = 0;
960                                 io->bufpos = eol + 1;
961                                 io->bufsize -= io->bufpos - line;
962                                 return line;
963                         }
964                 }
966                 if (io_eof(io)) {
967                         if (io->bufsize) {
968                                 io->bufpos[io->bufsize] = 0;
969                                 io->bufsize = 0;
970                                 return io->bufpos;
971                         }
972                         return NULL;
973                 }
975                 if (!can_read)
976                         return NULL;
978                 if (io->bufsize > 0 && io->bufpos > io->buf)
979                         memmove(io->buf, io->bufpos, io->bufsize);
981                 if (io->bufalloc == io->bufsize) {
982                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
983                                 return NULL;
984                         io->bufalloc += BUFSIZ;
985                 }
987                 io->bufpos = io->buf;
988                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
989                 if (io_error(io))
990                         return NULL;
991                 io->bufsize += readsize;
992         }
995 static bool
996 io_write(struct io *io, const void *buf, size_t bufsize)
998         size_t written = 0;
1000         while (!io_error(io) && written < bufsize) {
1001                 ssize_t size;
1003                 size = write(io->pipe, buf + written, bufsize - written);
1004                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1005                         continue;
1006                 else if (size == -1)
1007                         io->error = errno;
1008                 else
1009                         written += size;
1010         }
1012         return written == bufsize;
1015 static bool
1016 io_read_buf(struct io *io, char buf[], size_t bufsize)
1018         char *result = io_get(io, '\n', TRUE);
1020         if (result) {
1021                 result = chomp_string(result);
1022                 string_ncopy_do(buf, bufsize, result, strlen(result));
1023         }
1025         return io_done(io) && result;
1028 static bool
1029 io_run_buf(const char **argv, char buf[], size_t bufsize)
1031         struct io io = {};
1033         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1034             && io_read_buf(&io, buf, bufsize);
1037 static int
1038 io_load(struct io *io, const char *separators,
1039         int (*read_property)(char *, size_t, char *, size_t))
1041         char *name;
1042         int state = OK;
1044         if (!io_start(io))
1045                 return ERR;
1047         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1048                 char *value;
1049                 size_t namelen;
1050                 size_t valuelen;
1052                 name = chomp_string(name);
1053                 namelen = strcspn(name, separators);
1055                 if (name[namelen]) {
1056                         name[namelen] = 0;
1057                         value = chomp_string(name + namelen + 1);
1058                         valuelen = strlen(value);
1060                 } else {
1061                         value = "";
1062                         valuelen = 0;
1063                 }
1065                 state = read_property(name, namelen, value, valuelen);
1066         }
1068         if (state != ERR && io_error(io))
1069                 state = ERR;
1070         io_done(io);
1072         return state;
1075 static int
1076 io_run_load(const char **argv, const char *separators,
1077             int (*read_property)(char *, size_t, char *, size_t))
1079         struct io io = {};
1081         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1082                 ? io_load(&io, separators, read_property) : ERR;
1086 /*
1087  * User requests
1088  */
1090 #define REQ_INFO \
1091         /* XXX: Keep the view request first and in sync with views[]. */ \
1092         REQ_GROUP("View switching") \
1093         REQ_(VIEW_MAIN,         "Show main view"), \
1094         REQ_(VIEW_DIFF,         "Show diff view"), \
1095         REQ_(VIEW_LOG,          "Show log view"), \
1096         REQ_(VIEW_TREE,         "Show tree view"), \
1097         REQ_(VIEW_BLOB,         "Show blob view"), \
1098         REQ_(VIEW_BLAME,        "Show blame view"), \
1099         REQ_(VIEW_BRANCH,       "Show branch view"), \
1100         REQ_(VIEW_HELP,         "Show help page"), \
1101         REQ_(VIEW_PAGER,        "Show pager view"), \
1102         REQ_(VIEW_STATUS,       "Show status view"), \
1103         REQ_(VIEW_STAGE,        "Show stage view"), \
1104         \
1105         REQ_GROUP("View manipulation") \
1106         REQ_(ENTER,             "Enter current line and scroll"), \
1107         REQ_(NEXT,              "Move to next"), \
1108         REQ_(PREVIOUS,          "Move to previous"), \
1109         REQ_(PARENT,            "Move to parent"), \
1110         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1111         REQ_(REFRESH,           "Reload and refresh"), \
1112         REQ_(MAXIMIZE,          "Maximize the current view"), \
1113         REQ_(VIEW_CLOSE,        "Close the current view"), \
1114         REQ_(QUIT,              "Close all views and quit"), \
1115         \
1116         REQ_GROUP("View specific requests") \
1117         REQ_(STATUS_UPDATE,     "Update file status"), \
1118         REQ_(STATUS_REVERT,     "Revert file changes"), \
1119         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1120         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1121         \
1122         REQ_GROUP("Cursor navigation") \
1123         REQ_(MOVE_UP,           "Move cursor one line up"), \
1124         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1125         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1126         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1127         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1128         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1129         \
1130         REQ_GROUP("Scrolling") \
1131         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1132         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1133         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1134         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1135         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1136         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1137         \
1138         REQ_GROUP("Searching") \
1139         REQ_(SEARCH,            "Search the view"), \
1140         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1141         REQ_(FIND_NEXT,         "Find next search match"), \
1142         REQ_(FIND_PREV,         "Find previous search match"), \
1143         \
1144         REQ_GROUP("Option manipulation") \
1145         REQ_(OPTIONS,           "Open option menu"), \
1146         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1147         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1148         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1149         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1150         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1151         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1152         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1153         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1154         \
1155         REQ_GROUP("Misc") \
1156         REQ_(PROMPT,            "Bring up the prompt"), \
1157         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1158         REQ_(SHOW_VERSION,      "Show version information"), \
1159         REQ_(STOP_LOADING,      "Stop all loading views"), \
1160         REQ_(EDIT,              "Open in editor"), \
1161         REQ_(NONE,              "Do nothing")
1164 /* User action requests. */
1165 enum request {
1166 #define REQ_GROUP(help)
1167 #define REQ_(req, help) REQ_##req
1169         /* Offset all requests to avoid conflicts with ncurses getch values. */
1170         REQ_OFFSET = KEY_MAX + 1,
1171         REQ_INFO
1173 #undef  REQ_GROUP
1174 #undef  REQ_
1175 };
1177 struct request_info {
1178         enum request request;
1179         const char *name;
1180         int namelen;
1181         const char *help;
1182 };
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187         REQ_INFO
1188 #undef  REQ_GROUP
1189 #undef  REQ_
1190 };
1192 static enum request
1193 get_request(const char *name)
1195         int namelen = strlen(name);
1196         int i;
1198         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199                 if (enum_equals(req_info[i], name, namelen))
1200                         return req_info[i].request;
1202         return REQ_NONE;
1206 /*
1207  * Options
1208  */
1210 /* Option and state variables. */
1211 static enum date opt_date               = DATE_DEFAULT;
1212 static enum author opt_author           = AUTHOR_DEFAULT;
1213 static bool opt_line_number             = FALSE;
1214 static bool opt_line_graphics           = TRUE;
1215 static bool opt_rev_graph               = FALSE;
1216 static bool opt_show_refs               = TRUE;
1217 static int opt_num_interval             = 5;
1218 static double opt_hscroll               = 0.50;
1219 static double opt_scale_split_view      = 2.0 / 3.0;
1220 static int opt_tab_size                 = 8;
1221 static int opt_author_cols              = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR]        = "";
1223 static char opt_file[SIZEOF_STR]        = "";
1224 static char opt_ref[SIZEOF_REF]         = "";
1225 static char opt_head[SIZEOF_REF]        = "";
1226 static char opt_remote[SIZEOF_REF]      = "";
1227 static char opt_encoding[20]            = "UTF-8";
1228 static iconv_t opt_iconv_in             = ICONV_NONE;
1229 static iconv_t opt_iconv_out            = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR]      = "";
1231 static char opt_cdup[SIZEOF_STR]        = "";
1232 static char opt_prefix[SIZEOF_STR]      = "";
1233 static char opt_git_dir[SIZEOF_STR]     = "";
1234 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR]      = "";
1236 static FILE *opt_tty                    = NULL;
1238 #define is_initial_commit()     (!get_ref_head())
1239 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1242 /*
1243  * Line-oriented content detection.
1244  */
1246 #define LINE_INFO \
1247 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1248 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1249 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1250 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1251 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1252 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1253 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1254 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1255 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1261 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1262 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1263 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1264 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1265 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1266 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1267 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1268 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1269 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1270 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1271 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1272 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1273 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1275 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1276 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1277 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1278 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1279 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1280 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1281 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1282 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1283 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1284 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1285 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1286 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1287 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1288 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1289 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1290 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1291 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1292 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1293 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1294 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1295 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1296 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1297 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1298 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1299 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1301 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1302 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1303 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1305 enum line_type {
1306 #define LINE(type, line, fg, bg, attr) \
1307         LINE_##type
1308         LINE_INFO,
1309         LINE_NONE
1310 #undef  LINE
1311 };
1313 struct line_info {
1314         const char *name;       /* Option name. */
1315         int namelen;            /* Size of option name. */
1316         const char *line;       /* The start of line to match. */
1317         int linelen;            /* Size of string to match. */
1318         int fg, bg, attr;       /* Color and text attributes for the lines. */
1319 };
1321 static struct line_info line_info[] = {
1322 #define LINE(type, line, fg, bg, attr) \
1323         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1324         LINE_INFO
1325 #undef  LINE
1326 };
1328 static enum line_type
1329 get_line_type(const char *line)
1331         int linelen = strlen(line);
1332         enum line_type type;
1334         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1335                 /* Case insensitive search matches Signed-off-by lines better. */
1336                 if (linelen >= line_info[type].linelen &&
1337                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1338                         return type;
1340         return LINE_DEFAULT;
1343 static inline int
1344 get_line_attr(enum line_type type)
1346         assert(type < ARRAY_SIZE(line_info));
1347         return COLOR_PAIR(type) | line_info[type].attr;
1350 static struct line_info *
1351 get_line_info(const char *name)
1353         size_t namelen = strlen(name);
1354         enum line_type type;
1356         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1357                 if (enum_equals(line_info[type], name, namelen))
1358                         return &line_info[type];
1360         return NULL;
1363 static void
1364 init_colors(void)
1366         int default_bg = line_info[LINE_DEFAULT].bg;
1367         int default_fg = line_info[LINE_DEFAULT].fg;
1368         enum line_type type;
1370         start_color();
1372         if (assume_default_colors(default_fg, default_bg) == ERR) {
1373                 default_bg = COLOR_BLACK;
1374                 default_fg = COLOR_WHITE;
1375         }
1377         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1378                 struct line_info *info = &line_info[type];
1379                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1380                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1382                 init_pair(type, fg, bg);
1383         }
1386 struct line {
1387         enum line_type type;
1389         /* State flags */
1390         unsigned int selected:1;
1391         unsigned int dirty:1;
1392         unsigned int cleareol:1;
1393         unsigned int other:16;
1395         void *data;             /* User data */
1396 };
1399 /*
1400  * Keys
1401  */
1403 struct keybinding {
1404         int alias;
1405         enum request request;
1406 };
1408 static const struct keybinding default_keybindings[] = {
1409         /* View switching */
1410         { 'm',          REQ_VIEW_MAIN },
1411         { 'd',          REQ_VIEW_DIFF },
1412         { 'l',          REQ_VIEW_LOG },
1413         { 't',          REQ_VIEW_TREE },
1414         { 'f',          REQ_VIEW_BLOB },
1415         { 'B',          REQ_VIEW_BLAME },
1416         { 'H',          REQ_VIEW_BRANCH },
1417         { 'p',          REQ_VIEW_PAGER },
1418         { 'h',          REQ_VIEW_HELP },
1419         { 'S',          REQ_VIEW_STATUS },
1420         { 'c',          REQ_VIEW_STAGE },
1422         /* View manipulation */
1423         { 'q',          REQ_VIEW_CLOSE },
1424         { KEY_TAB,      REQ_VIEW_NEXT },
1425         { KEY_RETURN,   REQ_ENTER },
1426         { KEY_UP,       REQ_PREVIOUS },
1427         { KEY_DOWN,     REQ_NEXT },
1428         { 'R',          REQ_REFRESH },
1429         { KEY_F(5),     REQ_REFRESH },
1430         { 'O',          REQ_MAXIMIZE },
1432         /* Cursor navigation */
1433         { 'k',          REQ_MOVE_UP },
1434         { 'j',          REQ_MOVE_DOWN },
1435         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1436         { KEY_END,      REQ_MOVE_LAST_LINE },
1437         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1438         { ' ',          REQ_MOVE_PAGE_DOWN },
1439         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1440         { 'b',          REQ_MOVE_PAGE_UP },
1441         { '-',          REQ_MOVE_PAGE_UP },
1443         /* Scrolling */
1444         { KEY_LEFT,     REQ_SCROLL_LEFT },
1445         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1446         { KEY_IC,       REQ_SCROLL_LINE_UP },
1447         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1448         { 'w',          REQ_SCROLL_PAGE_UP },
1449         { 's',          REQ_SCROLL_PAGE_DOWN },
1451         /* Searching */
1452         { '/',          REQ_SEARCH },
1453         { '?',          REQ_SEARCH_BACK },
1454         { 'n',          REQ_FIND_NEXT },
1455         { 'N',          REQ_FIND_PREV },
1457         /* Misc */
1458         { 'Q',          REQ_QUIT },
1459         { 'z',          REQ_STOP_LOADING },
1460         { 'v',          REQ_SHOW_VERSION },
1461         { 'r',          REQ_SCREEN_REDRAW },
1462         { 'o',          REQ_OPTIONS },
1463         { '.',          REQ_TOGGLE_LINENO },
1464         { 'D',          REQ_TOGGLE_DATE },
1465         { 'A',          REQ_TOGGLE_AUTHOR },
1466         { 'g',          REQ_TOGGLE_REV_GRAPH },
1467         { 'F',          REQ_TOGGLE_REFS },
1468         { 'I',          REQ_TOGGLE_SORT_ORDER },
1469         { 'i',          REQ_TOGGLE_SORT_FIELD },
1470         { ':',          REQ_PROMPT },
1471         { 'u',          REQ_STATUS_UPDATE },
1472         { '!',          REQ_STATUS_REVERT },
1473         { 'M',          REQ_STATUS_MERGE },
1474         { '@',          REQ_STAGE_NEXT },
1475         { ',',          REQ_PARENT },
1476         { 'e',          REQ_EDIT },
1477 };
1479 #define KEYMAP_INFO \
1480         KEYMAP_(GENERIC), \
1481         KEYMAP_(MAIN), \
1482         KEYMAP_(DIFF), \
1483         KEYMAP_(LOG), \
1484         KEYMAP_(TREE), \
1485         KEYMAP_(BLOB), \
1486         KEYMAP_(BLAME), \
1487         KEYMAP_(BRANCH), \
1488         KEYMAP_(PAGER), \
1489         KEYMAP_(HELP), \
1490         KEYMAP_(STATUS), \
1491         KEYMAP_(STAGE)
1493 enum keymap {
1494 #define KEYMAP_(name) KEYMAP_##name
1495         KEYMAP_INFO
1496 #undef  KEYMAP_
1497 };
1499 static const struct enum_map keymap_table[] = {
1500 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1501         KEYMAP_INFO
1502 #undef  KEYMAP_
1503 };
1505 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1507 struct keybinding_table {
1508         struct keybinding *data;
1509         size_t size;
1510 };
1512 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1514 static void
1515 add_keybinding(enum keymap keymap, enum request request, int key)
1517         struct keybinding_table *table = &keybindings[keymap];
1519         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1520         if (!table->data)
1521                 die("Failed to allocate keybinding");
1522         table->data[table->size].alias = key;
1523         table->data[table->size++].request = request;
1526 /* Looks for a key binding first in the given map, then in the generic map, and
1527  * lastly in the default keybindings. */
1528 static enum request
1529 get_keybinding(enum keymap keymap, int key)
1531         size_t i;
1533         for (i = 0; i < keybindings[keymap].size; i++)
1534                 if (keybindings[keymap].data[i].alias == key)
1535                         return keybindings[keymap].data[i].request;
1537         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1538                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1539                         return keybindings[KEYMAP_GENERIC].data[i].request;
1541         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1542                 if (default_keybindings[i].alias == key)
1543                         return default_keybindings[i].request;
1545         return (enum request) key;
1549 struct key {
1550         const char *name;
1551         int value;
1552 };
1554 static const struct key key_table[] = {
1555         { "Enter",      KEY_RETURN },
1556         { "Space",      ' ' },
1557         { "Backspace",  KEY_BACKSPACE },
1558         { "Tab",        KEY_TAB },
1559         { "Escape",     KEY_ESC },
1560         { "Left",       KEY_LEFT },
1561         { "Right",      KEY_RIGHT },
1562         { "Up",         KEY_UP },
1563         { "Down",       KEY_DOWN },
1564         { "Insert",     KEY_IC },
1565         { "Delete",     KEY_DC },
1566         { "Hash",       '#' },
1567         { "Home",       KEY_HOME },
1568         { "End",        KEY_END },
1569         { "PageUp",     KEY_PPAGE },
1570         { "PageDown",   KEY_NPAGE },
1571         { "F1",         KEY_F(1) },
1572         { "F2",         KEY_F(2) },
1573         { "F3",         KEY_F(3) },
1574         { "F4",         KEY_F(4) },
1575         { "F5",         KEY_F(5) },
1576         { "F6",         KEY_F(6) },
1577         { "F7",         KEY_F(7) },
1578         { "F8",         KEY_F(8) },
1579         { "F9",         KEY_F(9) },
1580         { "F10",        KEY_F(10) },
1581         { "F11",        KEY_F(11) },
1582         { "F12",        KEY_F(12) },
1583 };
1585 static int
1586 get_key_value(const char *name)
1588         int i;
1590         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1591                 if (!strcasecmp(key_table[i].name, name))
1592                         return key_table[i].value;
1594         if (strlen(name) == 1 && isprint(*name))
1595                 return (int) *name;
1597         return ERR;
1600 static const char *
1601 get_key_name(int key_value)
1603         static char key_char[] = "'X'";
1604         const char *seq = NULL;
1605         int key;
1607         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1608                 if (key_table[key].value == key_value)
1609                         seq = key_table[key].name;
1611         if (seq == NULL &&
1612             key_value < 127 &&
1613             isprint(key_value)) {
1614                 key_char[1] = (char) key_value;
1615                 seq = key_char;
1616         }
1618         return seq ? seq : "(no key)";
1621 static bool
1622 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1624         const char *sep = *pos > 0 ? ", " : "";
1625         const char *keyname = get_key_name(keybinding->alias);
1627         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1630 static bool
1631 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1632                            enum keymap keymap, bool all)
1634         int i;
1636         for (i = 0; i < keybindings[keymap].size; i++) {
1637                 if (keybindings[keymap].data[i].request == request) {
1638                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1639                                 return FALSE;
1640                         if (!all)
1641                                 break;
1642                 }
1643         }
1645         return TRUE;
1648 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1650 static const char *
1651 get_keys(enum keymap keymap, enum request request, bool all)
1653         static char buf[BUFSIZ];
1654         size_t pos = 0;
1655         int i;
1657         buf[pos] = 0;
1659         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1660                 return "Too many keybindings!";
1661         if (pos > 0 && !all)
1662                 return buf;
1664         if (keymap != KEYMAP_GENERIC) {
1665                 /* Only the generic keymap includes the default keybindings when
1666                  * listing all keys. */
1667                 if (all)
1668                         return buf;
1670                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1671                         return "Too many keybindings!";
1672                 if (pos)
1673                         return buf;
1674         }
1676         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1677                 if (default_keybindings[i].request == request) {
1678                         if (!append_key(buf, &pos, &default_keybindings[i]))
1679                                 return "Too many keybindings!";
1680                         if (!all)
1681                                 return buf;
1682                 }
1683         }
1685         return buf;
1688 struct run_request {
1689         enum keymap keymap;
1690         int key;
1691         const char *argv[SIZEOF_ARG];
1692 };
1694 static struct run_request *run_request;
1695 static size_t run_requests;
1697 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1699 static enum request
1700 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1702         struct run_request *req;
1704         if (argc >= ARRAY_SIZE(req->argv) - 1)
1705                 return REQ_NONE;
1707         if (!realloc_run_requests(&run_request, run_requests, 1))
1708                 return REQ_NONE;
1710         req = &run_request[run_requests];
1711         req->keymap = keymap;
1712         req->key = key;
1713         req->argv[0] = NULL;
1715         if (!format_argv(req->argv, argv, FORMAT_NONE))
1716                 return REQ_NONE;
1718         return REQ_NONE + ++run_requests;
1721 static struct run_request *
1722 get_run_request(enum request request)
1724         if (request <= REQ_NONE)
1725                 return NULL;
1726         return &run_request[request - REQ_NONE - 1];
1729 static void
1730 add_builtin_run_requests(void)
1732         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1733         const char *commit[] = { "git", "commit", NULL };
1734         const char *gc[] = { "git", "gc", NULL };
1735         struct {
1736                 enum keymap keymap;
1737                 int key;
1738                 int argc;
1739                 const char **argv;
1740         } reqs[] = {
1741                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1742                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1743                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1744         };
1745         int i;
1747         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1748                 enum request req;
1750                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1751                 if (req != REQ_NONE)
1752                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1753         }
1756 /*
1757  * User config file handling.
1758  */
1760 static int   config_lineno;
1761 static bool  config_errors;
1762 static const char *config_msg;
1764 static const struct enum_map color_map[] = {
1765 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1766         COLOR_MAP(DEFAULT),
1767         COLOR_MAP(BLACK),
1768         COLOR_MAP(BLUE),
1769         COLOR_MAP(CYAN),
1770         COLOR_MAP(GREEN),
1771         COLOR_MAP(MAGENTA),
1772         COLOR_MAP(RED),
1773         COLOR_MAP(WHITE),
1774         COLOR_MAP(YELLOW),
1775 };
1777 static const struct enum_map attr_map[] = {
1778 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1779         ATTR_MAP(NORMAL),
1780         ATTR_MAP(BLINK),
1781         ATTR_MAP(BOLD),
1782         ATTR_MAP(DIM),
1783         ATTR_MAP(REVERSE),
1784         ATTR_MAP(STANDOUT),
1785         ATTR_MAP(UNDERLINE),
1786 };
1788 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1790 static int parse_step(double *opt, const char *arg)
1792         *opt = atoi(arg);
1793         if (!strchr(arg, '%'))
1794                 return OK;
1796         /* "Shift down" so 100% and 1 does not conflict. */
1797         *opt = (*opt - 1) / 100;
1798         if (*opt >= 1.0) {
1799                 *opt = 0.99;
1800                 config_msg = "Step value larger than 100%";
1801                 return ERR;
1802         }
1803         if (*opt < 0.0) {
1804                 *opt = 1;
1805                 config_msg = "Invalid step value";
1806                 return ERR;
1807         }
1808         return OK;
1811 static int
1812 parse_int(int *opt, const char *arg, int min, int max)
1814         int value = atoi(arg);
1816         if (min <= value && value <= max) {
1817                 *opt = value;
1818                 return OK;
1819         }
1821         config_msg = "Integer value out of bound";
1822         return ERR;
1825 static bool
1826 set_color(int *color, const char *name)
1828         if (map_enum(color, color_map, name))
1829                 return TRUE;
1830         if (!prefixcmp(name, "color"))
1831                 return parse_int(color, name + 5, 0, 255) == OK;
1832         return FALSE;
1835 /* Wants: object fgcolor bgcolor [attribute] */
1836 static int
1837 option_color_command(int argc, const char *argv[])
1839         struct line_info *info;
1841         if (argc < 3) {
1842                 config_msg = "Wrong number of arguments given to color command";
1843                 return ERR;
1844         }
1846         info = get_line_info(argv[0]);
1847         if (!info) {
1848                 static const struct enum_map obsolete[] = {
1849                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1850                         ENUM_MAP("main-date",   LINE_DATE),
1851                         ENUM_MAP("main-author", LINE_AUTHOR),
1852                 };
1853                 int index;
1855                 if (!map_enum(&index, obsolete, argv[0])) {
1856                         config_msg = "Unknown color name";
1857                         return ERR;
1858                 }
1859                 info = &line_info[index];
1860         }
1862         if (!set_color(&info->fg, argv[1]) ||
1863             !set_color(&info->bg, argv[2])) {
1864                 config_msg = "Unknown color";
1865                 return ERR;
1866         }
1868         info->attr = 0;
1869         while (argc-- > 3) {
1870                 int attr;
1872                 if (!set_attribute(&attr, argv[argc])) {
1873                         config_msg = "Unknown attribute";
1874                         return ERR;
1875                 }
1876                 info->attr |= attr;
1877         }
1879         return OK;
1882 static int parse_bool(bool *opt, const char *arg)
1884         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1885                 ? TRUE : FALSE;
1886         return OK;
1889 static int parse_enum_do(unsigned int *opt, const char *arg,
1890                          const struct enum_map *map, size_t map_size)
1892         bool is_true;
1894         assert(map_size > 1);
1896         if (map_enum_do(map, map_size, (int *) opt, arg))
1897                 return OK;
1899         if (parse_bool(&is_true, arg) != OK)
1900                 return ERR;
1902         *opt = is_true ? map[1].value : map[0].value;
1903         return OK;
1906 #define parse_enum(opt, arg, map) \
1907         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1909 static int
1910 parse_string(char *opt, const char *arg, size_t optsize)
1912         int arglen = strlen(arg);
1914         switch (arg[0]) {
1915         case '\"':
1916         case '\'':
1917                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1918                         config_msg = "Unmatched quotation";
1919                         return ERR;
1920                 }
1921                 arg += 1; arglen -= 2;
1922         default:
1923                 string_ncopy_do(opt, optsize, arg, arglen);
1924                 return OK;
1925         }
1928 /* Wants: name = value */
1929 static int
1930 option_set_command(int argc, const char *argv[])
1932         if (argc != 3) {
1933                 config_msg = "Wrong number of arguments given to set command";
1934                 return ERR;
1935         }
1937         if (strcmp(argv[1], "=")) {
1938                 config_msg = "No value assigned";
1939                 return ERR;
1940         }
1942         if (!strcmp(argv[0], "show-author"))
1943                 return parse_enum(&opt_author, argv[2], author_map);
1945         if (!strcmp(argv[0], "show-date"))
1946                 return parse_enum(&opt_date, argv[2], date_map);
1948         if (!strcmp(argv[0], "show-rev-graph"))
1949                 return parse_bool(&opt_rev_graph, argv[2]);
1951         if (!strcmp(argv[0], "show-refs"))
1952                 return parse_bool(&opt_show_refs, argv[2]);
1954         if (!strcmp(argv[0], "show-line-numbers"))
1955                 return parse_bool(&opt_line_number, argv[2]);
1957         if (!strcmp(argv[0], "line-graphics"))
1958                 return parse_bool(&opt_line_graphics, argv[2]);
1960         if (!strcmp(argv[0], "line-number-interval"))
1961                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1963         if (!strcmp(argv[0], "author-width"))
1964                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1966         if (!strcmp(argv[0], "horizontal-scroll"))
1967                 return parse_step(&opt_hscroll, argv[2]);
1969         if (!strcmp(argv[0], "split-view-height"))
1970                 return parse_step(&opt_scale_split_view, argv[2]);
1972         if (!strcmp(argv[0], "tab-size"))
1973                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1975         if (!strcmp(argv[0], "commit-encoding"))
1976                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1978         config_msg = "Unknown variable name";
1979         return ERR;
1982 /* Wants: mode request key */
1983 static int
1984 option_bind_command(int argc, const char *argv[])
1986         enum request request;
1987         int keymap = -1;
1988         int key;
1990         if (argc < 3) {
1991                 config_msg = "Wrong number of arguments given to bind command";
1992                 return ERR;
1993         }
1995         if (set_keymap(&keymap, argv[0]) == ERR) {
1996                 config_msg = "Unknown key map";
1997                 return ERR;
1998         }
2000         key = get_key_value(argv[1]);
2001         if (key == ERR) {
2002                 config_msg = "Unknown key";
2003                 return ERR;
2004         }
2006         request = get_request(argv[2]);
2007         if (request == REQ_NONE) {
2008                 static const struct enum_map obsolete[] = {
2009                         ENUM_MAP("cherry-pick",         REQ_NONE),
2010                         ENUM_MAP("screen-resize",       REQ_NONE),
2011                         ENUM_MAP("tree-parent",         REQ_PARENT),
2012                 };
2013                 int alias;
2015                 if (map_enum(&alias, obsolete, argv[2])) {
2016                         if (alias != REQ_NONE)
2017                                 add_keybinding(keymap, alias, key);
2018                         config_msg = "Obsolete request name";
2019                         return ERR;
2020                 }
2021         }
2022         if (request == REQ_NONE && *argv[2]++ == '!')
2023                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2024         if (request == REQ_NONE) {
2025                 config_msg = "Unknown request name";
2026                 return ERR;
2027         }
2029         add_keybinding(keymap, request, key);
2031         return OK;
2034 static int
2035 set_option(const char *opt, char *value)
2037         const char *argv[SIZEOF_ARG];
2038         int argc = 0;
2040         if (!argv_from_string(argv, &argc, value)) {
2041                 config_msg = "Too many option arguments";
2042                 return ERR;
2043         }
2045         if (!strcmp(opt, "color"))
2046                 return option_color_command(argc, argv);
2048         if (!strcmp(opt, "set"))
2049                 return option_set_command(argc, argv);
2051         if (!strcmp(opt, "bind"))
2052                 return option_bind_command(argc, argv);
2054         config_msg = "Unknown option command";
2055         return ERR;
2058 static int
2059 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2061         int status = OK;
2063         config_lineno++;
2064         config_msg = "Internal error";
2066         /* Check for comment markers, since read_properties() will
2067          * only ensure opt and value are split at first " \t". */
2068         optlen = strcspn(opt, "#");
2069         if (optlen == 0)
2070                 return OK;
2072         if (opt[optlen] != 0) {
2073                 config_msg = "No option value";
2074                 status = ERR;
2076         }  else {
2077                 /* Look for comment endings in the value. */
2078                 size_t len = strcspn(value, "#");
2080                 if (len < valuelen) {
2081                         valuelen = len;
2082                         value[valuelen] = 0;
2083                 }
2085                 status = set_option(opt, value);
2086         }
2088         if (status == ERR) {
2089                 warn("Error on line %d, near '%.*s': %s",
2090                      config_lineno, (int) optlen, opt, config_msg);
2091                 config_errors = TRUE;
2092         }
2094         /* Always keep going if errors are encountered. */
2095         return OK;
2098 static void
2099 load_option_file(const char *path)
2101         struct io io = {};
2103         /* It's OK that the file doesn't exist. */
2104         if (!io_open(&io, "%s", path))
2105                 return;
2107         config_lineno = 0;
2108         config_errors = FALSE;
2110         if (io_load(&io, " \t", read_option) == ERR ||
2111             config_errors == TRUE)
2112                 warn("Errors while loading %s.", path);
2115 static int
2116 load_options(void)
2118         const char *home = getenv("HOME");
2119         const char *tigrc_user = getenv("TIGRC_USER");
2120         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2121         char buf[SIZEOF_STR];
2123         add_builtin_run_requests();
2125         if (!tigrc_system)
2126                 tigrc_system = SYSCONFDIR "/tigrc";
2127         load_option_file(tigrc_system);
2129         if (!tigrc_user) {
2130                 if (!home || !string_format(buf, "%s/.tigrc", home))
2131                         return ERR;
2132                 tigrc_user = buf;
2133         }
2134         load_option_file(tigrc_user);
2136         return OK;
2140 /*
2141  * The viewer
2142  */
2144 struct view;
2145 struct view_ops;
2147 /* The display array of active views and the index of the current view. */
2148 static struct view *display[2];
2149 static unsigned int current_view;
2151 #define foreach_displayed_view(view, i) \
2152         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2154 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2156 /* Current head and commit ID */
2157 static char ref_blob[SIZEOF_REF]        = "";
2158 static char ref_commit[SIZEOF_REF]      = "HEAD";
2159 static char ref_head[SIZEOF_REF]        = "HEAD";
2161 struct view {
2162         const char *name;       /* View name */
2163         const char *cmd_env;    /* Command line set via environment */
2164         const char *id;         /* Points to either of ref_{head,commit,blob} */
2166         struct view_ops *ops;   /* View operations */
2168         enum keymap keymap;     /* What keymap does this view have */
2169         bool git_dir;           /* Whether the view requires a git directory. */
2171         char ref[SIZEOF_REF];   /* Hovered commit reference */
2172         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2174         int height, width;      /* The width and height of the main window */
2175         WINDOW *win;            /* The main window */
2176         WINDOW *title;          /* The title window living below the main window */
2178         /* Navigation */
2179         unsigned long offset;   /* Offset of the window top */
2180         unsigned long yoffset;  /* Offset from the window side. */
2181         unsigned long lineno;   /* Current line number */
2182         unsigned long p_offset; /* Previous offset of the window top */
2183         unsigned long p_yoffset;/* Previous offset from the window side */
2184         unsigned long p_lineno; /* Previous current line number */
2185         bool p_restore;         /* Should the previous position be restored. */
2187         /* Searching */
2188         char grep[SIZEOF_STR];  /* Search string */
2189         regex_t *regex;         /* Pre-compiled regexp */
2191         /* If non-NULL, points to the view that opened this view. If this view
2192          * is closed tig will switch back to the parent view. */
2193         struct view *parent;
2195         /* Buffering */
2196         size_t lines;           /* Total number of lines */
2197         struct line *line;      /* Line index */
2198         unsigned int digits;    /* Number of digits in the lines member. */
2200         /* Drawing */
2201         struct line *curline;   /* Line currently being drawn. */
2202         enum line_type curtype; /* Attribute currently used for drawing. */
2203         unsigned long col;      /* Column when drawing. */
2204         bool has_scrolled;      /* View was scrolled. */
2206         /* Loading */
2207         struct io io;
2208         struct io *pipe;
2209         time_t start_time;
2210         time_t update_secs;
2211 };
2213 struct view_ops {
2214         /* What type of content being displayed. Used in the title bar. */
2215         const char *type;
2216         /* Default command arguments. */
2217         const char **argv;
2218         /* Open and reads in all view content. */
2219         bool (*open)(struct view *view);
2220         /* Read one line; updates view->line. */
2221         bool (*read)(struct view *view, char *data);
2222         /* Draw one line; @lineno must be < view->height. */
2223         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2224         /* Depending on view handle a special requests. */
2225         enum request (*request)(struct view *view, enum request request, struct line *line);
2226         /* Search for regexp in a line. */
2227         bool (*grep)(struct view *view, struct line *line);
2228         /* Select line */
2229         void (*select)(struct view *view, struct line *line);
2230         /* Prepare view for loading */
2231         bool (*prepare)(struct view *view);
2232 };
2234 static struct view_ops blame_ops;
2235 static struct view_ops blob_ops;
2236 static struct view_ops diff_ops;
2237 static struct view_ops help_ops;
2238 static struct view_ops log_ops;
2239 static struct view_ops main_ops;
2240 static struct view_ops pager_ops;
2241 static struct view_ops stage_ops;
2242 static struct view_ops status_ops;
2243 static struct view_ops tree_ops;
2244 static struct view_ops branch_ops;
2246 #define VIEW_STR(name, env, ref, ops, map, git) \
2247         { name, #env, ref, ops, map, git }
2249 #define VIEW_(id, name, ops, git, ref) \
2250         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2253 static struct view views[] = {
2254         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2255         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2256         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2257         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2258         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2259         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2260         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2261         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2262         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2263         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2264         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2265 };
2267 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2268 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2270 #define foreach_view(view, i) \
2271         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2273 #define view_is_displayed(view) \
2274         (view == display[0] || view == display[1])
2277 static inline void
2278 set_view_attr(struct view *view, enum line_type type)
2280         if (!view->curline->selected && view->curtype != type) {
2281                 (void) wattrset(view->win, get_line_attr(type));
2282                 wchgat(view->win, -1, 0, type, NULL);
2283                 view->curtype = type;
2284         }
2287 static int
2288 draw_chars(struct view *view, enum line_type type, const char *string,
2289            int max_len, bool use_tilde)
2291         static char out_buffer[BUFSIZ * 2];
2292         int len = 0;
2293         int col = 0;
2294         int trimmed = FALSE;
2295         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2297         if (max_len <= 0)
2298                 return 0;
2300         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2302         set_view_attr(view, type);
2303         if (len > 0) {
2304                 if (opt_iconv_out != ICONV_NONE) {
2305                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2306                         size_t inlen = len + 1;
2308                         char *outbuf = out_buffer;
2309                         size_t outlen = sizeof(out_buffer);
2311                         size_t ret;
2313                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2314                         if (ret != (size_t) -1) {
2315                                 string = out_buffer;
2316                                 len = sizeof(out_buffer) - outlen;
2317                         }
2318                 }
2320                 waddnstr(view->win, string, len);
2321         }
2322         if (trimmed && use_tilde) {
2323                 set_view_attr(view, LINE_DELIMITER);
2324                 waddch(view->win, '~');
2325                 col++;
2326         }
2328         return col;
2331 static int
2332 draw_space(struct view *view, enum line_type type, int max, int spaces)
2334         static char space[] = "                    ";
2335         int col = 0;
2337         spaces = MIN(max, spaces);
2339         while (spaces > 0) {
2340                 int len = MIN(spaces, sizeof(space) - 1);
2342                 col += draw_chars(view, type, space, len, FALSE);
2343                 spaces -= len;
2344         }
2346         return col;
2349 static bool
2350 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2352         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2353         return view->width + view->yoffset <= view->col;
2356 static bool
2357 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2359         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2360         int max = view->width + view->yoffset - view->col;
2361         int i;
2363         if (max < size)
2364                 size = max;
2366         set_view_attr(view, type);
2367         /* Using waddch() instead of waddnstr() ensures that
2368          * they'll be rendered correctly for the cursor line. */
2369         for (i = skip; i < size; i++)
2370                 waddch(view->win, graphic[i]);
2372         view->col += size;
2373         if (size < max && skip <= size)
2374                 waddch(view->win, ' ');
2375         view->col++;
2377         return view->width + view->yoffset <= view->col;
2380 static bool
2381 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2383         int max = MIN(view->width + view->yoffset - view->col, len);
2384         int col;
2386         if (text)
2387                 col = draw_chars(view, type, text, max - 1, trim);
2388         else
2389                 col = draw_space(view, type, max - 1, max - 1);
2391         view->col += col;
2392         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2393         return view->width + view->yoffset <= view->col;
2396 static bool
2397 draw_date(struct view *view, struct time *time)
2399         const char *date = mkdate(time, opt_date);
2400         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2402         return draw_field(view, LINE_DATE, date, cols, FALSE);
2405 static bool
2406 draw_author(struct view *view, const char *author)
2408         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2409         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2411         if (abbreviate && author)
2412                 author = get_author_initials(author);
2414         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2417 static bool
2418 draw_mode(struct view *view, mode_t mode)
2420         const char *str;
2422         if (S_ISDIR(mode))
2423                 str = "drwxr-xr-x";
2424         else if (S_ISLNK(mode))
2425                 str = "lrwxrwxrwx";
2426         else if (S_ISGITLINK(mode))
2427                 str = "m---------";
2428         else if (S_ISREG(mode) && mode & S_IXUSR)
2429                 str = "-rwxr-xr-x";
2430         else if (S_ISREG(mode))
2431                 str = "-rw-r--r--";
2432         else
2433                 str = "----------";
2435         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2438 static bool
2439 draw_lineno(struct view *view, unsigned int lineno)
2441         char number[10];
2442         int digits3 = view->digits < 3 ? 3 : view->digits;
2443         int max = MIN(view->width + view->yoffset - view->col, digits3);
2444         char *text = NULL;
2445         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2447         lineno += view->offset + 1;
2448         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2449                 static char fmt[] = "%1ld";
2451                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2452                 if (string_format(number, fmt, lineno))
2453                         text = number;
2454         }
2455         if (text)
2456                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2457         else
2458                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2459         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2462 static bool
2463 draw_view_line(struct view *view, unsigned int lineno)
2465         struct line *line;
2466         bool selected = (view->offset + lineno == view->lineno);
2468         assert(view_is_displayed(view));
2470         if (view->offset + lineno >= view->lines)
2471                 return FALSE;
2473         line = &view->line[view->offset + lineno];
2475         wmove(view->win, lineno, 0);
2476         if (line->cleareol)
2477                 wclrtoeol(view->win);
2478         view->col = 0;
2479         view->curline = line;
2480         view->curtype = LINE_NONE;
2481         line->selected = FALSE;
2482         line->dirty = line->cleareol = 0;
2484         if (selected) {
2485                 set_view_attr(view, LINE_CURSOR);
2486                 line->selected = TRUE;
2487                 view->ops->select(view, line);
2488         }
2490         return view->ops->draw(view, line, lineno);
2493 static void
2494 redraw_view_dirty(struct view *view)
2496         bool dirty = FALSE;
2497         int lineno;
2499         for (lineno = 0; lineno < view->height; lineno++) {
2500                 if (view->offset + lineno >= view->lines)
2501                         break;
2502                 if (!view->line[view->offset + lineno].dirty)
2503                         continue;
2504                 dirty = TRUE;
2505                 if (!draw_view_line(view, lineno))
2506                         break;
2507         }
2509         if (!dirty)
2510                 return;
2511         wnoutrefresh(view->win);
2514 static void
2515 redraw_view_from(struct view *view, int lineno)
2517         assert(0 <= lineno && lineno < view->height);
2519         for (; lineno < view->height; lineno++) {
2520                 if (!draw_view_line(view, lineno))
2521                         break;
2522         }
2524         wnoutrefresh(view->win);
2527 static void
2528 redraw_view(struct view *view)
2530         werase(view->win);
2531         redraw_view_from(view, 0);
2535 static void
2536 update_view_title(struct view *view)
2538         char buf[SIZEOF_STR];
2539         char state[SIZEOF_STR];
2540         size_t bufpos = 0, statelen = 0;
2542         assert(view_is_displayed(view));
2544         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2545                 unsigned int view_lines = view->offset + view->height;
2546                 unsigned int lines = view->lines
2547                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2548                                    : 0;
2550                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2551                                    view->ops->type,
2552                                    view->lineno + 1,
2553                                    view->lines,
2554                                    lines);
2556         }
2558         if (view->pipe) {
2559                 time_t secs = time(NULL) - view->start_time;
2561                 /* Three git seconds are a long time ... */
2562                 if (secs > 2)
2563                         string_format_from(state, &statelen, " loading %lds", secs);
2564         }
2566         string_format_from(buf, &bufpos, "[%s]", view->name);
2567         if (*view->ref && bufpos < view->width) {
2568                 size_t refsize = strlen(view->ref);
2569                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2571                 if (minsize < view->width)
2572                         refsize = view->width - minsize + 7;
2573                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2574         }
2576         if (statelen && bufpos < view->width) {
2577                 string_format_from(buf, &bufpos, "%s", state);
2578         }
2580         if (view == display[current_view])
2581                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2582         else
2583                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2585         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2586         wclrtoeol(view->title);
2587         wnoutrefresh(view->title);
2590 static int
2591 apply_step(double step, int value)
2593         if (step >= 1)
2594                 return (int) step;
2595         value *= step + 0.01;
2596         return value ? value : 1;
2599 static void
2600 resize_display(void)
2602         int offset, i;
2603         struct view *base = display[0];
2604         struct view *view = display[1] ? display[1] : display[0];
2606         /* Setup window dimensions */
2608         getmaxyx(stdscr, base->height, base->width);
2610         /* Make room for the status window. */
2611         base->height -= 1;
2613         if (view != base) {
2614                 /* Horizontal split. */
2615                 view->width   = base->width;
2616                 view->height  = apply_step(opt_scale_split_view, base->height);
2617                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2618                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2619                 base->height -= view->height;
2621                 /* Make room for the title bar. */
2622                 view->height -= 1;
2623         }
2625         /* Make room for the title bar. */
2626         base->height -= 1;
2628         offset = 0;
2630         foreach_displayed_view (view, i) {
2631                 if (!view->win) {
2632                         view->win = newwin(view->height, 0, offset, 0);
2633                         if (!view->win)
2634                                 die("Failed to create %s view", view->name);
2636                         scrollok(view->win, FALSE);
2638                         view->title = newwin(1, 0, offset + view->height, 0);
2639                         if (!view->title)
2640                                 die("Failed to create title window");
2642                 } else {
2643                         wresize(view->win, view->height, view->width);
2644                         mvwin(view->win,   offset, 0);
2645                         mvwin(view->title, offset + view->height, 0);
2646                 }
2648                 offset += view->height + 1;
2649         }
2652 static void
2653 redraw_display(bool clear)
2655         struct view *view;
2656         int i;
2658         foreach_displayed_view (view, i) {
2659                 if (clear)
2660                         wclear(view->win);
2661                 redraw_view(view);
2662                 update_view_title(view);
2663         }
2666 static void
2667 toggle_enum_option_do(unsigned int *opt, const char *help,
2668                       const struct enum_map *map, size_t size)
2670         *opt = (*opt + 1) % size;
2671         redraw_display(FALSE);
2672         report("Displaying %s %s", enum_name(map[*opt]), help);
2675 #define toggle_enum_option(opt, help, map) \
2676         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2678 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2679 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2681 static void
2682 toggle_view_option(bool *option, const char *help)
2684         *option = !*option;
2685         redraw_display(FALSE);
2686         report("%sabling %s", *option ? "En" : "Dis", help);
2689 static void
2690 open_option_menu(void)
2692         const struct menu_item menu[] = {
2693                 { '.', "line numbers", &opt_line_number },
2694                 { 'D', "date display", &opt_date },
2695                 { 'A', "author display", &opt_author },
2696                 { 'g', "revision graph display", &opt_rev_graph },
2697                 { 'F', "reference display", &opt_show_refs },
2698                 { 0 }
2699         };
2700         int selected = 0;
2702         if (prompt_menu("Toggle option", menu, &selected)) {
2703                 if (menu[selected].data == &opt_date)
2704                         toggle_date();
2705                 else if (menu[selected].data == &opt_author)
2706                         toggle_author();
2707                 else
2708                         toggle_view_option(menu[selected].data, menu[selected].text);
2709         }
2712 static void
2713 maximize_view(struct view *view)
2715         memset(display, 0, sizeof(display));
2716         current_view = 0;
2717         display[current_view] = view;
2718         resize_display();
2719         redraw_display(FALSE);
2720         report("");
2724 /*
2725  * Navigation
2726  */
2728 static bool
2729 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2731         if (lineno >= view->lines)
2732                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2734         if (offset > lineno || offset + view->height <= lineno) {
2735                 unsigned long half = view->height / 2;
2737                 if (lineno > half)
2738                         offset = lineno - half;
2739                 else
2740                         offset = 0;
2741         }
2743         if (offset != view->offset || lineno != view->lineno) {
2744                 view->offset = offset;
2745                 view->lineno = lineno;
2746                 return TRUE;
2747         }
2749         return FALSE;
2752 /* Scrolling backend */
2753 static void
2754 do_scroll_view(struct view *view, int lines)
2756         bool redraw_current_line = FALSE;
2758         /* The rendering expects the new offset. */
2759         view->offset += lines;
2761         assert(0 <= view->offset && view->offset < view->lines);
2762         assert(lines);
2764         /* Move current line into the view. */
2765         if (view->lineno < view->offset) {
2766                 view->lineno = view->offset;
2767                 redraw_current_line = TRUE;
2768         } else if (view->lineno >= view->offset + view->height) {
2769                 view->lineno = view->offset + view->height - 1;
2770                 redraw_current_line = TRUE;
2771         }
2773         assert(view->offset <= view->lineno && view->lineno < view->lines);
2775         /* Redraw the whole screen if scrolling is pointless. */
2776         if (view->height < ABS(lines)) {
2777                 redraw_view(view);
2779         } else {
2780                 int line = lines > 0 ? view->height - lines : 0;
2781                 int end = line + ABS(lines);
2783                 scrollok(view->win, TRUE);
2784                 wscrl(view->win, lines);
2785                 scrollok(view->win, FALSE);
2787                 while (line < end && draw_view_line(view, line))
2788                         line++;
2790                 if (redraw_current_line)
2791                         draw_view_line(view, view->lineno - view->offset);
2792                 wnoutrefresh(view->win);
2793         }
2795         view->has_scrolled = TRUE;
2796         report("");
2799 /* Scroll frontend */
2800 static void
2801 scroll_view(struct view *view, enum request request)
2803         int lines = 1;
2805         assert(view_is_displayed(view));
2807         switch (request) {
2808         case REQ_SCROLL_LEFT:
2809                 if (view->yoffset == 0) {
2810                         report("Cannot scroll beyond the first column");
2811                         return;
2812                 }
2813                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2814                         view->yoffset = 0;
2815                 else
2816                         view->yoffset -= apply_step(opt_hscroll, view->width);
2817                 redraw_view_from(view, 0);
2818                 report("");
2819                 return;
2820         case REQ_SCROLL_RIGHT:
2821                 view->yoffset += apply_step(opt_hscroll, view->width);
2822                 redraw_view(view);
2823                 report("");
2824                 return;
2825         case REQ_SCROLL_PAGE_DOWN:
2826                 lines = view->height;
2827         case REQ_SCROLL_LINE_DOWN:
2828                 if (view->offset + lines > view->lines)
2829                         lines = view->lines - view->offset;
2831                 if (lines == 0 || view->offset + view->height >= view->lines) {
2832                         report("Cannot scroll beyond the last line");
2833                         return;
2834                 }
2835                 break;
2837         case REQ_SCROLL_PAGE_UP:
2838                 lines = view->height;
2839         case REQ_SCROLL_LINE_UP:
2840                 if (lines > view->offset)
2841                         lines = view->offset;
2843                 if (lines == 0) {
2844                         report("Cannot scroll beyond the first line");
2845                         return;
2846                 }
2848                 lines = -lines;
2849                 break;
2851         default:
2852                 die("request %d not handled in switch", request);
2853         }
2855         do_scroll_view(view, lines);
2858 /* Cursor moving */
2859 static void
2860 move_view(struct view *view, enum request request)
2862         int scroll_steps = 0;
2863         int steps;
2865         switch (request) {
2866         case REQ_MOVE_FIRST_LINE:
2867                 steps = -view->lineno;
2868                 break;
2870         case REQ_MOVE_LAST_LINE:
2871                 steps = view->lines - view->lineno - 1;
2872                 break;
2874         case REQ_MOVE_PAGE_UP:
2875                 steps = view->height > view->lineno
2876                       ? -view->lineno : -view->height;
2877                 break;
2879         case REQ_MOVE_PAGE_DOWN:
2880                 steps = view->lineno + view->height >= view->lines
2881                       ? view->lines - view->lineno - 1 : view->height;
2882                 break;
2884         case REQ_MOVE_UP:
2885                 steps = -1;
2886                 break;
2888         case REQ_MOVE_DOWN:
2889                 steps = 1;
2890                 break;
2892         default:
2893                 die("request %d not handled in switch", request);
2894         }
2896         if (steps <= 0 && view->lineno == 0) {
2897                 report("Cannot move beyond the first line");
2898                 return;
2900         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2901                 report("Cannot move beyond the last line");
2902                 return;
2903         }
2905         /* Move the current line */
2906         view->lineno += steps;
2907         assert(0 <= view->lineno && view->lineno < view->lines);
2909         /* Check whether the view needs to be scrolled */
2910         if (view->lineno < view->offset ||
2911             view->lineno >= view->offset + view->height) {
2912                 scroll_steps = steps;
2913                 if (steps < 0 && -steps > view->offset) {
2914                         scroll_steps = -view->offset;
2916                 } else if (steps > 0) {
2917                         if (view->lineno == view->lines - 1 &&
2918                             view->lines > view->height) {
2919                                 scroll_steps = view->lines - view->offset - 1;
2920                                 if (scroll_steps >= view->height)
2921                                         scroll_steps -= view->height - 1;
2922                         }
2923                 }
2924         }
2926         if (!view_is_displayed(view)) {
2927                 view->offset += scroll_steps;
2928                 assert(0 <= view->offset && view->offset < view->lines);
2929                 view->ops->select(view, &view->line[view->lineno]);
2930                 return;
2931         }
2933         /* Repaint the old "current" line if we be scrolling */
2934         if (ABS(steps) < view->height)
2935                 draw_view_line(view, view->lineno - steps - view->offset);
2937         if (scroll_steps) {
2938                 do_scroll_view(view, scroll_steps);
2939                 return;
2940         }
2942         /* Draw the current line */
2943         draw_view_line(view, view->lineno - view->offset);
2945         wnoutrefresh(view->win);
2946         report("");
2950 /*
2951  * Searching
2952  */
2954 static void search_view(struct view *view, enum request request);
2956 static bool
2957 grep_text(struct view *view, const char *text[])
2959         regmatch_t pmatch;
2960         size_t i;
2962         for (i = 0; text[i]; i++)
2963                 if (*text[i] &&
2964                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2965                         return TRUE;
2966         return FALSE;
2969 static void
2970 select_view_line(struct view *view, unsigned long lineno)
2972         unsigned long old_lineno = view->lineno;
2973         unsigned long old_offset = view->offset;
2975         if (goto_view_line(view, view->offset, lineno)) {
2976                 if (view_is_displayed(view)) {
2977                         if (old_offset != view->offset) {
2978                                 redraw_view(view);
2979                         } else {
2980                                 draw_view_line(view, old_lineno - view->offset);
2981                                 draw_view_line(view, view->lineno - view->offset);
2982                                 wnoutrefresh(view->win);
2983                         }
2984                 } else {
2985                         view->ops->select(view, &view->line[view->lineno]);
2986                 }
2987         }
2990 static void
2991 find_next(struct view *view, enum request request)
2993         unsigned long lineno = view->lineno;
2994         int direction;
2996         if (!*view->grep) {
2997                 if (!*opt_search)
2998                         report("No previous search");
2999                 else
3000                         search_view(view, request);
3001                 return;
3002         }
3004         switch (request) {
3005         case REQ_SEARCH:
3006         case REQ_FIND_NEXT:
3007                 direction = 1;
3008                 break;
3010         case REQ_SEARCH_BACK:
3011         case REQ_FIND_PREV:
3012                 direction = -1;
3013                 break;
3015         default:
3016                 return;
3017         }
3019         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3020                 lineno += direction;
3022         /* Note, lineno is unsigned long so will wrap around in which case it
3023          * will become bigger than view->lines. */
3024         for (; lineno < view->lines; lineno += direction) {
3025                 if (view->ops->grep(view, &view->line[lineno])) {
3026                         select_view_line(view, lineno);
3027                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3028                         return;
3029                 }
3030         }
3032         report("No match found for '%s'", view->grep);
3035 static void
3036 search_view(struct view *view, enum request request)
3038         int regex_err;
3040         if (view->regex) {
3041                 regfree(view->regex);
3042                 *view->grep = 0;
3043         } else {
3044                 view->regex = calloc(1, sizeof(*view->regex));
3045                 if (!view->regex)
3046                         return;
3047         }
3049         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3050         if (regex_err != 0) {
3051                 char buf[SIZEOF_STR] = "unknown error";
3053                 regerror(regex_err, view->regex, buf, sizeof(buf));
3054                 report("Search failed: %s", buf);
3055                 return;
3056         }
3058         string_copy(view->grep, opt_search);
3060         find_next(view, request);
3063 /*
3064  * Incremental updating
3065  */
3067 static void
3068 reset_view(struct view *view)
3070         int i;
3072         for (i = 0; i < view->lines; i++)
3073                 free(view->line[i].data);
3074         free(view->line);
3076         view->p_offset = view->offset;
3077         view->p_yoffset = view->yoffset;
3078         view->p_lineno = view->lineno;
3080         view->line = NULL;
3081         view->offset = 0;
3082         view->yoffset = 0;
3083         view->lines  = 0;
3084         view->lineno = 0;
3085         view->vid[0] = 0;
3086         view->update_secs = 0;
3089 static void
3090 free_argv(const char *argv[])
3092         int argc;
3094         for (argc = 0; argv[argc]; argc++)
3095                 free((void *) argv[argc]);
3098 static const char *
3099 format_arg(const char *name)
3101         static struct {
3102                 const char *name;
3103                 size_t namelen;
3104                 const char *value;
3105                 const char *value_if_empty;
3106         } vars[] = {
3107 #define FORMAT_VAR(name, value, value_if_empty) \
3108         { name, STRING_SIZE(name), value, value_if_empty }
3109                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3110                 FORMAT_VAR("%(file)",           opt_file,       ""),
3111                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3112                 FORMAT_VAR("%(head)",           ref_head,       ""),
3113                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3114                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3115         };
3116         int i;
3118         for (i = 0; i < ARRAY_SIZE(vars); i++)
3119                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3120                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3122         report("Unknown replacement: `%s`", name);
3123         return NULL;
3126 static bool
3127 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3129         char buf[SIZEOF_STR];
3130         int argc;
3131         bool noreplace = flags == FORMAT_NONE;
3133         free_argv(dst_argv);
3135         for (argc = 0; src_argv[argc]; argc++) {
3136                 const char *arg = src_argv[argc];
3137                 size_t bufpos = 0;
3139                 while (arg) {
3140                         char *next = strstr(arg, "%(");
3141                         int len = next - arg;
3142                         const char *value;
3144                         if (!next || noreplace) {
3145                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
3146                                         noreplace = TRUE;
3147                                 len = strlen(arg);
3148                                 value = "";
3150                         } else {
3151                                 value = format_arg(next);
3153                                 if (!value) {
3154                                         return FALSE;
3155                                 }
3156                         }
3158                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3159                                 return FALSE;
3161                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3162                 }
3164                 dst_argv[argc] = strdup(buf);
3165                 if (!dst_argv[argc])
3166                         break;
3167         }
3169         dst_argv[argc] = NULL;
3171         return src_argv[argc] == NULL;
3174 static bool
3175 restore_view_position(struct view *view)
3177         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3178                 return FALSE;
3180         /* Changing the view position cancels the restoring. */
3181         /* FIXME: Changing back to the first line is not detected. */
3182         if (view->offset != 0 || view->lineno != 0) {
3183                 view->p_restore = FALSE;
3184                 return FALSE;
3185         }
3187         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3188             view_is_displayed(view))
3189                 werase(view->win);
3191         view->yoffset = view->p_yoffset;
3192         view->p_restore = FALSE;
3194         return TRUE;
3197 static void
3198 end_update(struct view *view, bool force)
3200         if (!view->pipe)
3201                 return;
3202         while (!view->ops->read(view, NULL))
3203                 if (!force)
3204                         return;
3205         if (force)
3206                 io_kill(view->pipe);
3207         io_done(view->pipe);
3208         view->pipe = NULL;
3211 static void
3212 setup_update(struct view *view, const char *vid)
3214         reset_view(view);
3215         string_copy_rev(view->vid, vid);
3216         view->pipe = &view->io;
3217         view->start_time = time(NULL);
3220 static bool
3221 prepare_update(struct view *view, const char *argv[], const char *dir,
3222                enum format_flags flags)
3224         if (view->pipe)
3225                 end_update(view, TRUE);
3226         return io_format(&view->io, dir, IO_RD, argv, flags);
3229 static bool
3230 prepare_update_file(struct view *view, const char *name)
3232         if (view->pipe)
3233                 end_update(view, TRUE);
3234         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3237 static bool
3238 begin_update(struct view *view, bool refresh)
3240         if (view->pipe)
3241                 end_update(view, TRUE);
3243         if (!refresh) {
3244                 if (view->ops->prepare) {
3245                         if (!view->ops->prepare(view))
3246                                 return FALSE;
3247                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3248                         return FALSE;
3249                 }
3251                 /* Put the current ref_* value to the view title ref
3252                  * member. This is needed by the blob view. Most other
3253                  * views sets it automatically after loading because the
3254                  * first line is a commit line. */
3255                 string_copy_rev(view->ref, view->id);
3256         }
3258         if (!io_start(&view->io))
3259                 return FALSE;
3261         setup_update(view, view->id);
3263         return TRUE;
3266 static bool
3267 update_view(struct view *view)
3269         char out_buffer[BUFSIZ * 2];
3270         char *line;
3271         /* Clear the view and redraw everything since the tree sorting
3272          * might have rearranged things. */
3273         bool redraw = view->lines == 0;
3274         bool can_read = TRUE;
3276         if (!view->pipe)
3277                 return TRUE;
3279         if (!io_can_read(view->pipe)) {
3280                 if (view->lines == 0 && view_is_displayed(view)) {
3281                         time_t secs = time(NULL) - view->start_time;
3283                         if (secs > 1 && secs > view->update_secs) {
3284                                 if (view->update_secs == 0)
3285                                         redraw_view(view);
3286                                 update_view_title(view);
3287                                 view->update_secs = secs;
3288                         }
3289                 }
3290                 return TRUE;
3291         }
3293         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3294                 if (opt_iconv_in != ICONV_NONE) {
3295                         ICONV_CONST char *inbuf = line;
3296                         size_t inlen = strlen(line) + 1;
3298                         char *outbuf = out_buffer;
3299                         size_t outlen = sizeof(out_buffer);
3301                         size_t ret;
3303                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3304                         if (ret != (size_t) -1)
3305                                 line = out_buffer;
3306                 }
3308                 if (!view->ops->read(view, line)) {
3309                         report("Allocation failure");
3310                         end_update(view, TRUE);
3311                         return FALSE;
3312                 }
3313         }
3315         {
3316                 unsigned long lines = view->lines;
3317                 int digits;
3319                 for (digits = 0; lines; digits++)
3320                         lines /= 10;
3322                 /* Keep the displayed view in sync with line number scaling. */
3323                 if (digits != view->digits) {
3324                         view->digits = digits;
3325                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3326                                 redraw = TRUE;
3327                 }
3328         }
3330         if (io_error(view->pipe)) {
3331                 report("Failed to read: %s", io_strerror(view->pipe));
3332                 end_update(view, TRUE);
3334         } else if (io_eof(view->pipe)) {
3335                 report("");
3336                 end_update(view, FALSE);
3337         }
3339         if (restore_view_position(view))
3340                 redraw = TRUE;
3342         if (!view_is_displayed(view))
3343                 return TRUE;
3345         if (redraw)
3346                 redraw_view_from(view, 0);
3347         else
3348                 redraw_view_dirty(view);
3350         /* Update the title _after_ the redraw so that if the redraw picks up a
3351          * commit reference in view->ref it'll be available here. */
3352         update_view_title(view);
3353         return TRUE;
3356 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3358 static struct line *
3359 add_line_data(struct view *view, void *data, enum line_type type)
3361         struct line *line;
3363         if (!realloc_lines(&view->line, view->lines, 1))
3364                 return NULL;
3366         line = &view->line[view->lines++];
3367         memset(line, 0, sizeof(*line));
3368         line->type = type;
3369         line->data = data;
3370         line->dirty = 1;
3372         return line;
3375 static struct line *
3376 add_line_text(struct view *view, const char *text, enum line_type type)
3378         char *data = text ? strdup(text) : NULL;
3380         return data ? add_line_data(view, data, type) : NULL;
3383 static struct line *
3384 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3386         char buf[SIZEOF_STR];
3387         va_list args;
3389         va_start(args, fmt);
3390         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3391                 buf[0] = 0;
3392         va_end(args);
3394         return buf[0] ? add_line_text(view, buf, type) : NULL;
3397 /*
3398  * View opening
3399  */
3401 enum open_flags {
3402         OPEN_DEFAULT = 0,       /* Use default view switching. */
3403         OPEN_SPLIT = 1,         /* Split current view. */
3404         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3405         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3406         OPEN_PREPARED = 32,     /* Open already prepared command. */
3407 };
3409 static void
3410 open_view(struct view *prev, enum request request, enum open_flags flags)
3412         bool split = !!(flags & OPEN_SPLIT);
3413         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3414         bool nomaximize = !!(flags & OPEN_REFRESH);
3415         struct view *view = VIEW(request);
3416         int nviews = displayed_views();
3417         struct view *base_view = display[0];
3419         if (view == prev && nviews == 1 && !reload) {
3420                 report("Already in %s view", view->name);
3421                 return;
3422         }
3424         if (view->git_dir && !opt_git_dir[0]) {
3425                 report("The %s view is disabled in pager view", view->name);
3426                 return;
3427         }
3429         if (split) {
3430                 display[1] = view;
3431                 current_view = 1;
3432         } else if (!nomaximize) {
3433                 /* Maximize the current view. */
3434                 memset(display, 0, sizeof(display));
3435                 current_view = 0;
3436                 display[current_view] = view;
3437         }
3439         /* No parent signals that this is the first loaded view. */
3440         if (prev && view != prev) {
3441                 view->parent = prev;
3442         }
3444         /* Resize the view when switching between split- and full-screen,
3445          * or when switching between two different full-screen views. */
3446         if (nviews != displayed_views() ||
3447             (nviews == 1 && base_view != display[0]))
3448                 resize_display();
3450         if (view->ops->open) {
3451                 if (view->pipe)
3452                         end_update(view, TRUE);
3453                 if (!view->ops->open(view)) {
3454                         report("Failed to load %s view", view->name);
3455                         return;
3456                 }
3457                 restore_view_position(view);
3459         } else if ((reload || strcmp(view->vid, view->id)) &&
3460                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3461                 report("Failed to load %s view", view->name);
3462                 return;
3463         }
3465         if (split && prev->lineno - prev->offset >= prev->height) {
3466                 /* Take the title line into account. */
3467                 int lines = prev->lineno - prev->offset - prev->height + 1;
3469                 /* Scroll the view that was split if the current line is
3470                  * outside the new limited view. */
3471                 do_scroll_view(prev, lines);
3472         }
3474         if (prev && view != prev && split && view_is_displayed(prev)) {
3475                 /* "Blur" the previous view. */
3476                 update_view_title(prev);
3477         }
3479         if (view->pipe && view->lines == 0) {
3480                 /* Clear the old view and let the incremental updating refill
3481                  * the screen. */
3482                 werase(view->win);
3483                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3484                 report("");
3485         } else if (view_is_displayed(view)) {
3486                 redraw_view(view);
3487                 report("");
3488         }
3491 static void
3492 open_external_viewer(const char *argv[], const char *dir)
3494         def_prog_mode();           /* save current tty modes */
3495         endwin();                  /* restore original tty modes */
3496         io_run_fg(argv, dir);
3497         fprintf(stderr, "Press Enter to continue");
3498         getc(opt_tty);
3499         reset_prog_mode();
3500         redraw_display(TRUE);
3503 static void
3504 open_mergetool(const char *file)
3506         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3508         open_external_viewer(mergetool_argv, opt_cdup);
3511 static void
3512 open_editor(const char *file)
3514         const char *editor_argv[] = { "vi", file, NULL };
3515         const char *editor;
3517         editor = getenv("GIT_EDITOR");
3518         if (!editor && *opt_editor)
3519                 editor = opt_editor;
3520         if (!editor)
3521                 editor = getenv("VISUAL");
3522         if (!editor)
3523                 editor = getenv("EDITOR");
3524         if (!editor)
3525                 editor = "vi";
3527         editor_argv[0] = editor;
3528         open_external_viewer(editor_argv, opt_cdup);
3531 static void
3532 open_run_request(enum request request)
3534         struct run_request *req = get_run_request(request);
3535         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3537         if (!req) {
3538                 report("Unknown run request");
3539                 return;
3540         }
3542         if (format_argv(argv, req->argv, FORMAT_ALL))
3543                 open_external_viewer(argv, NULL);
3544         free_argv(argv);
3547 /*
3548  * User request switch noodle
3549  */
3551 static int
3552 view_driver(struct view *view, enum request request)
3554         int i;
3556         if (request == REQ_NONE)
3557                 return TRUE;
3559         if (request > REQ_NONE) {
3560                 open_run_request(request);
3561                 /* FIXME: When all views can refresh always do this. */
3562                 if (view == VIEW(REQ_VIEW_STATUS) ||
3563                     view == VIEW(REQ_VIEW_MAIN) ||
3564                     view == VIEW(REQ_VIEW_LOG) ||
3565                     view == VIEW(REQ_VIEW_BRANCH) ||
3566                     view == VIEW(REQ_VIEW_STAGE))
3567                         request = REQ_REFRESH;
3568                 else
3569                         return TRUE;
3570         }
3572         if (view && view->lines) {
3573                 request = view->ops->request(view, request, &view->line[view->lineno]);
3574                 if (request == REQ_NONE)
3575                         return TRUE;
3576         }
3578         switch (request) {
3579         case REQ_MOVE_UP:
3580         case REQ_MOVE_DOWN:
3581         case REQ_MOVE_PAGE_UP:
3582         case REQ_MOVE_PAGE_DOWN:
3583         case REQ_MOVE_FIRST_LINE:
3584         case REQ_MOVE_LAST_LINE:
3585                 move_view(view, request);
3586                 break;
3588         case REQ_SCROLL_LEFT:
3589         case REQ_SCROLL_RIGHT:
3590         case REQ_SCROLL_LINE_DOWN:
3591         case REQ_SCROLL_LINE_UP:
3592         case REQ_SCROLL_PAGE_DOWN:
3593         case REQ_SCROLL_PAGE_UP:
3594                 scroll_view(view, request);
3595                 break;
3597         case REQ_VIEW_BLAME:
3598                 if (!opt_file[0]) {
3599                         report("No file chosen, press %s to open tree view",
3600                                get_key(view->keymap, REQ_VIEW_TREE));
3601                         break;
3602                 }
3603                 open_view(view, request, OPEN_DEFAULT);
3604                 break;
3606         case REQ_VIEW_BLOB:
3607                 if (!ref_blob[0]) {
3608                         report("No file chosen, press %s to open tree view",
3609                                get_key(view->keymap, REQ_VIEW_TREE));
3610                         break;
3611                 }
3612                 open_view(view, request, OPEN_DEFAULT);
3613                 break;
3615         case REQ_VIEW_PAGER:
3616                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3617                         report("No pager content, press %s to run command from prompt",
3618                                get_key(view->keymap, REQ_PROMPT));
3619                         break;
3620                 }
3621                 open_view(view, request, OPEN_DEFAULT);
3622                 break;
3624         case REQ_VIEW_STAGE:
3625                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3626                         report("No stage content, press %s to open the status view and choose file",
3627                                get_key(view->keymap, REQ_VIEW_STATUS));
3628                         break;
3629                 }
3630                 open_view(view, request, OPEN_DEFAULT);
3631                 break;
3633         case REQ_VIEW_STATUS:
3634                 if (opt_is_inside_work_tree == FALSE) {
3635                         report("The status view requires a working tree");
3636                         break;
3637                 }
3638                 open_view(view, request, OPEN_DEFAULT);
3639                 break;
3641         case REQ_VIEW_MAIN:
3642         case REQ_VIEW_DIFF:
3643         case REQ_VIEW_LOG:
3644         case REQ_VIEW_TREE:
3645         case REQ_VIEW_HELP:
3646         case REQ_VIEW_BRANCH:
3647                 open_view(view, request, OPEN_DEFAULT);
3648                 break;
3650         case REQ_NEXT:
3651         case REQ_PREVIOUS:
3652                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3654                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3655                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3656                    (view == VIEW(REQ_VIEW_DIFF) &&
3657                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3658                    (view == VIEW(REQ_VIEW_STAGE) &&
3659                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3660                    (view == VIEW(REQ_VIEW_BLOB) &&
3661                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3662                    (view == VIEW(REQ_VIEW_MAIN) &&
3663                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3664                         int line;
3666                         view = view->parent;
3667                         line = view->lineno;
3668                         move_view(view, request);
3669                         if (view_is_displayed(view))
3670                                 update_view_title(view);
3671                         if (line != view->lineno)
3672                                 view->ops->request(view, REQ_ENTER,
3673                                                    &view->line[view->lineno]);
3675                 } else {
3676                         move_view(view, request);
3677                 }
3678                 break;
3680         case REQ_VIEW_NEXT:
3681         {
3682                 int nviews = displayed_views();
3683                 int next_view = (current_view + 1) % nviews;
3685                 if (next_view == current_view) {
3686                         report("Only one view is displayed");
3687                         break;
3688                 }
3690                 current_view = next_view;
3691                 /* Blur out the title of the previous view. */
3692                 update_view_title(view);
3693                 report("");
3694                 break;
3695         }
3696         case REQ_REFRESH:
3697                 report("Refreshing is not yet supported for the %s view", view->name);
3698                 break;
3700         case REQ_MAXIMIZE:
3701                 if (displayed_views() == 2)
3702                         maximize_view(view);
3703                 break;
3705         case REQ_OPTIONS:
3706                 open_option_menu();
3707                 break;
3709         case REQ_TOGGLE_LINENO:
3710                 toggle_view_option(&opt_line_number, "line numbers");
3711                 break;
3713         case REQ_TOGGLE_DATE:
3714                 toggle_date();
3715                 break;
3717         case REQ_TOGGLE_AUTHOR:
3718                 toggle_author();
3719                 break;
3721         case REQ_TOGGLE_REV_GRAPH:
3722                 toggle_view_option(&opt_rev_graph, "revision graph display");
3723                 break;
3725         case REQ_TOGGLE_REFS:
3726                 toggle_view_option(&opt_show_refs, "reference display");
3727                 break;
3729         case REQ_TOGGLE_SORT_FIELD:
3730         case REQ_TOGGLE_SORT_ORDER:
3731                 report("Sorting is not yet supported for the %s view", view->name);
3732                 break;
3734         case REQ_SEARCH:
3735         case REQ_SEARCH_BACK:
3736                 search_view(view, request);
3737                 break;
3739         case REQ_FIND_NEXT:
3740         case REQ_FIND_PREV:
3741                 find_next(view, request);
3742                 break;
3744         case REQ_STOP_LOADING:
3745                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3746                         view = &views[i];
3747                         if (view->pipe)
3748                                 report("Stopped loading the %s view", view->name),
3749                         end_update(view, TRUE);
3750                 }
3751                 break;
3753         case REQ_SHOW_VERSION:
3754                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3755                 return TRUE;
3757         case REQ_SCREEN_REDRAW:
3758                 redraw_display(TRUE);
3759                 break;
3761         case REQ_EDIT:
3762                 report("Nothing to edit");
3763                 break;
3765         case REQ_ENTER:
3766                 report("Nothing to enter");
3767                 break;
3769         case REQ_VIEW_CLOSE:
3770                 /* XXX: Mark closed views by letting view->parent point to the
3771                  * view itself. Parents to closed view should never be
3772                  * followed. */
3773                 if (view->parent &&
3774                     view->parent->parent != view->parent) {
3775                         maximize_view(view->parent);
3776                         view->parent = view;
3777                         break;
3778                 }
3779                 /* Fall-through */
3780         case REQ_QUIT:
3781                 return FALSE;
3783         default:
3784                 report("Unknown key, press %s for help",
3785                        get_key(view->keymap, REQ_VIEW_HELP));
3786                 return TRUE;
3787         }
3789         return TRUE;
3793 /*
3794  * View backend utilities
3795  */
3797 enum sort_field {
3798         ORDERBY_NAME,
3799         ORDERBY_DATE,
3800         ORDERBY_AUTHOR,
3801 };
3803 struct sort_state {
3804         const enum sort_field *fields;
3805         size_t size, current;
3806         bool reverse;
3807 };
3809 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3810 #define get_sort_field(state) ((state).fields[(state).current])
3811 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3813 static void
3814 sort_view(struct view *view, enum request request, struct sort_state *state,
3815           int (*compare)(const void *, const void *))
3817         switch (request) {
3818         case REQ_TOGGLE_SORT_FIELD:
3819                 state->current = (state->current + 1) % state->size;
3820                 break;
3822         case REQ_TOGGLE_SORT_ORDER:
3823                 state->reverse = !state->reverse;
3824                 break;
3825         default:
3826                 die("Not a sort request");
3827         }
3829         qsort(view->line, view->lines, sizeof(*view->line), compare);
3830         redraw_view(view);
3833 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3835 /* Small author cache to reduce memory consumption. It uses binary
3836  * search to lookup or find place to position new entries. No entries
3837  * are ever freed. */
3838 static const char *
3839 get_author(const char *name)
3841         static const char **authors;
3842         static size_t authors_size;
3843         int from = 0, to = authors_size - 1;
3845         while (from <= to) {
3846                 size_t pos = (to + from) / 2;
3847                 int cmp = strcmp(name, authors[pos]);
3849                 if (!cmp)
3850                         return authors[pos];
3852                 if (cmp < 0)
3853                         to = pos - 1;
3854                 else
3855                         from = pos + 1;
3856         }
3858         if (!realloc_authors(&authors, authors_size, 1))
3859                 return NULL;
3860         name = strdup(name);
3861         if (!name)
3862                 return NULL;
3864         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3865         authors[from] = name;
3866         authors_size++;
3868         return name;
3871 static void
3872 parse_timesec(struct time *time, const char *sec)
3874         time->sec = (time_t) atol(sec);
3877 static void
3878 parse_timezone(struct time *time, const char *zone)
3880         long tz;
3882         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3883         tz += ('0' - zone[2]) * 60 * 60;
3884         tz += ('0' - zone[3]) * 60;
3885         tz += ('0' - zone[4]);
3887         if (zone[0] == '-')
3888                 tz = -tz;
3890         time->tz = tz;
3891         time->sec -= tz;
3894 /* Parse author lines where the name may be empty:
3895  *      author  <email@address.tld> 1138474660 +0100
3896  */
3897 static void
3898 parse_author_line(char *ident, const char **author, struct time *time)
3900         char *nameend = strchr(ident, '<');
3901         char *emailend = strchr(ident, '>');
3903         if (nameend && emailend)
3904                 *nameend = *emailend = 0;
3905         ident = chomp_string(ident);
3906         if (!*ident) {
3907                 if (nameend)
3908                         ident = chomp_string(nameend + 1);
3909                 if (!*ident)
3910                         ident = "Unknown";
3911         }
3913         *author = get_author(ident);
3915         /* Parse epoch and timezone */
3916         if (emailend && emailend[1] == ' ') {
3917                 char *secs = emailend + 2;
3918                 char *zone = strchr(secs, ' ');
3920                 parse_timesec(time, secs);
3922                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3923                         parse_timezone(time, zone + 1);
3924         }
3927 static bool
3928 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3930         char rev[SIZEOF_REV];
3931         const char *revlist_argv[] = {
3932                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3933         };
3934         struct menu_item *items;
3935         char text[SIZEOF_STR];
3936         bool ok = TRUE;
3937         int i;
3939         items = calloc(*parents + 1, sizeof(*items));
3940         if (!items)
3941                 return FALSE;
3943         for (i = 0; i < *parents; i++) {
3944                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3945                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3946                     !(items[i].text = strdup(text))) {
3947                         ok = FALSE;
3948                         break;
3949                 }
3950         }
3952         if (ok) {
3953                 *parents = 0;
3954                 ok = prompt_menu("Select parent", items, parents);
3955         }
3956         for (i = 0; items[i].text; i++)
3957                 free((char *) items[i].text);
3958         free(items);
3959         return ok;
3962 static bool
3963 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3965         char buf[SIZEOF_STR * 4];
3966         const char *revlist_argv[] = {
3967                 "git", "log", "--no-color", "-1",
3968                         "--pretty=format:%P", id, "--", path, NULL
3969         };
3970         int parents;
3972         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3973             (parents = strlen(buf) / 40) < 0) {
3974                 report("Failed to get parent information");
3975                 return FALSE;
3977         } else if (parents == 0) {
3978                 if (path)
3979                         report("Path '%s' does not exist in the parent", path);
3980                 else
3981                         report("The selected commit has no parents");
3982                 return FALSE;
3983         }
3985         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3986                 return FALSE;
3988         string_copy_rev(rev, &buf[41 * parents]);
3989         return TRUE;
3992 /*
3993  * Pager backend
3994  */
3996 static bool
3997 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3999         char text[SIZEOF_STR];
4001         if (opt_line_number && draw_lineno(view, lineno))
4002                 return TRUE;
4004         string_expand(text, sizeof(text), line->data, opt_tab_size);
4005         draw_text(view, line->type, text, TRUE);
4006         return TRUE;
4009 static bool
4010 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4012         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4013         char ref[SIZEOF_STR];
4015         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4016                 return TRUE;
4018         /* This is the only fatal call, since it can "corrupt" the buffer. */
4019         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4020                 return FALSE;
4022         return TRUE;
4025 static void
4026 add_pager_refs(struct view *view, struct line *line)
4028         char buf[SIZEOF_STR];
4029         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4030         struct ref_list *list;
4031         size_t bufpos = 0, i;
4032         const char *sep = "Refs: ";
4033         bool is_tag = FALSE;
4035         assert(line->type == LINE_COMMIT);
4037         list = get_ref_list(commit_id);
4038         if (!list) {
4039                 if (view == VIEW(REQ_VIEW_DIFF))
4040                         goto try_add_describe_ref;
4041                 return;
4042         }
4044         for (i = 0; i < list->size; i++) {
4045                 struct ref *ref = list->refs[i];
4046                 const char *fmt = ref->tag    ? "%s[%s]" :
4047                                   ref->remote ? "%s<%s>" : "%s%s";
4049                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4050                         return;
4051                 sep = ", ";
4052                 if (ref->tag)
4053                         is_tag = TRUE;
4054         }
4056         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4057 try_add_describe_ref:
4058                 /* Add <tag>-g<commit_id> "fake" reference. */
4059                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4060                         return;
4061         }
4063         if (bufpos == 0)
4064                 return;
4066         add_line_text(view, buf, LINE_PP_REFS);
4069 static bool
4070 pager_read(struct view *view, char *data)
4072         struct line *line;
4074         if (!data)
4075                 return TRUE;
4077         line = add_line_text(view, data, get_line_type(data));
4078         if (!line)
4079                 return FALSE;
4081         if (line->type == LINE_COMMIT &&
4082             (view == VIEW(REQ_VIEW_DIFF) ||
4083              view == VIEW(REQ_VIEW_LOG)))
4084                 add_pager_refs(view, line);
4086         return TRUE;
4089 static enum request
4090 pager_request(struct view *view, enum request request, struct line *line)
4092         int split = 0;
4094         if (request != REQ_ENTER)
4095                 return request;
4097         if (line->type == LINE_COMMIT &&
4098            (view == VIEW(REQ_VIEW_LOG) ||
4099             view == VIEW(REQ_VIEW_PAGER))) {
4100                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4101                 split = 1;
4102         }
4104         /* Always scroll the view even if it was split. That way
4105          * you can use Enter to scroll through the log view and
4106          * split open each commit diff. */
4107         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4109         /* FIXME: A minor workaround. Scrolling the view will call report("")
4110          * but if we are scrolling a non-current view this won't properly
4111          * update the view title. */
4112         if (split)
4113                 update_view_title(view);
4115         return REQ_NONE;
4118 static bool
4119 pager_grep(struct view *view, struct line *line)
4121         const char *text[] = { line->data, NULL };
4123         return grep_text(view, text);
4126 static void
4127 pager_select(struct view *view, struct line *line)
4129         if (line->type == LINE_COMMIT) {
4130                 char *text = (char *)line->data + STRING_SIZE("commit ");
4132                 if (view != VIEW(REQ_VIEW_PAGER))
4133                         string_copy_rev(view->ref, text);
4134                 string_copy_rev(ref_commit, text);
4135         }
4138 static struct view_ops pager_ops = {
4139         "line",
4140         NULL,
4141         NULL,
4142         pager_read,
4143         pager_draw,
4144         pager_request,
4145         pager_grep,
4146         pager_select,
4147 };
4149 static const char *log_argv[SIZEOF_ARG] = {
4150         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4151 };
4153 static enum request
4154 log_request(struct view *view, enum request request, struct line *line)
4156         switch (request) {
4157         case REQ_REFRESH:
4158                 load_refs();
4159                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4160                 return REQ_NONE;
4161         default:
4162                 return pager_request(view, request, line);
4163         }
4166 static struct view_ops log_ops = {
4167         "line",
4168         log_argv,
4169         NULL,
4170         pager_read,
4171         pager_draw,
4172         log_request,
4173         pager_grep,
4174         pager_select,
4175 };
4177 static const char *diff_argv[SIZEOF_ARG] = {
4178         "git", "show", "--pretty=fuller", "--no-color", "--root",
4179                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4180 };
4182 static struct view_ops diff_ops = {
4183         "line",
4184         diff_argv,
4185         NULL,
4186         pager_read,
4187         pager_draw,
4188         pager_request,
4189         pager_grep,
4190         pager_select,
4191 };
4193 /*
4194  * Help backend
4195  */
4197 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4199 static bool
4200 help_open_keymap_title(struct view *view, enum keymap keymap)
4202         struct line *line;
4204         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4205                                help_keymap_hidden[keymap] ? '+' : '-',
4206                                enum_name(keymap_table[keymap]));
4207         if (line)
4208                 line->other = keymap;
4210         return help_keymap_hidden[keymap];
4213 static void
4214 help_open_keymap(struct view *view, enum keymap keymap)
4216         const char *group = NULL;
4217         char buf[SIZEOF_STR];
4218         size_t bufpos;
4219         bool add_title = TRUE;
4220         int i;
4222         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4223                 const char *key = NULL;
4225                 if (req_info[i].request == REQ_NONE)
4226                         continue;
4228                 if (!req_info[i].request) {
4229                         group = req_info[i].help;
4230                         continue;
4231                 }
4233                 key = get_keys(keymap, req_info[i].request, TRUE);
4234                 if (!key || !*key)
4235                         continue;
4237                 if (add_title && help_open_keymap_title(view, keymap))
4238                         return;
4239                 add_title = FALSE;
4241                 if (group) {
4242                         add_line_text(view, group, LINE_HELP_GROUP);
4243                         group = NULL;
4244                 }
4246                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4247                                 enum_name(req_info[i]), req_info[i].help);
4248         }
4250         group = "External commands:";
4252         for (i = 0; i < run_requests; i++) {
4253                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4254                 const char *key;
4255                 int argc;
4257                 if (!req || req->keymap != keymap)
4258                         continue;
4260                 key = get_key_name(req->key);
4261                 if (!*key)
4262                         key = "(no key defined)";
4264                 if (add_title && help_open_keymap_title(view, keymap))
4265                         return;
4266                 if (group) {
4267                         add_line_text(view, group, LINE_HELP_GROUP);
4268                         group = NULL;
4269                 }
4271                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4272                         if (!string_format_from(buf, &bufpos, "%s%s",
4273                                                 argc ? " " : "", req->argv[argc]))
4274                                 return;
4276                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4277         }
4280 static bool
4281 help_open(struct view *view)
4283         enum keymap keymap;
4285         reset_view(view);
4286         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4287         add_line_text(view, "", LINE_DEFAULT);
4289         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4290                 help_open_keymap(view, keymap);
4292         return TRUE;
4295 static enum request
4296 help_request(struct view *view, enum request request, struct line *line)
4298         switch (request) {
4299         case REQ_ENTER:
4300                 if (line->type == LINE_HELP_KEYMAP) {
4301                         help_keymap_hidden[line->other] =
4302                                 !help_keymap_hidden[line->other];
4303                         view->p_restore = TRUE;
4304                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4305                 }
4307                 return REQ_NONE;
4308         default:
4309                 return pager_request(view, request, line);
4310         }
4313 static struct view_ops help_ops = {
4314         "line",
4315         NULL,
4316         help_open,
4317         NULL,
4318         pager_draw,
4319         help_request,
4320         pager_grep,
4321         pager_select,
4322 };
4325 /*
4326  * Tree backend
4327  */
4329 struct tree_stack_entry {
4330         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4331         unsigned long lineno;           /* Line number to restore */
4332         char *name;                     /* Position of name in opt_path */
4333 };
4335 /* The top of the path stack. */
4336 static struct tree_stack_entry *tree_stack = NULL;
4337 unsigned long tree_lineno = 0;
4339 static void
4340 pop_tree_stack_entry(void)
4342         struct tree_stack_entry *entry = tree_stack;
4344         tree_lineno = entry->lineno;
4345         entry->name[0] = 0;
4346         tree_stack = entry->prev;
4347         free(entry);
4350 static void
4351 push_tree_stack_entry(const char *name, unsigned long lineno)
4353         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4354         size_t pathlen = strlen(opt_path);
4356         if (!entry)
4357                 return;
4359         entry->prev = tree_stack;
4360         entry->name = opt_path + pathlen;
4361         tree_stack = entry;
4363         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4364                 pop_tree_stack_entry();
4365                 return;
4366         }
4368         /* Move the current line to the first tree entry. */
4369         tree_lineno = 1;
4370         entry->lineno = lineno;
4373 /* Parse output from git-ls-tree(1):
4374  *
4375  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4376  */
4378 #define SIZEOF_TREE_ATTR \
4379         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4381 #define SIZEOF_TREE_MODE \
4382         STRING_SIZE("100644 ")
4384 #define TREE_ID_OFFSET \
4385         STRING_SIZE("100644 blob ")
4387 struct tree_entry {
4388         char id[SIZEOF_REV];
4389         mode_t mode;
4390         struct time time;               /* Date from the author ident. */
4391         const char *author;             /* Author of the commit. */
4392         char name[1];
4393 };
4395 static const char *
4396 tree_path(const struct line *line)
4398         return ((struct tree_entry *) line->data)->name;
4401 static int
4402 tree_compare_entry(const struct line *line1, const struct line *line2)
4404         if (line1->type != line2->type)
4405                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4406         return strcmp(tree_path(line1), tree_path(line2));
4409 static const enum sort_field tree_sort_fields[] = {
4410         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4411 };
4412 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4414 static int
4415 tree_compare(const void *l1, const void *l2)
4417         const struct line *line1 = (const struct line *) l1;
4418         const struct line *line2 = (const struct line *) l2;
4419         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4420         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4422         if (line1->type == LINE_TREE_HEAD)
4423                 return -1;
4424         if (line2->type == LINE_TREE_HEAD)
4425                 return 1;
4427         switch (get_sort_field(tree_sort_state)) {
4428         case ORDERBY_DATE:
4429                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4431         case ORDERBY_AUTHOR:
4432                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4434         case ORDERBY_NAME:
4435         default:
4436                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4437         }
4441 static struct line *
4442 tree_entry(struct view *view, enum line_type type, const char *path,
4443            const char *mode, const char *id)
4445         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4446         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4448         if (!entry || !line) {
4449                 free(entry);
4450                 return NULL;
4451         }
4453         strncpy(entry->name, path, strlen(path));
4454         if (mode)
4455                 entry->mode = strtoul(mode, NULL, 8);
4456         if (id)
4457                 string_copy_rev(entry->id, id);
4459         return line;
4462 static bool
4463 tree_read_date(struct view *view, char *text, bool *read_date)
4465         static const char *author_name;
4466         static struct time author_time;
4468         if (!text && *read_date) {
4469                 *read_date = FALSE;
4470                 return TRUE;
4472         } else if (!text) {
4473                 char *path = *opt_path ? opt_path : ".";
4474                 /* Find next entry to process */
4475                 const char *log_file[] = {
4476                         "git", "log", "--no-color", "--pretty=raw",
4477                                 "--cc", "--raw", view->id, "--", path, NULL
4478                 };
4479                 struct io io = {};
4481                 if (!view->lines) {
4482                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4483                         report("Tree is empty");
4484                         return TRUE;
4485                 }
4487                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4488                         report("Failed to load tree data");
4489                         return TRUE;
4490                 }
4492                 io_done(view->pipe);
4493                 view->io = io;
4494                 *read_date = TRUE;
4495                 return FALSE;
4497         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4498                 parse_author_line(text + STRING_SIZE("author "),
4499                                   &author_name, &author_time);
4501         } else if (*text == ':') {
4502                 char *pos;
4503                 size_t annotated = 1;
4504                 size_t i;
4506                 pos = strchr(text, '\t');
4507                 if (!pos)
4508                         return TRUE;
4509                 text = pos + 1;
4510                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4511                         text += strlen(opt_path);
4512                 pos = strchr(text, '/');
4513                 if (pos)
4514                         *pos = 0;
4516                 for (i = 1; i < view->lines; i++) {
4517                         struct line *line = &view->line[i];
4518                         struct tree_entry *entry = line->data;
4520                         annotated += !!entry->author;
4521                         if (entry->author || strcmp(entry->name, text))
4522                                 continue;
4524                         entry->author = author_name;
4525                         entry->time = author_time;
4526                         line->dirty = 1;
4527                         break;
4528                 }
4530                 if (annotated == view->lines)
4531                         io_kill(view->pipe);
4532         }
4533         return TRUE;
4536 static bool
4537 tree_read(struct view *view, char *text)
4539         static bool read_date = FALSE;
4540         struct tree_entry *data;
4541         struct line *entry, *line;
4542         enum line_type type;
4543         size_t textlen = text ? strlen(text) : 0;
4544         char *path = text + SIZEOF_TREE_ATTR;
4546         if (read_date || !text)
4547                 return tree_read_date(view, text, &read_date);
4549         if (textlen <= SIZEOF_TREE_ATTR)
4550                 return FALSE;
4551         if (view->lines == 0 &&
4552             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4553                 return FALSE;
4555         /* Strip the path part ... */
4556         if (*opt_path) {
4557                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4558                 size_t striplen = strlen(opt_path);
4560                 if (pathlen > striplen)
4561                         memmove(path, path + striplen,
4562                                 pathlen - striplen + 1);
4564                 /* Insert "link" to parent directory. */
4565                 if (view->lines == 1 &&
4566                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4567                         return FALSE;
4568         }
4570         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4571         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4572         if (!entry)
4573                 return FALSE;
4574         data = entry->data;
4576         /* Skip "Directory ..." and ".." line. */
4577         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4578                 if (tree_compare_entry(line, entry) <= 0)
4579                         continue;
4581                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4583                 line->data = data;
4584                 line->type = type;
4585                 for (; line <= entry; line++)
4586                         line->dirty = line->cleareol = 1;
4587                 return TRUE;
4588         }
4590         if (tree_lineno > view->lineno) {
4591                 view->lineno = tree_lineno;
4592                 tree_lineno = 0;
4593         }
4595         return TRUE;
4598 static bool
4599 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4601         struct tree_entry *entry = line->data;
4603         if (line->type == LINE_TREE_HEAD) {
4604                 if (draw_text(view, line->type, "Directory path /", TRUE))
4605                         return TRUE;
4606         } else {
4607                 if (draw_mode(view, entry->mode))
4608                         return TRUE;
4610                 if (opt_author && draw_author(view, entry->author))
4611                         return TRUE;
4613                 if (opt_date && draw_date(view, &entry->time))
4614                         return TRUE;
4615         }
4616         if (draw_text(view, line->type, entry->name, TRUE))
4617                 return TRUE;
4618         return TRUE;
4621 static void
4622 open_blob_editor()
4624         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4625         int fd = mkstemp(file);
4627         if (fd == -1)
4628                 report("Failed to create temporary file");
4629         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4630                 report("Failed to save blob data to file");
4631         else
4632                 open_editor(file);
4633         if (fd != -1)
4634                 unlink(file);
4637 static enum request
4638 tree_request(struct view *view, enum request request, struct line *line)
4640         enum open_flags flags;
4642         switch (request) {
4643         case REQ_VIEW_BLAME:
4644                 if (line->type != LINE_TREE_FILE) {
4645                         report("Blame only supported for files");
4646                         return REQ_NONE;
4647                 }
4649                 string_copy(opt_ref, view->vid);
4650                 return request;
4652         case REQ_EDIT:
4653                 if (line->type != LINE_TREE_FILE) {
4654                         report("Edit only supported for files");
4655                 } else if (!is_head_commit(view->vid)) {
4656                         open_blob_editor();
4657                 } else {
4658                         open_editor(opt_file);
4659                 }
4660                 return REQ_NONE;
4662         case REQ_TOGGLE_SORT_FIELD:
4663         case REQ_TOGGLE_SORT_ORDER:
4664                 sort_view(view, request, &tree_sort_state, tree_compare);
4665                 return REQ_NONE;
4667         case REQ_PARENT:
4668                 if (!*opt_path) {
4669                         /* quit view if at top of tree */
4670                         return REQ_VIEW_CLOSE;
4671                 }
4672                 /* fake 'cd  ..' */
4673                 line = &view->line[1];
4674                 break;
4676         case REQ_ENTER:
4677                 break;
4679         default:
4680                 return request;
4681         }
4683         /* Cleanup the stack if the tree view is at a different tree. */
4684         while (!*opt_path && tree_stack)
4685                 pop_tree_stack_entry();
4687         switch (line->type) {
4688         case LINE_TREE_DIR:
4689                 /* Depending on whether it is a subdirectory or parent link
4690                  * mangle the path buffer. */
4691                 if (line == &view->line[1] && *opt_path) {
4692                         pop_tree_stack_entry();
4694                 } else {
4695                         const char *basename = tree_path(line);
4697                         push_tree_stack_entry(basename, view->lineno);
4698                 }
4700                 /* Trees and subtrees share the same ID, so they are not not
4701                  * unique like blobs. */
4702                 flags = OPEN_RELOAD;
4703                 request = REQ_VIEW_TREE;
4704                 break;
4706         case LINE_TREE_FILE:
4707                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4708                 request = REQ_VIEW_BLOB;
4709                 break;
4711         default:
4712                 return REQ_NONE;
4713         }
4715         open_view(view, request, flags);
4716         if (request == REQ_VIEW_TREE)
4717                 view->lineno = tree_lineno;
4719         return REQ_NONE;
4722 static bool
4723 tree_grep(struct view *view, struct line *line)
4725         struct tree_entry *entry = line->data;
4726         const char *text[] = {
4727                 entry->name,
4728                 opt_author ? entry->author : "",
4729                 mkdate(&entry->time, opt_date),
4730                 NULL
4731         };
4733         return grep_text(view, text);
4736 static void
4737 tree_select(struct view *view, struct line *line)
4739         struct tree_entry *entry = line->data;
4741         if (line->type == LINE_TREE_FILE) {
4742                 string_copy_rev(ref_blob, entry->id);
4743                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4745         } else if (line->type != LINE_TREE_DIR) {
4746                 return;
4747         }
4749         string_copy_rev(view->ref, entry->id);
4752 static bool
4753 tree_prepare(struct view *view)
4755         if (view->lines == 0 && opt_prefix[0]) {
4756                 char *pos = opt_prefix;
4758                 while (pos && *pos) {
4759                         char *end = strchr(pos, '/');
4761                         if (end)
4762                                 *end = 0;
4763                         push_tree_stack_entry(pos, 0);
4764                         pos = end;
4765                         if (end) {
4766                                 *end = '/';
4767                                 pos++;
4768                         }
4769                 }
4771         } else if (strcmp(view->vid, view->id)) {
4772                 opt_path[0] = 0;
4773         }
4775         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4778 static const char *tree_argv[SIZEOF_ARG] = {
4779         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4780 };
4782 static struct view_ops tree_ops = {
4783         "file",
4784         tree_argv,
4785         NULL,
4786         tree_read,
4787         tree_draw,
4788         tree_request,
4789         tree_grep,
4790         tree_select,
4791         tree_prepare,
4792 };
4794 static bool
4795 blob_read(struct view *view, char *line)
4797         if (!line)
4798                 return TRUE;
4799         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4802 static enum request
4803 blob_request(struct view *view, enum request request, struct line *line)
4805         switch (request) {
4806         case REQ_EDIT:
4807                 open_blob_editor();
4808                 return REQ_NONE;
4809         default:
4810                 return pager_request(view, request, line);
4811         }
4814 static const char *blob_argv[SIZEOF_ARG] = {
4815         "git", "cat-file", "blob", "%(blob)", NULL
4816 };
4818 static struct view_ops blob_ops = {
4819         "line",
4820         blob_argv,
4821         NULL,
4822         blob_read,
4823         pager_draw,
4824         blob_request,
4825         pager_grep,
4826         pager_select,
4827 };
4829 /*
4830  * Blame backend
4831  *
4832  * Loading the blame view is a two phase job:
4833  *
4834  *  1. File content is read either using opt_file from the
4835  *     filesystem or using git-cat-file.
4836  *  2. Then blame information is incrementally added by
4837  *     reading output from git-blame.
4838  */
4840 static const char *blame_head_argv[] = {
4841         "git", "blame", "--incremental", "--", "%(file)", NULL
4842 };
4844 static const char *blame_ref_argv[] = {
4845         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4846 };
4848 static const char *blame_cat_file_argv[] = {
4849         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4850 };
4852 struct blame_commit {
4853         char id[SIZEOF_REV];            /* SHA1 ID. */
4854         char title[128];                /* First line of the commit message. */
4855         const char *author;             /* Author of the commit. */
4856         struct time time;               /* Date from the author ident. */
4857         char filename[128];             /* Name of file. */
4858         bool has_previous;              /* Was a "previous" line detected. */
4859 };
4861 struct blame {
4862         struct blame_commit *commit;
4863         unsigned long lineno;
4864         char text[1];
4865 };
4867 static bool
4868 blame_open(struct view *view)
4870         char path[SIZEOF_STR];
4872         if (!view->parent && *opt_prefix) {
4873                 string_copy(path, opt_file);
4874                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4875                         return FALSE;
4876         }
4878         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4879                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4880                         return FALSE;
4881         }
4883         setup_update(view, opt_file);
4884         string_format(view->ref, "%s ...", opt_file);
4886         return TRUE;
4889 static struct blame_commit *
4890 get_blame_commit(struct view *view, const char *id)
4892         size_t i;
4894         for (i = 0; i < view->lines; i++) {
4895                 struct blame *blame = view->line[i].data;
4897                 if (!blame->commit)
4898                         continue;
4900                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4901                         return blame->commit;
4902         }
4904         {
4905                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4907                 if (commit)
4908                         string_ncopy(commit->id, id, SIZEOF_REV);
4909                 return commit;
4910         }
4913 static bool
4914 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4916         const char *pos = *posref;
4918         *posref = NULL;
4919         pos = strchr(pos + 1, ' ');
4920         if (!pos || !isdigit(pos[1]))
4921                 return FALSE;
4922         *number = atoi(pos + 1);
4923         if (*number < min || *number > max)
4924                 return FALSE;
4926         *posref = pos;
4927         return TRUE;
4930 static struct blame_commit *
4931 parse_blame_commit(struct view *view, const char *text, int *blamed)
4933         struct blame_commit *commit;
4934         struct blame *blame;
4935         const char *pos = text + SIZEOF_REV - 2;
4936         size_t orig_lineno = 0;
4937         size_t lineno;
4938         size_t group;
4940         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4941                 return NULL;
4943         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4944             !parse_number(&pos, &lineno, 1, view->lines) ||
4945             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4946                 return NULL;
4948         commit = get_blame_commit(view, text);
4949         if (!commit)
4950                 return NULL;
4952         *blamed += group;
4953         while (group--) {
4954                 struct line *line = &view->line[lineno + group - 1];
4956                 blame = line->data;
4957                 blame->commit = commit;
4958                 blame->lineno = orig_lineno + group - 1;
4959                 line->dirty = 1;
4960         }
4962         return commit;
4965 static bool
4966 blame_read_file(struct view *view, const char *line, bool *read_file)
4968         if (!line) {
4969                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4970                 struct io io = {};
4972                 if (view->lines == 0 && !view->parent)
4973                         die("No blame exist for %s", view->vid);
4975                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4976                         report("Failed to load blame data");
4977                         return TRUE;
4978                 }
4980                 io_done(view->pipe);
4981                 view->io = io;
4982                 *read_file = FALSE;
4983                 return FALSE;
4985         } else {
4986                 size_t linelen = strlen(line);
4987                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4989                 if (!blame)
4990                         return FALSE;
4992                 blame->commit = NULL;
4993                 strncpy(blame->text, line, linelen);
4994                 blame->text[linelen] = 0;
4995                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4996         }
4999 static bool
5000 match_blame_header(const char *name, char **line)
5002         size_t namelen = strlen(name);
5003         bool matched = !strncmp(name, *line, namelen);
5005         if (matched)
5006                 *line += namelen;
5008         return matched;
5011 static bool
5012 blame_read(struct view *view, char *line)
5014         static struct blame_commit *commit = NULL;
5015         static int blamed = 0;
5016         static bool read_file = TRUE;
5018         if (read_file)
5019                 return blame_read_file(view, line, &read_file);
5021         if (!line) {
5022                 /* Reset all! */
5023                 commit = NULL;
5024                 blamed = 0;
5025                 read_file = TRUE;
5026                 string_format(view->ref, "%s", view->vid);
5027                 if (view_is_displayed(view)) {
5028                         update_view_title(view);
5029                         redraw_view_from(view, 0);
5030                 }
5031                 return TRUE;
5032         }
5034         if (!commit) {
5035                 commit = parse_blame_commit(view, line, &blamed);
5036                 string_format(view->ref, "%s %2d%%", view->vid,
5037                               view->lines ? blamed * 100 / view->lines : 0);
5039         } else if (match_blame_header("author ", &line)) {
5040                 commit->author = get_author(line);
5042         } else if (match_blame_header("author-time ", &line)) {
5043                 parse_timesec(&commit->time, line);
5045         } else if (match_blame_header("author-tz ", &line)) {
5046                 parse_timezone(&commit->time, line);
5048         } else if (match_blame_header("summary ", &line)) {
5049                 string_ncopy(commit->title, line, strlen(line));
5051         } else if (match_blame_header("previous ", &line)) {
5052                 commit->has_previous = TRUE;
5054         } else if (match_blame_header("filename ", &line)) {
5055                 string_ncopy(commit->filename, line, strlen(line));
5056                 commit = NULL;
5057         }
5059         return TRUE;
5062 static bool
5063 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5065         struct blame *blame = line->data;
5066         struct time *time = NULL;
5067         const char *id = NULL, *author = NULL;
5068         char text[SIZEOF_STR];
5070         if (blame->commit && *blame->commit->filename) {
5071                 id = blame->commit->id;
5072                 author = blame->commit->author;
5073                 time = &blame->commit->time;
5074         }
5076         if (opt_date && draw_date(view, time))
5077                 return TRUE;
5079         if (opt_author && draw_author(view, author))
5080                 return TRUE;
5082         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5083                 return TRUE;
5085         if (draw_lineno(view, lineno))
5086                 return TRUE;
5088         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5089         draw_text(view, LINE_DEFAULT, text, TRUE);
5090         return TRUE;
5093 static bool
5094 check_blame_commit(struct blame *blame, bool check_null_id)
5096         if (!blame->commit)
5097                 report("Commit data not loaded yet");
5098         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5099                 report("No commit exist for the selected line");
5100         else
5101                 return TRUE;
5102         return FALSE;
5105 static void
5106 setup_blame_parent_line(struct view *view, struct blame *blame)
5108         const char *diff_tree_argv[] = {
5109                 "git", "diff-tree", "-U0", blame->commit->id,
5110                         "--", blame->commit->filename, NULL
5111         };
5112         struct io io = {};
5113         int parent_lineno = -1;
5114         int blamed_lineno = -1;
5115         char *line;
5117         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5118                 return;
5120         while ((line = io_get(&io, '\n', TRUE))) {
5121                 if (*line == '@') {
5122                         char *pos = strchr(line, '+');
5124                         parent_lineno = atoi(line + 4);
5125                         if (pos)
5126                                 blamed_lineno = atoi(pos + 1);
5128                 } else if (*line == '+' && parent_lineno != -1) {
5129                         if (blame->lineno == blamed_lineno - 1 &&
5130                             !strcmp(blame->text, line + 1)) {
5131                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5132                                 break;
5133                         }
5134                         blamed_lineno++;
5135                 }
5136         }
5138         io_done(&io);
5141 static enum request
5142 blame_request(struct view *view, enum request request, struct line *line)
5144         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5145         struct blame *blame = line->data;
5147         switch (request) {
5148         case REQ_VIEW_BLAME:
5149                 if (check_blame_commit(blame, TRUE)) {
5150                         string_copy(opt_ref, blame->commit->id);
5151                         string_copy(opt_file, blame->commit->filename);
5152                         if (blame->lineno)
5153                                 view->lineno = blame->lineno;
5154                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5155                 }
5156                 break;
5158         case REQ_PARENT:
5159                 if (check_blame_commit(blame, TRUE) &&
5160                     select_commit_parent(blame->commit->id, opt_ref,
5161                                          blame->commit->filename)) {
5162                         string_copy(opt_file, blame->commit->filename);
5163                         setup_blame_parent_line(view, blame);
5164                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5165                 }
5166                 break;
5168         case REQ_ENTER:
5169                 if (!check_blame_commit(blame, FALSE))
5170                         break;
5172                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5173                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5174                         break;
5176                 if (!strcmp(blame->commit->id, NULL_ID)) {
5177                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5178                         const char *diff_index_argv[] = {
5179                                 "git", "diff-index", "--root", "--patch-with-stat",
5180                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5181                         };
5183                         if (!blame->commit->has_previous) {
5184                                 diff_index_argv[1] = "diff";
5185                                 diff_index_argv[2] = "--no-color";
5186                                 diff_index_argv[6] = "--";
5187                                 diff_index_argv[7] = "/dev/null";
5188                         }
5190                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5191                                 report("Failed to allocate diff command");
5192                                 break;
5193                         }
5194                         flags |= OPEN_PREPARED;
5195                 }
5197                 open_view(view, REQ_VIEW_DIFF, flags);
5198                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5199                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5200                 break;
5202         default:
5203                 return request;
5204         }
5206         return REQ_NONE;
5209 static bool
5210 blame_grep(struct view *view, struct line *line)
5212         struct blame *blame = line->data;
5213         struct blame_commit *commit = blame->commit;
5214         const char *text[] = {
5215                 blame->text,
5216                 commit ? commit->title : "",
5217                 commit ? commit->id : "",
5218                 commit && opt_author ? commit->author : "",
5219                 commit ? mkdate(&commit->time, opt_date) : "",
5220                 NULL
5221         };
5223         return grep_text(view, text);
5226 static void
5227 blame_select(struct view *view, struct line *line)
5229         struct blame *blame = line->data;
5230         struct blame_commit *commit = blame->commit;
5232         if (!commit)
5233                 return;
5235         if (!strcmp(commit->id, NULL_ID))
5236                 string_ncopy(ref_commit, "HEAD", 4);
5237         else
5238                 string_copy_rev(ref_commit, commit->id);
5241 static struct view_ops blame_ops = {
5242         "line",
5243         NULL,
5244         blame_open,
5245         blame_read,
5246         blame_draw,
5247         blame_request,
5248         blame_grep,
5249         blame_select,
5250 };
5252 /*
5253  * Branch backend
5254  */
5256 struct branch {
5257         const char *author;             /* Author of the last commit. */
5258         struct time time;               /* Date of the last activity. */
5259         const struct ref *ref;          /* Name and commit ID information. */
5260 };
5262 static const struct ref branch_all;
5264 static const enum sort_field branch_sort_fields[] = {
5265         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5266 };
5267 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5269 static int
5270 branch_compare(const void *l1, const void *l2)
5272         const struct branch *branch1 = ((const struct line *) l1)->data;
5273         const struct branch *branch2 = ((const struct line *) l2)->data;
5275         switch (get_sort_field(branch_sort_state)) {
5276         case ORDERBY_DATE:
5277                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5279         case ORDERBY_AUTHOR:
5280                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5282         case ORDERBY_NAME:
5283         default:
5284                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5285         }
5288 static bool
5289 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5291         struct branch *branch = line->data;
5292         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5294         if (opt_date && draw_date(view, &branch->time))
5295                 return TRUE;
5297         if (opt_author && draw_author(view, branch->author))
5298                 return TRUE;
5300         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5301         return TRUE;
5304 static enum request
5305 branch_request(struct view *view, enum request request, struct line *line)
5307         struct branch *branch = line->data;
5309         switch (request) {
5310         case REQ_REFRESH:
5311                 load_refs();
5312                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5313                 return REQ_NONE;
5315         case REQ_TOGGLE_SORT_FIELD:
5316         case REQ_TOGGLE_SORT_ORDER:
5317                 sort_view(view, request, &branch_sort_state, branch_compare);
5318                 return REQ_NONE;
5320         case REQ_ENTER:
5321                 if (branch->ref == &branch_all) {
5322                         const char *all_branches_argv[] = {
5323                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5324                                       "--topo-order", "--all", NULL
5325                         };
5326                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5328                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5329                                 report("Failed to load view of all branches");
5330                                 return REQ_NONE;
5331                         }
5332                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5333                 } else {
5334                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5335                 }
5336                 return REQ_NONE;
5338         default:
5339                 return request;
5340         }
5343 static bool
5344 branch_read(struct view *view, char *line)
5346         static char id[SIZEOF_REV];
5347         struct branch *reference;
5348         size_t i;
5350         if (!line)
5351                 return TRUE;
5353         switch (get_line_type(line)) {
5354         case LINE_COMMIT:
5355                 string_copy_rev(id, line + STRING_SIZE("commit "));
5356                 return TRUE;
5358         case LINE_AUTHOR:
5359                 for (i = 0, reference = NULL; i < view->lines; i++) {
5360                         struct branch *branch = view->line[i].data;
5362                         if (strcmp(branch->ref->id, id))
5363                                 continue;
5365                         view->line[i].dirty = TRUE;
5366                         if (reference) {
5367                                 branch->author = reference->author;
5368                                 branch->time = reference->time;
5369                                 continue;
5370                         }
5372                         parse_author_line(line + STRING_SIZE("author "),
5373                                           &branch->author, &branch->time);
5374                         reference = branch;
5375                 }
5376                 return TRUE;
5378         default:
5379                 return TRUE;
5380         }
5384 static bool
5385 branch_open_visitor(void *data, const struct ref *ref)
5387         struct view *view = data;
5388         struct branch *branch;
5390         if (ref->tag || ref->ltag || ref->remote)
5391                 return TRUE;
5393         branch = calloc(1, sizeof(*branch));
5394         if (!branch)
5395                 return FALSE;
5397         branch->ref = ref;
5398         return !!add_line_data(view, branch, LINE_DEFAULT);
5401 static bool
5402 branch_open(struct view *view)
5404         const char *branch_log[] = {
5405                 "git", "log", "--no-color", "--pretty=raw",
5406                         "--simplify-by-decoration", "--all", NULL
5407         };
5409         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5410                 report("Failed to load branch data");
5411                 return TRUE;
5412         }
5414         setup_update(view, view->id);
5415         branch_open_visitor(view, &branch_all);
5416         foreach_ref(branch_open_visitor, view);
5417         view->p_restore = TRUE;
5419         return TRUE;
5422 static bool
5423 branch_grep(struct view *view, struct line *line)
5425         struct branch *branch = line->data;
5426         const char *text[] = {
5427                 branch->ref->name,
5428                 branch->author,
5429                 NULL
5430         };
5432         return grep_text(view, text);
5435 static void
5436 branch_select(struct view *view, struct line *line)
5438         struct branch *branch = line->data;
5440         string_copy_rev(view->ref, branch->ref->id);
5441         string_copy_rev(ref_commit, branch->ref->id);
5442         string_copy_rev(ref_head, branch->ref->id);
5445 static struct view_ops branch_ops = {
5446         "branch",
5447         NULL,
5448         branch_open,
5449         branch_read,
5450         branch_draw,
5451         branch_request,
5452         branch_grep,
5453         branch_select,
5454 };
5456 /*
5457  * Status backend
5458  */
5460 struct status {
5461         char status;
5462         struct {
5463                 mode_t mode;
5464                 char rev[SIZEOF_REV];
5465                 char name[SIZEOF_STR];
5466         } old;
5467         struct {
5468                 mode_t mode;
5469                 char rev[SIZEOF_REV];
5470                 char name[SIZEOF_STR];
5471         } new;
5472 };
5474 static char status_onbranch[SIZEOF_STR];
5475 static struct status stage_status;
5476 static enum line_type stage_line_type;
5477 static size_t stage_chunks;
5478 static int *stage_chunk;
5480 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5482 /* This should work even for the "On branch" line. */
5483 static inline bool
5484 status_has_none(struct view *view, struct line *line)
5486         return line < view->line + view->lines && !line[1].data;
5489 /* Get fields from the diff line:
5490  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5491  */
5492 static inline bool
5493 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5495         const char *old_mode = buf +  1;
5496         const char *new_mode = buf +  8;
5497         const char *old_rev  = buf + 15;
5498         const char *new_rev  = buf + 56;
5499         const char *status   = buf + 97;
5501         if (bufsize < 98 ||
5502             old_mode[-1] != ':' ||
5503             new_mode[-1] != ' ' ||
5504             old_rev[-1]  != ' ' ||
5505             new_rev[-1]  != ' ' ||
5506             status[-1]   != ' ')
5507                 return FALSE;
5509         file->status = *status;
5511         string_copy_rev(file->old.rev, old_rev);
5512         string_copy_rev(file->new.rev, new_rev);
5514         file->old.mode = strtoul(old_mode, NULL, 8);
5515         file->new.mode = strtoul(new_mode, NULL, 8);
5517         file->old.name[0] = file->new.name[0] = 0;
5519         return TRUE;
5522 static bool
5523 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5525         struct status *unmerged = NULL;
5526         char *buf;
5527         struct io io = {};
5529         if (!io_run(&io, argv, opt_cdup, IO_RD))
5530                 return FALSE;
5532         add_line_data(view, NULL, type);
5534         while ((buf = io_get(&io, 0, TRUE))) {
5535                 struct status *file = unmerged;
5537                 if (!file) {
5538                         file = calloc(1, sizeof(*file));
5539                         if (!file || !add_line_data(view, file, type))
5540                                 goto error_out;
5541                 }
5543                 /* Parse diff info part. */
5544                 if (status) {
5545                         file->status = status;
5546                         if (status == 'A')
5547                                 string_copy(file->old.rev, NULL_ID);
5549                 } else if (!file->status || file == unmerged) {
5550                         if (!status_get_diff(file, buf, strlen(buf)))
5551                                 goto error_out;
5553                         buf = io_get(&io, 0, TRUE);
5554                         if (!buf)
5555                                 break;
5557                         /* Collapse all modified entries that follow an
5558                          * associated unmerged entry. */
5559                         if (unmerged == file) {
5560                                 unmerged->status = 'U';
5561                                 unmerged = NULL;
5562                         } else if (file->status == 'U') {
5563                                 unmerged = file;
5564                         }
5565                 }
5567                 /* Grab the old name for rename/copy. */
5568                 if (!*file->old.name &&
5569                     (file->status == 'R' || file->status == 'C')) {
5570                         string_ncopy(file->old.name, buf, strlen(buf));
5572                         buf = io_get(&io, 0, TRUE);
5573                         if (!buf)
5574                                 break;
5575                 }
5577                 /* git-ls-files just delivers a NUL separated list of
5578                  * file names similar to the second half of the
5579                  * git-diff-* output. */
5580                 string_ncopy(file->new.name, buf, strlen(buf));
5581                 if (!*file->old.name)
5582                         string_copy(file->old.name, file->new.name);
5583                 file = NULL;
5584         }
5586         if (io_error(&io)) {
5587 error_out:
5588                 io_done(&io);
5589                 return FALSE;
5590         }
5592         if (!view->line[view->lines - 1].data)
5593                 add_line_data(view, NULL, LINE_STAT_NONE);
5595         io_done(&io);
5596         return TRUE;
5599 /* Don't show unmerged entries in the staged section. */
5600 static const char *status_diff_index_argv[] = {
5601         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5602                              "--cached", "-M", "HEAD", NULL
5603 };
5605 static const char *status_diff_files_argv[] = {
5606         "git", "diff-files", "-z", NULL
5607 };
5609 static const char *status_list_other_argv[] = {
5610         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5611 };
5613 static const char *status_list_no_head_argv[] = {
5614         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5615 };
5617 static const char *update_index_argv[] = {
5618         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5619 };
5621 /* Restore the previous line number to stay in the context or select a
5622  * line with something that can be updated. */
5623 static void
5624 status_restore(struct view *view)
5626         if (view->p_lineno >= view->lines)
5627                 view->p_lineno = view->lines - 1;
5628         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5629                 view->p_lineno++;
5630         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5631                 view->p_lineno--;
5633         /* If the above fails, always skip the "On branch" line. */
5634         if (view->p_lineno < view->lines)
5635                 view->lineno = view->p_lineno;
5636         else
5637                 view->lineno = 1;
5639         if (view->lineno < view->offset)
5640                 view->offset = view->lineno;
5641         else if (view->offset + view->height <= view->lineno)
5642                 view->offset = view->lineno - view->height + 1;
5644         view->p_restore = FALSE;
5647 static void
5648 status_update_onbranch(void)
5650         static const char *paths[][2] = {
5651                 { "rebase-apply/rebasing",      "Rebasing" },
5652                 { "rebase-apply/applying",      "Applying mailbox" },
5653                 { "rebase-apply/",              "Rebasing mailbox" },
5654                 { "rebase-merge/interactive",   "Interactive rebase" },
5655                 { "rebase-merge/",              "Rebase merge" },
5656                 { "MERGE_HEAD",                 "Merging" },
5657                 { "BISECT_LOG",                 "Bisecting" },
5658                 { "HEAD",                       "On branch" },
5659         };
5660         char buf[SIZEOF_STR];
5661         struct stat stat;
5662         int i;
5664         if (is_initial_commit()) {
5665                 string_copy(status_onbranch, "Initial commit");
5666                 return;
5667         }
5669         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5670                 char *head = opt_head;
5672                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5673                     lstat(buf, &stat) < 0)
5674                         continue;
5676                 if (!*opt_head) {
5677                         struct io io = {};
5679                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5680                             io_read_buf(&io, buf, sizeof(buf))) {
5681                                 head = buf;
5682                                 if (!prefixcmp(head, "refs/heads/"))
5683                                         head += STRING_SIZE("refs/heads/");
5684                         }
5685                 }
5687                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5688                         string_copy(status_onbranch, opt_head);
5689                 return;
5690         }
5692         string_copy(status_onbranch, "Not currently on any branch");
5695 /* First parse staged info using git-diff-index(1), then parse unstaged
5696  * info using git-diff-files(1), and finally untracked files using
5697  * git-ls-files(1). */
5698 static bool
5699 status_open(struct view *view)
5701         reset_view(view);
5703         add_line_data(view, NULL, LINE_STAT_HEAD);
5704         status_update_onbranch();
5706         io_run_bg(update_index_argv);
5708         if (is_initial_commit()) {
5709                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5710                         return FALSE;
5711         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5712                 return FALSE;
5713         }
5715         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5716             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5717                 return FALSE;
5719         /* Restore the exact position or use the specialized restore
5720          * mode? */
5721         if (!view->p_restore)
5722                 status_restore(view);
5723         return TRUE;
5726 static bool
5727 status_draw(struct view *view, struct line *line, unsigned int lineno)
5729         struct status *status = line->data;
5730         enum line_type type;
5731         const char *text;
5733         if (!status) {
5734                 switch (line->type) {
5735                 case LINE_STAT_STAGED:
5736                         type = LINE_STAT_SECTION;
5737                         text = "Changes to be committed:";
5738                         break;
5740                 case LINE_STAT_UNSTAGED:
5741                         type = LINE_STAT_SECTION;
5742                         text = "Changed but not updated:";
5743                         break;
5745                 case LINE_STAT_UNTRACKED:
5746                         type = LINE_STAT_SECTION;
5747                         text = "Untracked files:";
5748                         break;
5750                 case LINE_STAT_NONE:
5751                         type = LINE_DEFAULT;
5752                         text = "  (no files)";
5753                         break;
5755                 case LINE_STAT_HEAD:
5756                         type = LINE_STAT_HEAD;
5757                         text = status_onbranch;
5758                         break;
5760                 default:
5761                         return FALSE;
5762                 }
5763         } else {
5764                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5766                 buf[0] = status->status;
5767                 if (draw_text(view, line->type, buf, TRUE))
5768                         return TRUE;
5769                 type = LINE_DEFAULT;
5770                 text = status->new.name;
5771         }
5773         draw_text(view, type, text, TRUE);
5774         return TRUE;
5777 static enum request
5778 status_load_error(struct view *view, struct view *stage, const char *path)
5780         if (displayed_views() == 2 || display[current_view] != view)
5781                 maximize_view(view);
5782         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5783         return REQ_NONE;
5786 static enum request
5787 status_enter(struct view *view, struct line *line)
5789         struct status *status = line->data;
5790         const char *oldpath = status ? status->old.name : NULL;
5791         /* Diffs for unmerged entries are empty when passing the new
5792          * path, so leave it empty. */
5793         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5794         const char *info;
5795         enum open_flags split;
5796         struct view *stage = VIEW(REQ_VIEW_STAGE);
5798         if (line->type == LINE_STAT_NONE ||
5799             (!status && line[1].type == LINE_STAT_NONE)) {
5800                 report("No file to diff");
5801                 return REQ_NONE;
5802         }
5804         switch (line->type) {
5805         case LINE_STAT_STAGED:
5806                 if (is_initial_commit()) {
5807                         const char *no_head_diff_argv[] = {
5808                                 "git", "diff", "--no-color", "--patch-with-stat",
5809                                         "--", "/dev/null", newpath, NULL
5810                         };
5812                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5813                                 return status_load_error(view, stage, newpath);
5814                 } else {
5815                         const char *index_show_argv[] = {
5816                                 "git", "diff-index", "--root", "--patch-with-stat",
5817                                         "-C", "-M", "--cached", "HEAD", "--",
5818                                         oldpath, newpath, NULL
5819                         };
5821                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5822                                 return status_load_error(view, stage, newpath);
5823                 }
5825                 if (status)
5826                         info = "Staged changes to %s";
5827                 else
5828                         info = "Staged changes";
5829                 break;
5831         case LINE_STAT_UNSTAGED:
5832         {
5833                 const char *files_show_argv[] = {
5834                         "git", "diff-files", "--root", "--patch-with-stat",
5835                                 "-C", "-M", "--", oldpath, newpath, NULL
5836                 };
5838                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5839                         return status_load_error(view, stage, newpath);
5840                 if (status)
5841                         info = "Unstaged changes to %s";
5842                 else
5843                         info = "Unstaged changes";
5844                 break;
5845         }
5846         case LINE_STAT_UNTRACKED:
5847                 if (!newpath) {
5848                         report("No file to show");
5849                         return REQ_NONE;
5850                 }
5852                 if (!suffixcmp(status->new.name, -1, "/")) {
5853                         report("Cannot display a directory");
5854                         return REQ_NONE;
5855                 }
5857                 if (!prepare_update_file(stage, newpath))
5858                         return status_load_error(view, stage, newpath);
5859                 info = "Untracked file %s";
5860                 break;
5862         case LINE_STAT_HEAD:
5863                 return REQ_NONE;
5865         default:
5866                 die("line type %d not handled in switch", line->type);
5867         }
5869         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5870         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5871         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5872                 if (status) {
5873                         stage_status = *status;
5874                 } else {
5875                         memset(&stage_status, 0, sizeof(stage_status));
5876                 }
5878                 stage_line_type = line->type;
5879                 stage_chunks = 0;
5880                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5881         }
5883         return REQ_NONE;
5886 static bool
5887 status_exists(struct status *status, enum line_type type)
5889         struct view *view = VIEW(REQ_VIEW_STATUS);
5890         unsigned long lineno;
5892         for (lineno = 0; lineno < view->lines; lineno++) {
5893                 struct line *line = &view->line[lineno];
5894                 struct status *pos = line->data;
5896                 if (line->type != type)
5897                         continue;
5898                 if (!pos && (!status || !status->status) && line[1].data) {
5899                         select_view_line(view, lineno);
5900                         return TRUE;
5901                 }
5902                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5903                         select_view_line(view, lineno);
5904                         return TRUE;
5905                 }
5906         }
5908         return FALSE;
5912 static bool
5913 status_update_prepare(struct io *io, enum line_type type)
5915         const char *staged_argv[] = {
5916                 "git", "update-index", "-z", "--index-info", NULL
5917         };
5918         const char *others_argv[] = {
5919                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5920         };
5922         switch (type) {
5923         case LINE_STAT_STAGED:
5924                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5926         case LINE_STAT_UNSTAGED:
5927         case LINE_STAT_UNTRACKED:
5928                 return io_run(io, others_argv, opt_cdup, IO_WR);
5930         default:
5931                 die("line type %d not handled in switch", type);
5932                 return FALSE;
5933         }
5936 static bool
5937 status_update_write(struct io *io, struct status *status, enum line_type type)
5939         char buf[SIZEOF_STR];
5940         size_t bufsize = 0;
5942         switch (type) {
5943         case LINE_STAT_STAGED:
5944                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5945                                         status->old.mode,
5946                                         status->old.rev,
5947                                         status->old.name, 0))
5948                         return FALSE;
5949                 break;
5951         case LINE_STAT_UNSTAGED:
5952         case LINE_STAT_UNTRACKED:
5953                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5954                         return FALSE;
5955                 break;
5957         default:
5958                 die("line type %d not handled in switch", type);
5959         }
5961         return io_write(io, buf, bufsize);
5964 static bool
5965 status_update_file(struct status *status, enum line_type type)
5967         struct io io = {};
5968         bool result;
5970         if (!status_update_prepare(&io, type))
5971                 return FALSE;
5973         result = status_update_write(&io, status, type);
5974         return io_done(&io) && result;
5977 static bool
5978 status_update_files(struct view *view, struct line *line)
5980         char buf[sizeof(view->ref)];
5981         struct io io = {};
5982         bool result = TRUE;
5983         struct line *pos = view->line + view->lines;
5984         int files = 0;
5985         int file, done;
5986         int cursor_y = -1, cursor_x = -1;
5988         if (!status_update_prepare(&io, line->type))
5989                 return FALSE;
5991         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5992                 files++;
5994         string_copy(buf, view->ref);
5995         getsyx(cursor_y, cursor_x);
5996         for (file = 0, done = 5; result && file < files; line++, file++) {
5997                 int almost_done = file * 100 / files;
5999                 if (almost_done > done) {
6000                         done = almost_done;
6001                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6002                                       file, files, done);
6003                         update_view_title(view);
6004                         setsyx(cursor_y, cursor_x);
6005                         doupdate();
6006                 }
6007                 result = status_update_write(&io, line->data, line->type);
6008         }
6009         string_copy(view->ref, buf);
6011         return io_done(&io) && result;
6014 static bool
6015 status_update(struct view *view)
6017         struct line *line = &view->line[view->lineno];
6019         assert(view->lines);
6021         if (!line->data) {
6022                 /* This should work even for the "On branch" line. */
6023                 if (line < view->line + view->lines && !line[1].data) {
6024                         report("Nothing to update");
6025                         return FALSE;
6026                 }
6028                 if (!status_update_files(view, line + 1)) {
6029                         report("Failed to update file status");
6030                         return FALSE;
6031                 }
6033         } else if (!status_update_file(line->data, line->type)) {
6034                 report("Failed to update file status");
6035                 return FALSE;
6036         }
6038         return TRUE;
6041 static bool
6042 status_revert(struct status *status, enum line_type type, bool has_none)
6044         if (!status || type != LINE_STAT_UNSTAGED) {
6045                 if (type == LINE_STAT_STAGED) {
6046                         report("Cannot revert changes to staged files");
6047                 } else if (type == LINE_STAT_UNTRACKED) {
6048                         report("Cannot revert changes to untracked files");
6049                 } else if (has_none) {
6050                         report("Nothing to revert");
6051                 } else {
6052                         report("Cannot revert changes to multiple files");
6053                 }
6055         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6056                 char mode[10] = "100644";
6057                 const char *reset_argv[] = {
6058                         "git", "update-index", "--cacheinfo", mode,
6059                                 status->old.rev, status->old.name, NULL
6060                 };
6061                 const char *checkout_argv[] = {
6062                         "git", "checkout", "--", status->old.name, NULL
6063                 };
6065                 if (status->status == 'U') {
6066                         string_format(mode, "%5o", status->old.mode);
6068                         if (status->old.mode == 0 && status->new.mode == 0) {
6069                                 reset_argv[2] = "--force-remove";
6070                                 reset_argv[3] = status->old.name;
6071                                 reset_argv[4] = NULL;
6072                         }
6074                         if (!io_run_fg(reset_argv, opt_cdup))
6075                                 return FALSE;
6076                         if (status->old.mode == 0 && status->new.mode == 0)
6077                                 return TRUE;
6078                 }
6080                 return io_run_fg(checkout_argv, opt_cdup);
6081         }
6083         return FALSE;
6086 static enum request
6087 status_request(struct view *view, enum request request, struct line *line)
6089         struct status *status = line->data;
6091         switch (request) {
6092         case REQ_STATUS_UPDATE:
6093                 if (!status_update(view))
6094                         return REQ_NONE;
6095                 break;
6097         case REQ_STATUS_REVERT:
6098                 if (!status_revert(status, line->type, status_has_none(view, line)))
6099                         return REQ_NONE;
6100                 break;
6102         case REQ_STATUS_MERGE:
6103                 if (!status || status->status != 'U') {
6104                         report("Merging only possible for files with unmerged status ('U').");
6105                         return REQ_NONE;
6106                 }
6107                 open_mergetool(status->new.name);
6108                 break;
6110         case REQ_EDIT:
6111                 if (!status)
6112                         return request;
6113                 if (status->status == 'D') {
6114                         report("File has been deleted.");
6115                         return REQ_NONE;
6116                 }
6118                 open_editor(status->new.name);
6119                 break;
6121         case REQ_VIEW_BLAME:
6122                 if (status)
6123                         opt_ref[0] = 0;
6124                 return request;
6126         case REQ_ENTER:
6127                 /* After returning the status view has been split to
6128                  * show the stage view. No further reloading is
6129                  * necessary. */
6130                 return status_enter(view, line);
6132         case REQ_REFRESH:
6133                 /* Simply reload the view. */
6134                 break;
6136         default:
6137                 return request;
6138         }
6140         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6142         return REQ_NONE;
6145 static void
6146 status_select(struct view *view, struct line *line)
6148         struct status *status = line->data;
6149         char file[SIZEOF_STR] = "all files";
6150         const char *text;
6151         const char *key;
6153         if (status && !string_format(file, "'%s'", status->new.name))
6154                 return;
6156         if (!status && line[1].type == LINE_STAT_NONE)
6157                 line++;
6159         switch (line->type) {
6160         case LINE_STAT_STAGED:
6161                 text = "Press %s to unstage %s for commit";
6162                 break;
6164         case LINE_STAT_UNSTAGED:
6165                 text = "Press %s to stage %s for commit";
6166                 break;
6168         case LINE_STAT_UNTRACKED:
6169                 text = "Press %s to stage %s for addition";
6170                 break;
6172         case LINE_STAT_HEAD:
6173         case LINE_STAT_NONE:
6174                 text = "Nothing to update";
6175                 break;
6177         default:
6178                 die("line type %d not handled in switch", line->type);
6179         }
6181         if (status && status->status == 'U') {
6182                 text = "Press %s to resolve conflict in %s";
6183                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6185         } else {
6186                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6187         }
6189         string_format(view->ref, text, key, file);
6190         if (status)
6191                 string_copy(opt_file, status->new.name);
6194 static bool
6195 status_grep(struct view *view, struct line *line)
6197         struct status *status = line->data;
6199         if (status) {
6200                 const char buf[2] = { status->status, 0 };
6201                 const char *text[] = { status->new.name, buf, NULL };
6203                 return grep_text(view, text);
6204         }
6206         return FALSE;
6209 static struct view_ops status_ops = {
6210         "file",
6211         NULL,
6212         status_open,
6213         NULL,
6214         status_draw,
6215         status_request,
6216         status_grep,
6217         status_select,
6218 };
6221 static bool
6222 stage_diff_write(struct io *io, struct line *line, struct line *end)
6224         while (line < end) {
6225                 if (!io_write(io, line->data, strlen(line->data)) ||
6226                     !io_write(io, "\n", 1))
6227                         return FALSE;
6228                 line++;
6229                 if (line->type == LINE_DIFF_CHUNK ||
6230                     line->type == LINE_DIFF_HEADER)
6231                         break;
6232         }
6234         return TRUE;
6237 static struct line *
6238 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6240         for (; view->line < line; line--)
6241                 if (line->type == type)
6242                         return line;
6244         return NULL;
6247 static bool
6248 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6250         const char *apply_argv[SIZEOF_ARG] = {
6251                 "git", "apply", "--whitespace=nowarn", NULL
6252         };
6253         struct line *diff_hdr;
6254         struct io io = {};
6255         int argc = 3;
6257         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6258         if (!diff_hdr)
6259                 return FALSE;
6261         if (!revert)
6262                 apply_argv[argc++] = "--cached";
6263         if (revert || stage_line_type == LINE_STAT_STAGED)
6264                 apply_argv[argc++] = "-R";
6265         apply_argv[argc++] = "-";
6266         apply_argv[argc++] = NULL;
6267         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6268                 return FALSE;
6270         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6271             !stage_diff_write(&io, chunk, view->line + view->lines))
6272                 chunk = NULL;
6274         io_done(&io);
6275         io_run_bg(update_index_argv);
6277         return chunk ? TRUE : FALSE;
6280 static bool
6281 stage_update(struct view *view, struct line *line)
6283         struct line *chunk = NULL;
6285         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6286                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6288         if (chunk) {
6289                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6290                         report("Failed to apply chunk");
6291                         return FALSE;
6292                 }
6294         } else if (!stage_status.status) {
6295                 view = VIEW(REQ_VIEW_STATUS);
6297                 for (line = view->line; line < view->line + view->lines; line++)
6298                         if (line->type == stage_line_type)
6299                                 break;
6301                 if (!status_update_files(view, line + 1)) {
6302                         report("Failed to update files");
6303                         return FALSE;
6304                 }
6306         } else if (!status_update_file(&stage_status, stage_line_type)) {
6307                 report("Failed to update file");
6308                 return FALSE;
6309         }
6311         return TRUE;
6314 static bool
6315 stage_revert(struct view *view, struct line *line)
6317         struct line *chunk = NULL;
6319         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6320                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6322         if (chunk) {
6323                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6324                         return FALSE;
6326                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6327                         report("Failed to revert chunk");
6328                         return FALSE;
6329                 }
6330                 return TRUE;
6332         } else {
6333                 return status_revert(stage_status.status ? &stage_status : NULL,
6334                                      stage_line_type, FALSE);
6335         }
6339 static void
6340 stage_next(struct view *view, struct line *line)
6342         int i;
6344         if (!stage_chunks) {
6345                 for (line = view->line; line < view->line + view->lines; line++) {
6346                         if (line->type != LINE_DIFF_CHUNK)
6347                                 continue;
6349                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6350                                 report("Allocation failure");
6351                                 return;
6352                         }
6354                         stage_chunk[stage_chunks++] = line - view->line;
6355                 }
6356         }
6358         for (i = 0; i < stage_chunks; i++) {
6359                 if (stage_chunk[i] > view->lineno) {
6360                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6361                         report("Chunk %d of %d", i + 1, stage_chunks);
6362                         return;
6363                 }
6364         }
6366         report("No next chunk found");
6369 static enum request
6370 stage_request(struct view *view, enum request request, struct line *line)
6372         switch (request) {
6373         case REQ_STATUS_UPDATE:
6374                 if (!stage_update(view, line))
6375                         return REQ_NONE;
6376                 break;
6378         case REQ_STATUS_REVERT:
6379                 if (!stage_revert(view, line))
6380                         return REQ_NONE;
6381                 break;
6383         case REQ_STAGE_NEXT:
6384                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6385                         report("File is untracked; press %s to add",
6386                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6387                         return REQ_NONE;
6388                 }
6389                 stage_next(view, line);
6390                 return REQ_NONE;
6392         case REQ_EDIT:
6393                 if (!stage_status.new.name[0])
6394                         return request;
6395                 if (stage_status.status == 'D') {
6396                         report("File has been deleted.");
6397                         return REQ_NONE;
6398                 }
6400                 open_editor(stage_status.new.name);
6401                 break;
6403         case REQ_REFRESH:
6404                 /* Reload everything ... */
6405                 break;
6407         case REQ_VIEW_BLAME:
6408                 if (stage_status.new.name[0]) {
6409                         string_copy(opt_file, stage_status.new.name);
6410                         opt_ref[0] = 0;
6411                 }
6412                 return request;
6414         case REQ_ENTER:
6415                 return pager_request(view, request, line);
6417         default:
6418                 return request;
6419         }
6421         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6422         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6424         /* Check whether the staged entry still exists, and close the
6425          * stage view if it doesn't. */
6426         if (!status_exists(&stage_status, stage_line_type)) {
6427                 status_restore(VIEW(REQ_VIEW_STATUS));
6428                 return REQ_VIEW_CLOSE;
6429         }
6431         if (stage_line_type == LINE_STAT_UNTRACKED) {
6432                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6433                         report("Cannot display a directory");
6434                         return REQ_NONE;
6435                 }
6437                 if (!prepare_update_file(view, stage_status.new.name)) {
6438                         report("Failed to open file: %s", strerror(errno));
6439                         return REQ_NONE;
6440                 }
6441         }
6442         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6444         return REQ_NONE;
6447 static struct view_ops stage_ops = {
6448         "line",
6449         NULL,
6450         NULL,
6451         pager_read,
6452         pager_draw,
6453         stage_request,
6454         pager_grep,
6455         pager_select,
6456 };
6459 /*
6460  * Revision graph
6461  */
6463 struct commit {
6464         char id[SIZEOF_REV];            /* SHA1 ID. */
6465         char title[128];                /* First line of the commit message. */
6466         const char *author;             /* Author of the commit. */
6467         struct time time;               /* Date from the author ident. */
6468         struct ref_list *refs;          /* Repository references. */
6469         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6470         size_t graph_size;              /* The width of the graph array. */
6471         bool has_parents;               /* Rewritten --parents seen. */
6472 };
6474 /* Size of rev graph with no  "padding" columns */
6475 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6477 struct rev_graph {
6478         struct rev_graph *prev, *next, *parents;
6479         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6480         size_t size;
6481         struct commit *commit;
6482         size_t pos;
6483         unsigned int boundary:1;
6484 };
6486 /* Parents of the commit being visualized. */
6487 static struct rev_graph graph_parents[4];
6489 /* The current stack of revisions on the graph. */
6490 static struct rev_graph graph_stacks[4] = {
6491         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6492         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6493         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6494         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6495 };
6497 static inline bool
6498 graph_parent_is_merge(struct rev_graph *graph)
6500         return graph->parents->size > 1;
6503 static inline void
6504 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6506         struct commit *commit = graph->commit;
6508         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6509                 commit->graph[commit->graph_size++] = symbol;
6512 static void
6513 clear_rev_graph(struct rev_graph *graph)
6515         graph->boundary = 0;
6516         graph->size = graph->pos = 0;
6517         graph->commit = NULL;
6518         memset(graph->parents, 0, sizeof(*graph->parents));
6521 static void
6522 done_rev_graph(struct rev_graph *graph)
6524         if (graph_parent_is_merge(graph) &&
6525             graph->pos < graph->size - 1 &&
6526             graph->next->size == graph->size + graph->parents->size - 1) {
6527                 size_t i = graph->pos + graph->parents->size - 1;
6529                 graph->commit->graph_size = i * 2;
6530                 while (i < graph->next->size - 1) {
6531                         append_to_rev_graph(graph, ' ');
6532                         append_to_rev_graph(graph, '\\');
6533                         i++;
6534                 }
6535         }
6537         clear_rev_graph(graph);
6540 static void
6541 push_rev_graph(struct rev_graph *graph, const char *parent)
6543         int i;
6545         /* "Collapse" duplicate parents lines.
6546          *
6547          * FIXME: This needs to also update update the drawn graph but
6548          * for now it just serves as a method for pruning graph lines. */
6549         for (i = 0; i < graph->size; i++)
6550                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6551                         return;
6553         if (graph->size < SIZEOF_REVITEMS) {
6554                 string_copy_rev(graph->rev[graph->size++], parent);
6555         }
6558 static chtype
6559 get_rev_graph_symbol(struct rev_graph *graph)
6561         chtype symbol;
6563         if (graph->boundary)
6564                 symbol = REVGRAPH_BOUND;
6565         else if (graph->parents->size == 0)
6566                 symbol = REVGRAPH_INIT;
6567         else if (graph_parent_is_merge(graph))
6568                 symbol = REVGRAPH_MERGE;
6569         else if (graph->pos >= graph->size)
6570                 symbol = REVGRAPH_BRANCH;
6571         else
6572                 symbol = REVGRAPH_COMMIT;
6574         return symbol;
6577 static void
6578 draw_rev_graph(struct rev_graph *graph)
6580         struct rev_filler {
6581                 chtype separator, line;
6582         };
6583         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6584         static struct rev_filler fillers[] = {
6585                 { ' ',  '|' },
6586                 { '`',  '.' },
6587                 { '\'', ' ' },
6588                 { '/',  ' ' },
6589         };
6590         chtype symbol = get_rev_graph_symbol(graph);
6591         struct rev_filler *filler;
6592         size_t i;
6594         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6595         filler = &fillers[DEFAULT];
6597         for (i = 0; i < graph->pos; i++) {
6598                 append_to_rev_graph(graph, filler->line);
6599                 if (graph_parent_is_merge(graph->prev) &&
6600                     graph->prev->pos == i)
6601                         filler = &fillers[RSHARP];
6603                 append_to_rev_graph(graph, filler->separator);
6604         }
6606         /* Place the symbol for this revision. */
6607         append_to_rev_graph(graph, symbol);
6609         if (graph->prev->size > graph->size)
6610                 filler = &fillers[RDIAG];
6611         else
6612                 filler = &fillers[DEFAULT];
6614         i++;
6616         for (; i < graph->size; i++) {
6617                 append_to_rev_graph(graph, filler->separator);
6618                 append_to_rev_graph(graph, filler->line);
6619                 if (graph_parent_is_merge(graph->prev) &&
6620                     i < graph->prev->pos + graph->parents->size)
6621                         filler = &fillers[RSHARP];
6622                 if (graph->prev->size > graph->size)
6623                         filler = &fillers[LDIAG];
6624         }
6626         if (graph->prev->size > graph->size) {
6627                 append_to_rev_graph(graph, filler->separator);
6628                 if (filler->line != ' ')
6629                         append_to_rev_graph(graph, filler->line);
6630         }
6633 /* Prepare the next rev graph */
6634 static void
6635 prepare_rev_graph(struct rev_graph *graph)
6637         size_t i;
6639         /* First, traverse all lines of revisions up to the active one. */
6640         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6641                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6642                         break;
6644                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6645         }
6647         /* Interleave the new revision parent(s). */
6648         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6649                 push_rev_graph(graph->next, graph->parents->rev[i]);
6651         /* Lastly, put any remaining revisions. */
6652         for (i = graph->pos + 1; i < graph->size; i++)
6653                 push_rev_graph(graph->next, graph->rev[i]);
6656 static void
6657 update_rev_graph(struct view *view, struct rev_graph *graph)
6659         /* If this is the finalizing update ... */
6660         if (graph->commit)
6661                 prepare_rev_graph(graph);
6663         /* Graph visualization needs a one rev look-ahead,
6664          * so the first update doesn't visualize anything. */
6665         if (!graph->prev->commit)
6666                 return;
6668         if (view->lines > 2)
6669                 view->line[view->lines - 3].dirty = 1;
6670         if (view->lines > 1)
6671                 view->line[view->lines - 2].dirty = 1;
6672         draw_rev_graph(graph->prev);
6673         done_rev_graph(graph->prev->prev);
6677 /*
6678  * Main view backend
6679  */
6681 static const char *main_argv[SIZEOF_ARG] = {
6682         "git", "log", "--no-color", "--pretty=raw", "--parents",
6683                       "--topo-order", "%(head)", NULL
6684 };
6686 static bool
6687 main_draw(struct view *view, struct line *line, unsigned int lineno)
6689         struct commit *commit = line->data;
6691         if (!commit->author)
6692                 return FALSE;
6694         if (opt_date && draw_date(view, &commit->time))
6695                 return TRUE;
6697         if (opt_author && draw_author(view, commit->author))
6698                 return TRUE;
6700         if (opt_rev_graph && commit->graph_size &&
6701             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6702                 return TRUE;
6704         if (opt_show_refs && commit->refs) {
6705                 size_t i;
6707                 for (i = 0; i < commit->refs->size; i++) {
6708                         struct ref *ref = commit->refs->refs[i];
6709                         enum line_type type;
6711                         if (ref->head)
6712                                 type = LINE_MAIN_HEAD;
6713                         else if (ref->ltag)
6714                                 type = LINE_MAIN_LOCAL_TAG;
6715                         else if (ref->tag)
6716                                 type = LINE_MAIN_TAG;
6717                         else if (ref->tracked)
6718                                 type = LINE_MAIN_TRACKED;
6719                         else if (ref->remote)
6720                                 type = LINE_MAIN_REMOTE;
6721                         else
6722                                 type = LINE_MAIN_REF;
6724                         if (draw_text(view, type, "[", TRUE) ||
6725                             draw_text(view, type, ref->name, TRUE) ||
6726                             draw_text(view, type, "]", TRUE))
6727                                 return TRUE;
6729                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6730                                 return TRUE;
6731                 }
6732         }
6734         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6735         return TRUE;
6738 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6739 static bool
6740 main_read(struct view *view, char *line)
6742         static struct rev_graph *graph = graph_stacks;
6743         enum line_type type;
6744         struct commit *commit;
6746         if (!line) {
6747                 int i;
6749                 if (!view->lines && !view->parent)
6750                         die("No revisions match the given arguments.");
6751                 if (view->lines > 0) {
6752                         commit = view->line[view->lines - 1].data;
6753                         view->line[view->lines - 1].dirty = 1;
6754                         if (!commit->author) {
6755                                 view->lines--;
6756                                 free(commit);
6757                                 graph->commit = NULL;
6758                         }
6759                 }
6760                 update_rev_graph(view, graph);
6762                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6763                         clear_rev_graph(&graph_stacks[i]);
6764                 return TRUE;
6765         }
6767         type = get_line_type(line);
6768         if (type == LINE_COMMIT) {
6769                 commit = calloc(1, sizeof(struct commit));
6770                 if (!commit)
6771                         return FALSE;
6773                 line += STRING_SIZE("commit ");
6774                 if (*line == '-') {
6775                         graph->boundary = 1;
6776                         line++;
6777                 }
6779                 string_copy_rev(commit->id, line);
6780                 commit->refs = get_ref_list(commit->id);
6781                 graph->commit = commit;
6782                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6784                 while ((line = strchr(line, ' '))) {
6785                         line++;
6786                         push_rev_graph(graph->parents, line);
6787                         commit->has_parents = TRUE;
6788                 }
6789                 return TRUE;
6790         }
6792         if (!view->lines)
6793                 return TRUE;
6794         commit = view->line[view->lines - 1].data;
6796         switch (type) {
6797         case LINE_PARENT:
6798                 if (commit->has_parents)
6799                         break;
6800                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6801                 break;
6803         case LINE_AUTHOR:
6804                 parse_author_line(line + STRING_SIZE("author "),
6805                                   &commit->author, &commit->time);
6806                 update_rev_graph(view, graph);
6807                 graph = graph->next;
6808                 break;
6810         default:
6811                 /* Fill in the commit title if it has not already been set. */
6812                 if (commit->title[0])
6813                         break;
6815                 /* Require titles to start with a non-space character at the
6816                  * offset used by git log. */
6817                 if (strncmp(line, "    ", 4))
6818                         break;
6819                 line += 4;
6820                 /* Well, if the title starts with a whitespace character,
6821                  * try to be forgiving.  Otherwise we end up with no title. */
6822                 while (isspace(*line))
6823                         line++;
6824                 if (*line == '\0')
6825                         break;
6826                 /* FIXME: More graceful handling of titles; append "..." to
6827                  * shortened titles, etc. */
6829                 string_expand(commit->title, sizeof(commit->title), line, 1);
6830                 view->line[view->lines - 1].dirty = 1;
6831         }
6833         return TRUE;
6836 static enum request
6837 main_request(struct view *view, enum request request, struct line *line)
6839         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6841         switch (request) {
6842         case REQ_ENTER:
6843                 open_view(view, REQ_VIEW_DIFF, flags);
6844                 break;
6845         case REQ_REFRESH:
6846                 load_refs();
6847                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6848                 break;
6849         default:
6850                 return request;
6851         }
6853         return REQ_NONE;
6856 static bool
6857 grep_refs(struct ref_list *list, regex_t *regex)
6859         regmatch_t pmatch;
6860         size_t i;
6862         if (!opt_show_refs || !list)
6863                 return FALSE;
6865         for (i = 0; i < list->size; i++) {
6866                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6867                         return TRUE;
6868         }
6870         return FALSE;
6873 static bool
6874 main_grep(struct view *view, struct line *line)
6876         struct commit *commit = line->data;
6877         const char *text[] = {
6878                 commit->title,
6879                 opt_author ? commit->author : "",
6880                 mkdate(&commit->time, opt_date),
6881                 NULL
6882         };
6884         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6887 static void
6888 main_select(struct view *view, struct line *line)
6890         struct commit *commit = line->data;
6892         string_copy_rev(view->ref, commit->id);
6893         string_copy_rev(ref_commit, view->ref);
6896 static struct view_ops main_ops = {
6897         "commit",
6898         main_argv,
6899         NULL,
6900         main_read,
6901         main_draw,
6902         main_request,
6903         main_grep,
6904         main_select,
6905 };
6908 /*
6909  * Status management
6910  */
6912 /* Whether or not the curses interface has been initialized. */
6913 static bool cursed = FALSE;
6915 /* Terminal hacks and workarounds. */
6916 static bool use_scroll_redrawwin;
6917 static bool use_scroll_status_wclear;
6919 /* The status window is used for polling keystrokes. */
6920 static WINDOW *status_win;
6922 /* Reading from the prompt? */
6923 static bool input_mode = FALSE;
6925 static bool status_empty = FALSE;
6927 /* Update status and title window. */
6928 static void
6929 report(const char *msg, ...)
6931         struct view *view = display[current_view];
6933         if (input_mode)
6934                 return;
6936         if (!view) {
6937                 char buf[SIZEOF_STR];
6938                 va_list args;
6940                 va_start(args, msg);
6941                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6942                         buf[sizeof(buf) - 1] = 0;
6943                         buf[sizeof(buf) - 2] = '.';
6944                         buf[sizeof(buf) - 3] = '.';
6945                         buf[sizeof(buf) - 4] = '.';
6946                 }
6947                 va_end(args);
6948                 die("%s", buf);
6949         }
6951         if (!status_empty || *msg) {
6952                 va_list args;
6954                 va_start(args, msg);
6956                 wmove(status_win, 0, 0);
6957                 if (view->has_scrolled && use_scroll_status_wclear)
6958                         wclear(status_win);
6959                 if (*msg) {
6960                         vwprintw(status_win, msg, args);
6961                         status_empty = FALSE;
6962                 } else {
6963                         status_empty = TRUE;
6964                 }
6965                 wclrtoeol(status_win);
6966                 wnoutrefresh(status_win);
6968                 va_end(args);
6969         }
6971         update_view_title(view);
6974 static void
6975 init_display(void)
6977         const char *term;
6978         int x, y;
6980         /* Initialize the curses library */
6981         if (isatty(STDIN_FILENO)) {
6982                 cursed = !!initscr();
6983                 opt_tty = stdin;
6984         } else {
6985                 /* Leave stdin and stdout alone when acting as a pager. */
6986                 opt_tty = fopen("/dev/tty", "r+");
6987                 if (!opt_tty)
6988                         die("Failed to open /dev/tty");
6989                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6990         }
6992         if (!cursed)
6993                 die("Failed to initialize curses");
6995         nonl();         /* Disable conversion and detect newlines from input. */
6996         cbreak();       /* Take input chars one at a time, no wait for \n */
6997         noecho();       /* Don't echo input */
6998         leaveok(stdscr, FALSE);
7000         if (has_colors())
7001                 init_colors();
7003         getmaxyx(stdscr, y, x);
7004         status_win = newwin(1, 0, y - 1, 0);
7005         if (!status_win)
7006                 die("Failed to create status window");
7008         /* Enable keyboard mapping */
7009         keypad(status_win, TRUE);
7010         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7012         TABSIZE = opt_tab_size;
7014         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7015         if (term && !strcmp(term, "gnome-terminal")) {
7016                 /* In the gnome-terminal-emulator, the message from
7017                  * scrolling up one line when impossible followed by
7018                  * scrolling down one line causes corruption of the
7019                  * status line. This is fixed by calling wclear. */
7020                 use_scroll_status_wclear = TRUE;
7021                 use_scroll_redrawwin = FALSE;
7023         } else if (term && !strcmp(term, "xrvt-xpm")) {
7024                 /* No problems with full optimizations in xrvt-(unicode)
7025                  * and aterm. */
7026                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7028         } else {
7029                 /* When scrolling in (u)xterm the last line in the
7030                  * scrolling direction will update slowly. */
7031                 use_scroll_redrawwin = TRUE;
7032                 use_scroll_status_wclear = FALSE;
7033         }
7036 static int
7037 get_input(int prompt_position)
7039         struct view *view;
7040         int i, key, cursor_y, cursor_x;
7041         bool loading = FALSE;
7043         if (prompt_position)
7044                 input_mode = TRUE;
7046         while (TRUE) {
7047                 foreach_view (view, i) {
7048                         update_view(view);
7049                         if (view_is_displayed(view) && view->has_scrolled &&
7050                             use_scroll_redrawwin)
7051                                 redrawwin(view->win);
7052                         view->has_scrolled = FALSE;
7053                         if (view->pipe)
7054                                 loading = TRUE;
7055                 }
7057                 /* Update the cursor position. */
7058                 if (prompt_position) {
7059                         getbegyx(status_win, cursor_y, cursor_x);
7060                         cursor_x = prompt_position;
7061                 } else {
7062                         view = display[current_view];
7063                         getbegyx(view->win, cursor_y, cursor_x);
7064                         cursor_x = view->width - 1;
7065                         cursor_y += view->lineno - view->offset;
7066                 }
7067                 setsyx(cursor_y, cursor_x);
7069                 /* Refresh, accept single keystroke of input */
7070                 doupdate();
7071                 nodelay(status_win, loading);
7072                 key = wgetch(status_win);
7074                 /* wgetch() with nodelay() enabled returns ERR when
7075                  * there's no input. */
7076                 if (key == ERR) {
7078                 } else if (key == KEY_RESIZE) {
7079                         int height, width;
7081                         getmaxyx(stdscr, height, width);
7083                         wresize(status_win, 1, width);
7084                         mvwin(status_win, height - 1, 0);
7085                         wnoutrefresh(status_win);
7086                         resize_display();
7087                         redraw_display(TRUE);
7089                 } else {
7090                         input_mode = FALSE;
7091                         return key;
7092                 }
7093         }
7096 static char *
7097 prompt_input(const char *prompt, input_handler handler, void *data)
7099         enum input_status status = INPUT_OK;
7100         static char buf[SIZEOF_STR];
7101         size_t pos = 0;
7103         buf[pos] = 0;
7105         while (status == INPUT_OK || status == INPUT_SKIP) {
7106                 int key;
7108                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7109                 wclrtoeol(status_win);
7111                 key = get_input(pos + 1);
7112                 switch (key) {
7113                 case KEY_RETURN:
7114                 case KEY_ENTER:
7115                 case '\n':
7116                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7117                         break;
7119                 case KEY_BACKSPACE:
7120                         if (pos > 0)
7121                                 buf[--pos] = 0;
7122                         else
7123                                 status = INPUT_CANCEL;
7124                         break;
7126                 case KEY_ESC:
7127                         status = INPUT_CANCEL;
7128                         break;
7130                 default:
7131                         if (pos >= sizeof(buf)) {
7132                                 report("Input string too long");
7133                                 return NULL;
7134                         }
7136                         status = handler(data, buf, key);
7137                         if (status == INPUT_OK)
7138                                 buf[pos++] = (char) key;
7139                 }
7140         }
7142         /* Clear the status window */
7143         status_empty = FALSE;
7144         report("");
7146         if (status == INPUT_CANCEL)
7147                 return NULL;
7149         buf[pos++] = 0;
7151         return buf;
7154 static enum input_status
7155 prompt_yesno_handler(void *data, char *buf, int c)
7157         if (c == 'y' || c == 'Y')
7158                 return INPUT_STOP;
7159         if (c == 'n' || c == 'N')
7160                 return INPUT_CANCEL;
7161         return INPUT_SKIP;
7164 static bool
7165 prompt_yesno(const char *prompt)
7167         char prompt2[SIZEOF_STR];
7169         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7170                 return FALSE;
7172         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7175 static enum input_status
7176 read_prompt_handler(void *data, char *buf, int c)
7178         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7181 static char *
7182 read_prompt(const char *prompt)
7184         return prompt_input(prompt, read_prompt_handler, NULL);
7187 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7189         enum input_status status = INPUT_OK;
7190         int size = 0;
7192         while (items[size].text)
7193                 size++;
7195         while (status == INPUT_OK) {
7196                 const struct menu_item *item = &items[*selected];
7197                 int key;
7198                 int i;
7200                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7201                           prompt, *selected + 1, size);
7202                 if (item->hotkey)
7203                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7204                 wprintw(status_win, "%s", item->text);
7205                 wclrtoeol(status_win);
7207                 key = get_input(COLS - 1);
7208                 switch (key) {
7209                 case KEY_RETURN:
7210                 case KEY_ENTER:
7211                 case '\n':
7212                         status = INPUT_STOP;
7213                         break;
7215                 case KEY_LEFT:
7216                 case KEY_UP:
7217                         *selected = *selected - 1;
7218                         if (*selected < 0)
7219                                 *selected = size - 1;
7220                         break;
7222                 case KEY_RIGHT:
7223                 case KEY_DOWN:
7224                         *selected = (*selected + 1) % size;
7225                         break;
7227                 case KEY_ESC:
7228                         status = INPUT_CANCEL;
7229                         break;
7231                 default:
7232                         for (i = 0; items[i].text; i++)
7233                                 if (items[i].hotkey == key) {
7234                                         *selected = i;
7235                                         status = INPUT_STOP;
7236                                         break;
7237                                 }
7238                 }
7239         }
7241         /* Clear the status window */
7242         status_empty = FALSE;
7243         report("");
7245         return status != INPUT_CANCEL;
7248 /*
7249  * Repository properties
7250  */
7252 static struct ref **refs = NULL;
7253 static size_t refs_size = 0;
7254 static struct ref *refs_head = NULL;
7256 static struct ref_list **ref_lists = NULL;
7257 static size_t ref_lists_size = 0;
7259 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7260 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7261 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7263 static int
7264 compare_refs(const void *ref1_, const void *ref2_)
7266         const struct ref *ref1 = *(const struct ref **)ref1_;
7267         const struct ref *ref2 = *(const struct ref **)ref2_;
7269         if (ref1->tag != ref2->tag)
7270                 return ref2->tag - ref1->tag;
7271         if (ref1->ltag != ref2->ltag)
7272                 return ref2->ltag - ref2->ltag;
7273         if (ref1->head != ref2->head)
7274                 return ref2->head - ref1->head;
7275         if (ref1->tracked != ref2->tracked)
7276                 return ref2->tracked - ref1->tracked;
7277         if (ref1->remote != ref2->remote)
7278                 return ref2->remote - ref1->remote;
7279         return strcmp(ref1->name, ref2->name);
7282 static void
7283 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7285         size_t i;
7287         for (i = 0; i < refs_size; i++)
7288                 if (!visitor(data, refs[i]))
7289                         break;
7292 static struct ref *
7293 get_ref_head()
7295         return refs_head;
7298 static struct ref_list *
7299 get_ref_list(const char *id)
7301         struct ref_list *list;
7302         size_t i;
7304         for (i = 0; i < ref_lists_size; i++)
7305                 if (!strcmp(id, ref_lists[i]->id))
7306                         return ref_lists[i];
7308         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7309                 return NULL;
7310         list = calloc(1, sizeof(*list));
7311         if (!list)
7312                 return NULL;
7314         for (i = 0; i < refs_size; i++) {
7315                 if (!strcmp(id, refs[i]->id) &&
7316                     realloc_refs_list(&list->refs, list->size, 1))
7317                         list->refs[list->size++] = refs[i];
7318         }
7320         if (!list->refs) {
7321                 free(list);
7322                 return NULL;
7323         }
7325         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7326         ref_lists[ref_lists_size++] = list;
7327         return list;
7330 static int
7331 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7333         struct ref *ref = NULL;
7334         bool tag = FALSE;
7335         bool ltag = FALSE;
7336         bool remote = FALSE;
7337         bool tracked = FALSE;
7338         bool head = FALSE;
7339         int from = 0, to = refs_size - 1;
7341         if (!prefixcmp(name, "refs/tags/")) {
7342                 if (!suffixcmp(name, namelen, "^{}")) {
7343                         namelen -= 3;
7344                         name[namelen] = 0;
7345                 } else {
7346                         ltag = TRUE;
7347                 }
7349                 tag = TRUE;
7350                 namelen -= STRING_SIZE("refs/tags/");
7351                 name    += STRING_SIZE("refs/tags/");
7353         } else if (!prefixcmp(name, "refs/remotes/")) {
7354                 remote = TRUE;
7355                 namelen -= STRING_SIZE("refs/remotes/");
7356                 name    += STRING_SIZE("refs/remotes/");
7357                 tracked  = !strcmp(opt_remote, name);
7359         } else if (!prefixcmp(name, "refs/heads/")) {
7360                 namelen -= STRING_SIZE("refs/heads/");
7361                 name    += STRING_SIZE("refs/heads/");
7362                 if (!strncmp(opt_head, name, namelen))
7363                         return OK;
7365         } else if (!strcmp(name, "HEAD")) {
7366                 head     = TRUE;
7367                 if (*opt_head) {
7368                         namelen  = strlen(opt_head);
7369                         name     = opt_head;
7370                 }
7371         }
7373         /* If we are reloading or it's an annotated tag, replace the
7374          * previous SHA1 with the resolved commit id; relies on the fact
7375          * git-ls-remote lists the commit id of an annotated tag right
7376          * before the commit id it points to. */
7377         while (from <= to) {
7378                 size_t pos = (to + from) / 2;
7379                 int cmp = strcmp(name, refs[pos]->name);
7381                 if (!cmp) {
7382                         ref = refs[pos];
7383                         break;
7384                 }
7386                 if (cmp < 0)
7387                         to = pos - 1;
7388                 else
7389                         from = pos + 1;
7390         }
7392         if (!ref) {
7393                 if (!realloc_refs(&refs, refs_size, 1))
7394                         return ERR;
7395                 ref = calloc(1, sizeof(*ref) + namelen);
7396                 if (!ref)
7397                         return ERR;
7398                 memmove(refs + from + 1, refs + from,
7399                         (refs_size - from) * sizeof(*refs));
7400                 refs[from] = ref;
7401                 strncpy(ref->name, name, namelen);
7402                 refs_size++;
7403         }
7405         ref->head = head;
7406         ref->tag = tag;
7407         ref->ltag = ltag;
7408         ref->remote = remote;
7409         ref->tracked = tracked;
7410         string_copy_rev(ref->id, id);
7412         if (head)
7413                 refs_head = ref;
7414         return OK;
7417 static int
7418 load_refs(void)
7420         const char *head_argv[] = {
7421                 "git", "symbolic-ref", "HEAD", NULL
7422         };
7423         static const char *ls_remote_argv[SIZEOF_ARG] = {
7424                 "git", "ls-remote", opt_git_dir, NULL
7425         };
7426         static bool init = FALSE;
7427         size_t i;
7429         if (!init) {
7430                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7431                         die("TIG_LS_REMOTE contains too many arguments");
7432                 init = TRUE;
7433         }
7435         if (!*opt_git_dir)
7436                 return OK;
7438         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7439             !prefixcmp(opt_head, "refs/heads/")) {
7440                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7442                 memmove(opt_head, offset, strlen(offset) + 1);
7443         }
7445         refs_head = NULL;
7446         for (i = 0; i < refs_size; i++)
7447                 refs[i]->id[0] = 0;
7449         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7450                 return ERR;
7452         /* Update the ref lists to reflect changes. */
7453         for (i = 0; i < ref_lists_size; i++) {
7454                 struct ref_list *list = ref_lists[i];
7455                 size_t old, new;
7457                 for (old = new = 0; old < list->size; old++)
7458                         if (!strcmp(list->id, list->refs[old]->id))
7459                                 list->refs[new++] = list->refs[old];
7460                 list->size = new;
7461         }
7463         return OK;
7466 static void
7467 set_remote_branch(const char *name, const char *value, size_t valuelen)
7469         if (!strcmp(name, ".remote")) {
7470                 string_ncopy(opt_remote, value, valuelen);
7472         } else if (*opt_remote && !strcmp(name, ".merge")) {
7473                 size_t from = strlen(opt_remote);
7475                 if (!prefixcmp(value, "refs/heads/"))
7476                         value += STRING_SIZE("refs/heads/");
7478                 if (!string_format_from(opt_remote, &from, "/%s", value))
7479                         opt_remote[0] = 0;
7480         }
7483 static void
7484 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7486         const char *argv[SIZEOF_ARG] = { name, "=" };
7487         int argc = 1 + (cmd == option_set_command);
7488         int error = ERR;
7490         if (!argv_from_string(argv, &argc, value))
7491                 config_msg = "Too many option arguments";
7492         else
7493                 error = cmd(argc, argv);
7495         if (error == ERR)
7496                 warn("Option 'tig.%s': %s", name, config_msg);
7499 static bool
7500 set_environment_variable(const char *name, const char *value)
7502         size_t len = strlen(name) + 1 + strlen(value) + 1;
7503         char *env = malloc(len);
7505         if (env &&
7506             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7507             putenv(env) == 0)
7508                 return TRUE;
7509         free(env);
7510         return FALSE;
7513 static void
7514 set_work_tree(const char *value)
7516         char cwd[SIZEOF_STR];
7518         if (!getcwd(cwd, sizeof(cwd)))
7519                 die("Failed to get cwd path: %s", strerror(errno));
7520         if (chdir(opt_git_dir) < 0)
7521                 die("Failed to chdir(%s): %s", strerror(errno));
7522         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7523                 die("Failed to get git path: %s", strerror(errno));
7524         if (chdir(cwd) < 0)
7525                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7526         if (chdir(value) < 0)
7527                 die("Failed to chdir(%s): %s", value, strerror(errno));
7528         if (!getcwd(cwd, sizeof(cwd)))
7529                 die("Failed to get cwd path: %s", strerror(errno));
7530         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7531                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7532         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7533                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7534         opt_is_inside_work_tree = TRUE;
7537 static int
7538 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7540         if (!strcmp(name, "i18n.commitencoding"))
7541                 string_ncopy(opt_encoding, value, valuelen);
7543         else if (!strcmp(name, "core.editor"))
7544                 string_ncopy(opt_editor, value, valuelen);
7546         else if (!strcmp(name, "core.worktree"))
7547                 set_work_tree(value);
7549         else if (!prefixcmp(name, "tig.color."))
7550                 set_repo_config_option(name + 10, value, option_color_command);
7552         else if (!prefixcmp(name, "tig.bind."))
7553                 set_repo_config_option(name + 9, value, option_bind_command);
7555         else if (!prefixcmp(name, "tig."))
7556                 set_repo_config_option(name + 4, value, option_set_command);
7558         else if (*opt_head && !prefixcmp(name, "branch.") &&
7559                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7560                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7562         return OK;
7565 static int
7566 load_git_config(void)
7568         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7570         return io_run_load(config_list_argv, "=", read_repo_config_option);
7573 static int
7574 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7576         if (!opt_git_dir[0]) {
7577                 string_ncopy(opt_git_dir, name, namelen);
7579         } else if (opt_is_inside_work_tree == -1) {
7580                 /* This can be 3 different values depending on the
7581                  * version of git being used. If git-rev-parse does not
7582                  * understand --is-inside-work-tree it will simply echo
7583                  * the option else either "true" or "false" is printed.
7584                  * Default to true for the unknown case. */
7585                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7587         } else if (*name == '.') {
7588                 string_ncopy(opt_cdup, name, namelen);
7590         } else {
7591                 string_ncopy(opt_prefix, name, namelen);
7592         }
7594         return OK;
7597 static int
7598 load_repo_info(void)
7600         const char *rev_parse_argv[] = {
7601                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7602                         "--show-cdup", "--show-prefix", NULL
7603         };
7605         return io_run_load(rev_parse_argv, "=", read_repo_info);
7609 /*
7610  * Main
7611  */
7613 static const char usage[] =
7614 "tig " TIG_VERSION " (" __DATE__ ")\n"
7615 "\n"
7616 "Usage: tig        [options] [revs] [--] [paths]\n"
7617 "   or: tig show   [options] [revs] [--] [paths]\n"
7618 "   or: tig blame  [rev] path\n"
7619 "   or: tig status\n"
7620 "   or: tig <      [git command output]\n"
7621 "\n"
7622 "Options:\n"
7623 "  -v, --version   Show version and exit\n"
7624 "  -h, --help      Show help message and exit";
7626 static void __NORETURN
7627 quit(int sig)
7629         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7630         if (cursed)
7631                 endwin();
7632         exit(0);
7635 static void __NORETURN
7636 die(const char *err, ...)
7638         va_list args;
7640         endwin();
7642         va_start(args, err);
7643         fputs("tig: ", stderr);
7644         vfprintf(stderr, err, args);
7645         fputs("\n", stderr);
7646         va_end(args);
7648         exit(1);
7651 static void
7652 warn(const char *msg, ...)
7654         va_list args;
7656         va_start(args, msg);
7657         fputs("tig warning: ", stderr);
7658         vfprintf(stderr, msg, args);
7659         fputs("\n", stderr);
7660         va_end(args);
7663 static enum request
7664 parse_options(int argc, const char *argv[])
7666         enum request request = REQ_VIEW_MAIN;
7667         const char *subcommand;
7668         bool seen_dashdash = FALSE;
7669         /* XXX: This is vulnerable to the user overriding options
7670          * required for the main view parser. */
7671         const char *custom_argv[SIZEOF_ARG] = {
7672                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7673                         "--topo-order", NULL
7674         };
7675         int i, j = 6;
7677         if (!isatty(STDIN_FILENO)) {
7678                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7679                 return REQ_VIEW_PAGER;
7680         }
7682         if (argc <= 1)
7683                 return REQ_NONE;
7685         subcommand = argv[1];
7686         if (!strcmp(subcommand, "status")) {
7687                 if (argc > 2)
7688                         warn("ignoring arguments after `%s'", subcommand);
7689                 return REQ_VIEW_STATUS;
7691         } else if (!strcmp(subcommand, "blame")) {
7692                 if (argc <= 2 || argc > 4)
7693                         die("invalid number of options to blame\n\n%s", usage);
7695                 i = 2;
7696                 if (argc == 4) {
7697                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7698                         i++;
7699                 }
7701                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7702                 return REQ_VIEW_BLAME;
7704         } else if (!strcmp(subcommand, "show")) {
7705                 request = REQ_VIEW_DIFF;
7707         } else {
7708                 subcommand = NULL;
7709         }
7711         if (subcommand) {
7712                 custom_argv[1] = subcommand;
7713                 j = 2;
7714         }
7716         for (i = 1 + !!subcommand; i < argc; i++) {
7717                 const char *opt = argv[i];
7719                 if (seen_dashdash || !strcmp(opt, "--")) {
7720                         seen_dashdash = TRUE;
7722                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7723                         printf("tig version %s\n", TIG_VERSION);
7724                         quit(0);
7726                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7727                         printf("%s\n", usage);
7728                         quit(0);
7729                 }
7731                 custom_argv[j++] = opt;
7732                 if (j >= ARRAY_SIZE(custom_argv))
7733                         die("command too long");
7734         }
7736         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7737                 die("Failed to format arguments");
7739         return request;
7742 int
7743 main(int argc, const char *argv[])
7745         const char *codeset = "UTF-8";
7746         enum request request = parse_options(argc, argv);
7747         struct view *view;
7748         size_t i;
7750         signal(SIGINT, quit);
7751         signal(SIGPIPE, SIG_IGN);
7753         if (setlocale(LC_ALL, "")) {
7754                 codeset = nl_langinfo(CODESET);
7755         }
7757         if (load_repo_info() == ERR)
7758                 die("Failed to load repo info.");
7760         if (load_options() == ERR)
7761                 die("Failed to load user config.");
7763         if (load_git_config() == ERR)
7764                 die("Failed to load repo config.");
7766         /* Require a git repository unless when running in pager mode. */
7767         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7768                 die("Not a git repository");
7770         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7771                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7772                 if (opt_iconv_in == ICONV_NONE)
7773                         die("Failed to initialize character set conversion");
7774         }
7776         if (codeset && strcmp(codeset, "UTF-8")) {
7777                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7778                 if (opt_iconv_out == ICONV_NONE)
7779                         die("Failed to initialize character set conversion");
7780         }
7782         if (load_refs() == ERR)
7783                 die("Failed to load refs.");
7785         foreach_view (view, i)
7786                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7787                         die("Too many arguments in the `%s` environment variable",
7788                             view->cmd_env);
7790         init_display();
7792         if (request != REQ_NONE)
7793                 open_view(NULL, request, OPEN_PREPARED);
7794         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7796         while (view_driver(display[current_view], request)) {
7797                 int key = get_input(0);
7799                 view = display[current_view];
7800                 request = get_keybinding(view->keymap, key);
7802                 /* Some low-level request handling. This keeps access to
7803                  * status_win restricted. */
7804                 switch (request) {
7805                 case REQ_PROMPT:
7806                 {
7807                         char *cmd = read_prompt(":");
7809                         if (cmd && isdigit(*cmd)) {
7810                                 int lineno = view->lineno + 1;
7812                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7813                                         select_view_line(view, lineno - 1);
7814                                         report("");
7815                                 } else {
7816                                         report("Unable to parse '%s' as a line number", cmd);
7817                                 }
7819                         } else if (cmd) {
7820                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7821                                 const char *argv[SIZEOF_ARG] = { "git" };
7822                                 int argc = 1;
7824                                 /* When running random commands, initially show the
7825                                  * command in the title. However, it maybe later be
7826                                  * overwritten if a commit line is selected. */
7827                                 string_ncopy(next->ref, cmd, strlen(cmd));
7829                                 if (!argv_from_string(argv, &argc, cmd)) {
7830                                         report("Too many arguments");
7831                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7832                                         report("Failed to format command");
7833                                 } else {
7834                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7835                                 }
7836                         }
7838                         request = REQ_NONE;
7839                         break;
7840                 }
7841                 case REQ_SEARCH:
7842                 case REQ_SEARCH_BACK:
7843                 {
7844                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7845                         char *search = read_prompt(prompt);
7847                         if (search)
7848                                 string_ncopy(opt_search, search, strlen(search));
7849                         else if (*opt_search)
7850                                 request = request == REQ_SEARCH ?
7851                                         REQ_FIND_NEXT :
7852                                         REQ_FIND_PREV;
7853                         else
7854                                 request = REQ_NONE;
7855                         break;
7856                 }
7857                 default:
7858                         break;
7859                 }
7860         }
7862         quit(0);
7864         return 0;