Code

fix off-by-one on parent selection
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int head:1;    /* Is it the current HEAD? */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int ltag:1;    /* If so, is the tag local? */
127         unsigned int remote:1;  /* Is it a remote ref? */
128         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129         char name[1];           /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         size_t size;            /* Number of refs. */
135         struct ref **refs;      /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151         INPUT_OK,
152         INPUT_SKIP,
153         INPUT_STOP,
154         INPUT_CANCEL
155 };
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163         int hotkey;
164         const char *text;
165         void *data;
166 };
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
170 /*
171  * Allocation helpers ... Entering macro hell to never be seen again.
172  */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
175 static type *                                                                   \
176 name(type **mem, size_t size, size_t increase)                                  \
177 {                                                                               \
178         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
179         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180         type *tmp = *mem;                                                       \
181                                                                                 \
182         if (mem == NULL || num_chunks != num_chunks_new) {                      \
183                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184                 if (tmp)                                                        \
185                         *mem = tmp;                                             \
186         }                                                                       \
187                                                                                 \
188         return tmp;                                                             \
191 /*
192  * String helpers
193  */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198         if (srclen > dstlen - 1)
199                 srclen = dstlen - 1;
201         strncpy(dst, src, srclen);
202         dst[srclen] = 0;
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211         string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
222         size_t size, pos;
224         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225                 if (src[pos] == '\t') {
226                         size_t expanded = tabsize - (size % tabsize);
228                         if (expanded + size >= dstlen - 1)
229                                 expanded = dstlen - size - 1;
230                         memcpy(dst + size, "        ", expanded);
231                         size += expanded;
232                 } else {
233                         dst[size++] = src[pos];
234                 }
235         }
237         dst[size] = 0;
240 static char *
241 chomp_string(char *name)
243         int namelen;
245         while (isspace(*name))
246                 name++;
248         namelen = strlen(name) - 1;
249         while (namelen > 0 && isspace(name[namelen]))
250                 name[namelen--] = 0;
252         return name;
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
258         va_list args;
259         size_t pos = bufpos ? *bufpos : 0;
261         va_start(args, fmt);
262         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263         va_end(args);
265         if (bufpos)
266                 *bufpos = pos;
268         return pos >= bufsize ? FALSE : TRUE;
271 #define string_format(buf, fmt, args...) \
272         string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275         string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
280         size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284         /* Diff-Header == DIFF_HEADER */
285         for (i = 0; i < len; i++) {
286                 if (toupper(str1[i]) == toupper(str2[i]))
287                         continue;
289                 if (string_enum_sep(str1[i]) &&
290                     string_enum_sep(str2[i]))
291                         continue;
293                 return str1[i] - str2[i];
294         }
296         return 0;
299 #define enum_equals(entry, str, len) \
300         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303         const char *name;
304         int namelen;
305         int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
313         static char buf[SIZEOF_STR];
314         int bufpos;
316         for (bufpos = 0; bufpos <= namelen; bufpos++) {
317                 buf[bufpos] = tolower(name[bufpos]);
318                 if (buf[bufpos] == '_')
319                         buf[bufpos] = '-';
320         }
322         buf[bufpos] = 0;
323         return buf;
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
331         size_t namelen = strlen(name);
332         int i;
334         for (i = 0; i < map_size; i++)
335                 if (enum_equals(map[i], name, namelen)) {
336                         *value = map[i].value;
337                         return TRUE;
338                 }
340         return FALSE;
343 #define map_enum(attr, map, name) \
344         map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347         strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
352         size_t len = slen >= 0 ? slen : strlen(str);
353         size_t suffixlen = strlen(suffix);
355         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
359 /*
360  * Unicode / UTF-8 handling
361  *
362  * NOTE: Much of the following code for dealing with Unicode is derived from
363  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
365  */
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
370         if (c >= 0x1100 &&
371            (c <= 0x115f                         /* Hangul Jamo */
372             || c == 0x2329
373             || c == 0x232a
374             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
375                                                 /* CJK ... Yi */
376             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
377             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
378             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
379             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
380             || (c >= 0xffe0  && c <= 0xffe6)
381             || (c >= 0x20000 && c <= 0x2fffd)
382             || (c >= 0x30000 && c <= 0x3fffd)))
383                 return 2;
385         if (c == '\t')
386                 return tab_size;
388         return 1;
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392  * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
396         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
397         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
398         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
399         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
400         2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
401         3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
402 };
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
407         int c = *(unsigned char *) string;
409         return utf8_bytes[c];
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
416         unsigned long unicode;
418         switch (length) {
419         case 1:
420                 unicode  =   string[0];
421                 break;
422         case 2:
423                 unicode  =  (string[0] & 0x1f) << 6;
424                 unicode +=  (string[1] & 0x3f);
425                 break;
426         case 3:
427                 unicode  =  (string[0] & 0x0f) << 12;
428                 unicode += ((string[1] & 0x3f) << 6);
429                 unicode +=  (string[2] & 0x3f);
430                 break;
431         case 4:
432                 unicode  =  (string[0] & 0x0f) << 18;
433                 unicode += ((string[1] & 0x3f) << 12);
434                 unicode += ((string[2] & 0x3f) << 6);
435                 unicode +=  (string[3] & 0x3f);
436                 break;
437         case 5:
438                 unicode  =  (string[0] & 0x0f) << 24;
439                 unicode += ((string[1] & 0x3f) << 18);
440                 unicode += ((string[2] & 0x3f) << 12);
441                 unicode += ((string[3] & 0x3f) << 6);
442                 unicode +=  (string[4] & 0x3f);
443                 break;
444         case 6:
445                 unicode  =  (string[0] & 0x01) << 30;
446                 unicode += ((string[1] & 0x3f) << 24);
447                 unicode += ((string[2] & 0x3f) << 18);
448                 unicode += ((string[3] & 0x3f) << 12);
449                 unicode += ((string[4] & 0x3f) << 6);
450                 unicode +=  (string[5] & 0x3f);
451                 break;
452         default:
453                 return 0;
454         }
456         /* Invalid characters could return the special 0xfffd value but NUL
457          * should be just as good. */
458         return unicode > 0xffff ? 0 : unicode;
461 /* Calculates how much of string can be shown within the given maximum width
462  * and sets trimmed parameter to non-zero value if all of string could not be
463  * shown. If the reserve flag is TRUE, it will reserve at least one
464  * trailing character, which can be useful when drawing a delimiter.
465  *
466  * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
470         const char *string = *start;
471         const char *end = strchr(string, '\0');
472         unsigned char last_bytes = 0;
473         size_t last_ucwidth = 0;
475         *width = 0;
476         *trimmed = 0;
478         while (string < end) {
479                 unsigned char bytes = utf8_char_length(string, end);
480                 size_t ucwidth;
481                 unsigned long unicode;
483                 if (string + bytes > end)
484                         break;
486                 /* Change representation to figure out whether
487                  * it is a single- or double-width character. */
489                 unicode = utf8_to_unicode(string, bytes);
490                 /* FIXME: Graceful handling of invalid Unicode character. */
491                 if (!unicode)
492                         break;
494                 ucwidth = unicode_width(unicode, tab_size);
495                 if (skip > 0) {
496                         skip -= ucwidth <= skip ? ucwidth : skip;
497                         *start += bytes;
498                 }
499                 *width  += ucwidth;
500                 if (*width > max_width) {
501                         *trimmed = 1;
502                         *width -= ucwidth;
503                         if (reserve && *width == max_width) {
504                                 string -= last_bytes;
505                                 *width -= last_ucwidth;
506                         }
507                         break;
508                 }
510                 string  += bytes;
511                 last_bytes = ucwidth ? bytes : 0;
512                 last_ucwidth = ucwidth;
513         }
515         return string - *start;
519 #define DATE_INFO \
520         DATE_(NO), \
521         DATE_(DEFAULT), \
522         DATE_(LOCAL), \
523         DATE_(RELATIVE), \
524         DATE_(SHORT)
526 enum date {
527 #define DATE_(name) DATE_##name
528         DATE_INFO
529 #undef  DATE_
530 };
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534         DATE_INFO
535 #undef  DATE_
536 };
538 struct time {
539         time_t sec;
540         int tz;
541 };
543 static inline int timecmp(const struct time *t1, const struct time *t2)
545         return t1->sec - t2->sec;
548 static const char *
549 mkdate(const struct time *time, enum date date)
551         static char buf[DATE_COLS + 1];
552         static const struct enum_map reldate[] = {
553                 { "second", 1,                  60 * 2 },
554                 { "minute", 60,                 60 * 60 * 2 },
555                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
556                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
557                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
558                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
559         };
560         struct tm tm;
562         if (!date || !time || !time->sec)
563                 return "";
565         if (date == DATE_RELATIVE) {
566                 struct timeval now;
567                 time_t date = time->sec + time->tz;
568                 time_t seconds;
569                 int i;
571                 gettimeofday(&now, NULL);
572                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
573                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
574                         if (seconds >= reldate[i].value)
575                                 continue;
577                         seconds /= reldate[i].namelen;
578                         if (!string_format(buf, "%ld %s%s %s",
579                                            seconds, reldate[i].name,
580                                            seconds > 1 ? "s" : "",
581                                            now.tv_sec >= date ? "ago" : "ahead"))
582                                 break;
583                         return buf;
584                 }
585         }
587         if (date == DATE_LOCAL) {
588                 time_t date = time->sec + time->tz;
589                 localtime_r(&date, &tm);
590         }
591         else {
592                 gmtime_r(&time->sec, &tm);
593         }
594         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
598 #define AUTHOR_VALUES \
599         AUTHOR_(NO), \
600         AUTHOR_(FULL), \
601         AUTHOR_(ABBREVIATED)
603 enum author {
604 #define AUTHOR_(name) AUTHOR_##name
605         AUTHOR_VALUES,
606 #undef  AUTHOR_
607         AUTHOR_DEFAULT = AUTHOR_FULL
608 };
610 static const struct enum_map author_map[] = {
611 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
612         AUTHOR_VALUES
613 #undef  AUTHOR_
614 };
616 static const char *
617 get_author_initials(const char *author)
619         static char initials[AUTHOR_COLS * 6 + 1];
620         size_t pos = 0;
621         const char *end = strchr(author, '\0');
623 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
625         memset(initials, 0, sizeof(initials));
626         while (author < end) {
627                 unsigned char bytes;
628                 size_t i;
630                 while (is_initial_sep(*author))
631                         author++;
633                 bytes = utf8_char_length(author, end);
634                 if (bytes < sizeof(initials) - 1 - pos) {
635                         while (bytes--) {
636                                 initials[pos++] = *author++;
637                         }
638                 }
640                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
641                         if (i < sizeof(initials) - 1)
642                                 initials[i++] = *author;
643                 }
645                 initials[i++] = 0;
646         }
648         return initials;
652 static bool
653 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
655         int valuelen;
657         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
658                 bool advance = cmd[valuelen] != 0;
660                 cmd[valuelen] = 0;
661                 argv[(*argc)++] = chomp_string(cmd);
662                 cmd = chomp_string(cmd + valuelen + advance);
663         }
665         if (*argc < SIZEOF_ARG)
666                 argv[*argc] = NULL;
667         return *argc < SIZEOF_ARG;
670 static bool
671 argv_from_env(const char **argv, const char *name)
673         char *env = argv ? getenv(name) : NULL;
674         int argc = 0;
676         if (env && *env)
677                 env = strdup(env);
678         return !env || argv_from_string(argv, &argc, env);
682 /*
683  * Executing external commands.
684  */
686 enum io_type {
687         IO_FD,                  /* File descriptor based IO. */
688         IO_BG,                  /* Execute command in the background. */
689         IO_FG,                  /* Execute command with same std{in,out,err}. */
690         IO_RD,                  /* Read only fork+exec IO. */
691         IO_WR,                  /* Write only fork+exec IO. */
692         IO_AP,                  /* Append fork+exec output to file. */
693 };
695 struct io {
696         enum io_type type;      /* The requested type of pipe. */
697         const char *dir;        /* Directory from which to execute. */
698         pid_t pid;              /* PID of spawned process. */
699         int pipe;               /* Pipe end for reading or writing. */
700         int error;              /* Error status. */
701         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
702         char *buf;              /* Read buffer. */
703         size_t bufalloc;        /* Allocated buffer size. */
704         size_t bufsize;         /* Buffer content size. */
705         char *bufpos;           /* Current buffer position. */
706         unsigned int eof:1;     /* Has end of file been reached. */
707 };
709 static void
710 io_reset(struct io *io)
712         io->pipe = -1;
713         io->pid = 0;
714         io->buf = io->bufpos = NULL;
715         io->bufalloc = io->bufsize = 0;
716         io->error = 0;
717         io->eof = 0;
720 static void
721 io_init(struct io *io, const char *dir, enum io_type type)
723         io_reset(io);
724         io->type = type;
725         io->dir = dir;
728 static bool
729 io_format(struct io *io, const char *dir, enum io_type type,
730           const char *argv[], enum format_flags flags)
732         io_init(io, dir, type);
733         return format_argv(io->argv, argv, flags);
736 static bool
737 io_open(struct io *io, const char *fmt, ...)
739         char name[SIZEOF_STR] = "";
740         bool fits;
741         va_list args;
743         io_init(io, NULL, IO_FD);
745         va_start(args, fmt);
746         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
747         va_end(args);
749         if (!fits) {
750                 io->error = ENAMETOOLONG;
751                 return FALSE;
752         }
753         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
754         if (io->pipe == -1)
755                 io->error = errno;
756         return io->pipe != -1;
759 static bool
760 io_kill(struct io *io)
762         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
765 static bool
766 io_done(struct io *io)
768         pid_t pid = io->pid;
770         if (io->pipe != -1)
771                 close(io->pipe);
772         free(io->buf);
773         io_reset(io);
775         while (pid > 0) {
776                 int status;
777                 pid_t waiting = waitpid(pid, &status, 0);
779                 if (waiting < 0) {
780                         if (errno == EINTR)
781                                 continue;
782                         io->error = errno;
783                         return FALSE;
784                 }
786                 return waiting == pid &&
787                        !WIFSIGNALED(status) &&
788                        WIFEXITED(status) &&
789                        !WEXITSTATUS(status);
790         }
792         return TRUE;
795 static bool
796 io_start(struct io *io)
798         int pipefds[2] = { -1, -1 };
800         if (io->type == IO_FD)
801                 return TRUE;
803         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
804                 io->error = errno;
805                 return FALSE;
806         } else if (io->type == IO_AP) {
807                 pipefds[1] = io->pipe;
808         }
810         if ((io->pid = fork())) {
811                 if (io->pid == -1)
812                         io->error = errno;
813                 if (pipefds[!(io->type == IO_WR)] != -1)
814                         close(pipefds[!(io->type == IO_WR)]);
815                 if (io->pid != -1) {
816                         io->pipe = pipefds[!!(io->type == IO_WR)];
817                         return TRUE;
818                 }
820         } else {
821                 if (io->type != IO_FG) {
822                         int devnull = open("/dev/null", O_RDWR);
823                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
824                         int writefd = (io->type == IO_RD || io->type == IO_AP)
825                                                         ? pipefds[1] : devnull;
827                         dup2(readfd,  STDIN_FILENO);
828                         dup2(writefd, STDOUT_FILENO);
829                         dup2(devnull, STDERR_FILENO);
831                         close(devnull);
832                         if (pipefds[0] != -1)
833                                 close(pipefds[0]);
834                         if (pipefds[1] != -1)
835                                 close(pipefds[1]);
836                 }
838                 if (io->dir && *io->dir && chdir(io->dir) == -1)
839                         exit(errno);
841                 execvp(io->argv[0], (char *const*) io->argv);
842                 exit(errno);
843         }
845         if (pipefds[!!(io->type == IO_WR)] != -1)
846                 close(pipefds[!!(io->type == IO_WR)]);
847         return FALSE;
850 static bool
851 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
853         io_init(io, dir, type);
854         if (!format_argv(io->argv, argv, FORMAT_NONE))
855                 return FALSE;
856         return io_start(io);
859 static int
860 io_complete(struct io *io)
862         return io_start(io) && io_done(io);
865 static int
866 io_run_bg(const char **argv)
868         struct io io = {};
870         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
871                 return FALSE;
872         return io_complete(&io);
875 static bool
876 io_run_fg(const char **argv, const char *dir)
878         struct io io = {};
880         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
881                 return FALSE;
882         return io_complete(&io);
885 static bool
886 io_run_append(const char **argv, enum format_flags flags, int fd)
888         struct io io = {};
890         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
891                 close(fd);
892                 return FALSE;
893         }
895         io.pipe = fd;
896         return io_complete(&io);
899 static bool
900 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
902         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
905 static bool
906 io_eof(struct io *io)
908         return io->eof;
911 static int
912 io_error(struct io *io)
914         return io->error;
917 static char *
918 io_strerror(struct io *io)
920         return strerror(io->error);
923 static bool
924 io_can_read(struct io *io)
926         struct timeval tv = { 0, 500 };
927         fd_set fds;
929         FD_ZERO(&fds);
930         FD_SET(io->pipe, &fds);
932         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
935 static ssize_t
936 io_read(struct io *io, void *buf, size_t bufsize)
938         do {
939                 ssize_t readsize = read(io->pipe, buf, bufsize);
941                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
942                         continue;
943                 else if (readsize == -1)
944                         io->error = errno;
945                 else if (readsize == 0)
946                         io->eof = 1;
947                 return readsize;
948         } while (1);
951 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
953 static char *
954 io_get(struct io *io, int c, bool can_read)
956         char *eol;
957         ssize_t readsize;
959         while (TRUE) {
960                 if (io->bufsize > 0) {
961                         eol = memchr(io->bufpos, c, io->bufsize);
962                         if (eol) {
963                                 char *line = io->bufpos;
965                                 *eol = 0;
966                                 io->bufpos = eol + 1;
967                                 io->bufsize -= io->bufpos - line;
968                                 return line;
969                         }
970                 }
972                 if (io_eof(io)) {
973                         if (io->bufsize) {
974                                 io->bufpos[io->bufsize] = 0;
975                                 io->bufsize = 0;
976                                 return io->bufpos;
977                         }
978                         return NULL;
979                 }
981                 if (!can_read)
982                         return NULL;
984                 if (io->bufsize > 0 && io->bufpos > io->buf)
985                         memmove(io->buf, io->bufpos, io->bufsize);
987                 if (io->bufalloc == io->bufsize) {
988                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
989                                 return NULL;
990                         io->bufalloc += BUFSIZ;
991                 }
993                 io->bufpos = io->buf;
994                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
995                 if (io_error(io))
996                         return NULL;
997                 io->bufsize += readsize;
998         }
1001 static bool
1002 io_write(struct io *io, const void *buf, size_t bufsize)
1004         size_t written = 0;
1006         while (!io_error(io) && written < bufsize) {
1007                 ssize_t size;
1009                 size = write(io->pipe, buf + written, bufsize - written);
1010                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1011                         continue;
1012                 else if (size == -1)
1013                         io->error = errno;
1014                 else
1015                         written += size;
1016         }
1018         return written == bufsize;
1021 static bool
1022 io_read_buf(struct io *io, char buf[], size_t bufsize)
1024         char *result = io_get(io, '\n', TRUE);
1026         if (result) {
1027                 result = chomp_string(result);
1028                 string_ncopy_do(buf, bufsize, result, strlen(result));
1029         }
1031         return io_done(io) && result;
1034 static bool
1035 io_run_buf(const char **argv, char buf[], size_t bufsize)
1037         struct io io = {};
1039         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1040             && io_read_buf(&io, buf, bufsize);
1043 static int
1044 io_load(struct io *io, const char *separators,
1045         int (*read_property)(char *, size_t, char *, size_t))
1047         char *name;
1048         int state = OK;
1050         if (!io_start(io))
1051                 return ERR;
1053         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054                 char *value;
1055                 size_t namelen;
1056                 size_t valuelen;
1058                 name = chomp_string(name);
1059                 namelen = strcspn(name, separators);
1061                 if (name[namelen]) {
1062                         name[namelen] = 0;
1063                         value = chomp_string(name + namelen + 1);
1064                         valuelen = strlen(value);
1066                 } else {
1067                         value = "";
1068                         valuelen = 0;
1069                 }
1071                 state = read_property(name, namelen, value, valuelen);
1072         }
1074         if (state != ERR && io_error(io))
1075                 state = ERR;
1076         io_done(io);
1078         return state;
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083             int (*read_property)(char *, size_t, char *, size_t))
1085         struct io io = {};
1087         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1088                 ? io_load(&io, separators, read_property) : ERR;
1092 /*
1093  * User requests
1094  */
1096 #define REQ_INFO \
1097         /* XXX: Keep the view request first and in sync with views[]. */ \
1098         REQ_GROUP("View switching") \
1099         REQ_(VIEW_MAIN,         "Show main view"), \
1100         REQ_(VIEW_DIFF,         "Show diff view"), \
1101         REQ_(VIEW_LOG,          "Show log view"), \
1102         REQ_(VIEW_TREE,         "Show tree view"), \
1103         REQ_(VIEW_BLOB,         "Show blob view"), \
1104         REQ_(VIEW_BLAME,        "Show blame view"), \
1105         REQ_(VIEW_BRANCH,       "Show branch view"), \
1106         REQ_(VIEW_HELP,         "Show help page"), \
1107         REQ_(VIEW_PAGER,        "Show pager view"), \
1108         REQ_(VIEW_STATUS,       "Show status view"), \
1109         REQ_(VIEW_STAGE,        "Show stage view"), \
1110         \
1111         REQ_GROUP("View manipulation") \
1112         REQ_(ENTER,             "Enter current line and scroll"), \
1113         REQ_(NEXT,              "Move to next"), \
1114         REQ_(PREVIOUS,          "Move to previous"), \
1115         REQ_(PARENT,            "Move to parent"), \
1116         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1117         REQ_(REFRESH,           "Reload and refresh"), \
1118         REQ_(MAXIMIZE,          "Maximize the current view"), \
1119         REQ_(VIEW_CLOSE,        "Close the current view"), \
1120         REQ_(QUIT,              "Close all views and quit"), \
1121         \
1122         REQ_GROUP("View specific requests") \
1123         REQ_(STATUS_UPDATE,     "Update file status"), \
1124         REQ_(STATUS_REVERT,     "Revert file changes"), \
1125         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1126         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1127         \
1128         REQ_GROUP("Cursor navigation") \
1129         REQ_(MOVE_UP,           "Move cursor one line up"), \
1130         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1131         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1132         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1133         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1134         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1135         \
1136         REQ_GROUP("Scrolling") \
1137         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1138         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1139         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1140         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1141         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1142         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1143         \
1144         REQ_GROUP("Searching") \
1145         REQ_(SEARCH,            "Search the view"), \
1146         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1147         REQ_(FIND_NEXT,         "Find next search match"), \
1148         REQ_(FIND_PREV,         "Find previous search match"), \
1149         \
1150         REQ_GROUP("Option manipulation") \
1151         REQ_(OPTIONS,           "Open option menu"), \
1152         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1153         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1154         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1155         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1156         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1157         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1158         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1159         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1160         \
1161         REQ_GROUP("Misc") \
1162         REQ_(PROMPT,            "Bring up the prompt"), \
1163         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1164         REQ_(SHOW_VERSION,      "Show version information"), \
1165         REQ_(STOP_LOADING,      "Stop all loading views"), \
1166         REQ_(EDIT,              "Open in editor"), \
1167         REQ_(NONE,              "Do nothing")
1170 /* User action requests. */
1171 enum request {
1172 #define REQ_GROUP(help)
1173 #define REQ_(req, help) REQ_##req
1175         /* Offset all requests to avoid conflicts with ncurses getch values. */
1176         REQ_UNKNOWN = KEY_MAX + 1,
1177         REQ_OFFSET,
1178         REQ_INFO
1180 #undef  REQ_GROUP
1181 #undef  REQ_
1182 };
1184 struct request_info {
1185         enum request request;
1186         const char *name;
1187         int namelen;
1188         const char *help;
1189 };
1191 static const struct request_info req_info[] = {
1192 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1193 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1194         REQ_INFO
1195 #undef  REQ_GROUP
1196 #undef  REQ_
1197 };
1199 static enum request
1200 get_request(const char *name)
1202         int namelen = strlen(name);
1203         int i;
1205         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1206                 if (enum_equals(req_info[i], name, namelen))
1207                         return req_info[i].request;
1209         return REQ_UNKNOWN;
1213 /*
1214  * Options
1215  */
1217 /* Option and state variables. */
1218 static enum date opt_date               = DATE_DEFAULT;
1219 static enum author opt_author           = AUTHOR_DEFAULT;
1220 static bool opt_line_number             = FALSE;
1221 static bool opt_line_graphics           = TRUE;
1222 static bool opt_rev_graph               = FALSE;
1223 static bool opt_show_refs               = TRUE;
1224 static int opt_num_interval             = 5;
1225 static double opt_hscroll               = 0.50;
1226 static double opt_scale_split_view      = 2.0 / 3.0;
1227 static int opt_tab_size                 = 8;
1228 static int opt_author_cols              = AUTHOR_COLS;
1229 static char opt_path[SIZEOF_STR]        = "";
1230 static char opt_file[SIZEOF_STR]        = "";
1231 static char opt_ref[SIZEOF_REF]         = "";
1232 static char opt_head[SIZEOF_REF]        = "";
1233 static char opt_remote[SIZEOF_REF]      = "";
1234 static char opt_encoding[20]            = "UTF-8";
1235 static iconv_t opt_iconv_in             = ICONV_NONE;
1236 static iconv_t opt_iconv_out            = ICONV_NONE;
1237 static char opt_search[SIZEOF_STR]      = "";
1238 static char opt_cdup[SIZEOF_STR]        = "";
1239 static char opt_prefix[SIZEOF_STR]      = "";
1240 static char opt_git_dir[SIZEOF_STR]     = "";
1241 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1242 static char opt_editor[SIZEOF_STR]      = "";
1243 static FILE *opt_tty                    = NULL;
1245 #define is_initial_commit()     (!get_ref_head())
1246 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1249 /*
1250  * Line-oriented content detection.
1251  */
1253 #define LINE_INFO \
1254 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1255 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1256 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1257 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1258 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1259 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1261 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1262 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1263 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1264 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1265 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1266 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1267 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1268 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1269 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1270 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1271 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1272 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1273 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1275 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1276 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1277 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1278 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1279 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1280 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1281 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1282 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1283 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1284 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1285 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1286 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1287 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1288 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1289 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1290 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1291 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1292 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1293 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1294 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1295 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1296 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1297 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1298 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1299 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1300 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1301 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1302 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1303 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1304 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1305 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1306 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1307 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1308 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1309 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1310 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1311 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1312 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1314 enum line_type {
1315 #define LINE(type, line, fg, bg, attr) \
1316         LINE_##type
1317         LINE_INFO,
1318         LINE_NONE
1319 #undef  LINE
1320 };
1322 struct line_info {
1323         const char *name;       /* Option name. */
1324         int namelen;            /* Size of option name. */
1325         const char *line;       /* The start of line to match. */
1326         int linelen;            /* Size of string to match. */
1327         int fg, bg, attr;       /* Color and text attributes for the lines. */
1328 };
1330 static struct line_info line_info[] = {
1331 #define LINE(type, line, fg, bg, attr) \
1332         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1333         LINE_INFO
1334 #undef  LINE
1335 };
1337 static enum line_type
1338 get_line_type(const char *line)
1340         int linelen = strlen(line);
1341         enum line_type type;
1343         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1344                 /* Case insensitive search matches Signed-off-by lines better. */
1345                 if (linelen >= line_info[type].linelen &&
1346                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1347                         return type;
1349         return LINE_DEFAULT;
1352 static inline int
1353 get_line_attr(enum line_type type)
1355         assert(type < ARRAY_SIZE(line_info));
1356         return COLOR_PAIR(type) | line_info[type].attr;
1359 static struct line_info *
1360 get_line_info(const char *name)
1362         size_t namelen = strlen(name);
1363         enum line_type type;
1365         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1366                 if (enum_equals(line_info[type], name, namelen))
1367                         return &line_info[type];
1369         return NULL;
1372 static void
1373 init_colors(void)
1375         int default_bg = line_info[LINE_DEFAULT].bg;
1376         int default_fg = line_info[LINE_DEFAULT].fg;
1377         enum line_type type;
1379         start_color();
1381         if (assume_default_colors(default_fg, default_bg) == ERR) {
1382                 default_bg = COLOR_BLACK;
1383                 default_fg = COLOR_WHITE;
1384         }
1386         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1387                 struct line_info *info = &line_info[type];
1388                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1389                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1391                 init_pair(type, fg, bg);
1392         }
1395 struct line {
1396         enum line_type type;
1398         /* State flags */
1399         unsigned int selected:1;
1400         unsigned int dirty:1;
1401         unsigned int cleareol:1;
1402         unsigned int other:16;
1404         void *data;             /* User data */
1405 };
1408 /*
1409  * Keys
1410  */
1412 struct keybinding {
1413         int alias;
1414         enum request request;
1415 };
1417 static struct keybinding default_keybindings[] = {
1418         /* View switching */
1419         { 'm',          REQ_VIEW_MAIN },
1420         { 'd',          REQ_VIEW_DIFF },
1421         { 'l',          REQ_VIEW_LOG },
1422         { 't',          REQ_VIEW_TREE },
1423         { 'f',          REQ_VIEW_BLOB },
1424         { 'B',          REQ_VIEW_BLAME },
1425         { 'H',          REQ_VIEW_BRANCH },
1426         { 'p',          REQ_VIEW_PAGER },
1427         { 'h',          REQ_VIEW_HELP },
1428         { 'S',          REQ_VIEW_STATUS },
1429         { 'c',          REQ_VIEW_STAGE },
1431         /* View manipulation */
1432         { 'q',          REQ_VIEW_CLOSE },
1433         { KEY_TAB,      REQ_VIEW_NEXT },
1434         { KEY_RETURN,   REQ_ENTER },
1435         { KEY_UP,       REQ_PREVIOUS },
1436         { KEY_DOWN,     REQ_NEXT },
1437         { 'R',          REQ_REFRESH },
1438         { KEY_F(5),     REQ_REFRESH },
1439         { 'O',          REQ_MAXIMIZE },
1441         /* Cursor navigation */
1442         { 'k',          REQ_MOVE_UP },
1443         { 'j',          REQ_MOVE_DOWN },
1444         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1445         { KEY_END,      REQ_MOVE_LAST_LINE },
1446         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1447         { ' ',          REQ_MOVE_PAGE_DOWN },
1448         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1449         { 'b',          REQ_MOVE_PAGE_UP },
1450         { '-',          REQ_MOVE_PAGE_UP },
1452         /* Scrolling */
1453         { KEY_LEFT,     REQ_SCROLL_LEFT },
1454         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1455         { KEY_IC,       REQ_SCROLL_LINE_UP },
1456         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1457         { 'w',          REQ_SCROLL_PAGE_UP },
1458         { 's',          REQ_SCROLL_PAGE_DOWN },
1460         /* Searching */
1461         { '/',          REQ_SEARCH },
1462         { '?',          REQ_SEARCH_BACK },
1463         { 'n',          REQ_FIND_NEXT },
1464         { 'N',          REQ_FIND_PREV },
1466         /* Misc */
1467         { 'Q',          REQ_QUIT },
1468         { 'z',          REQ_STOP_LOADING },
1469         { 'v',          REQ_SHOW_VERSION },
1470         { 'r',          REQ_SCREEN_REDRAW },
1471         { 'o',          REQ_OPTIONS },
1472         { '.',          REQ_TOGGLE_LINENO },
1473         { 'D',          REQ_TOGGLE_DATE },
1474         { 'A',          REQ_TOGGLE_AUTHOR },
1475         { 'g',          REQ_TOGGLE_REV_GRAPH },
1476         { 'F',          REQ_TOGGLE_REFS },
1477         { 'I',          REQ_TOGGLE_SORT_ORDER },
1478         { 'i',          REQ_TOGGLE_SORT_FIELD },
1479         { ':',          REQ_PROMPT },
1480         { 'u',          REQ_STATUS_UPDATE },
1481         { '!',          REQ_STATUS_REVERT },
1482         { 'M',          REQ_STATUS_MERGE },
1483         { '@',          REQ_STAGE_NEXT },
1484         { ',',          REQ_PARENT },
1485         { 'e',          REQ_EDIT },
1486 };
1488 #define KEYMAP_INFO \
1489         KEYMAP_(GENERIC), \
1490         KEYMAP_(MAIN), \
1491         KEYMAP_(DIFF), \
1492         KEYMAP_(LOG), \
1493         KEYMAP_(TREE), \
1494         KEYMAP_(BLOB), \
1495         KEYMAP_(BLAME), \
1496         KEYMAP_(BRANCH), \
1497         KEYMAP_(PAGER), \
1498         KEYMAP_(HELP), \
1499         KEYMAP_(STATUS), \
1500         KEYMAP_(STAGE)
1502 enum keymap {
1503 #define KEYMAP_(name) KEYMAP_##name
1504         KEYMAP_INFO
1505 #undef  KEYMAP_
1506 };
1508 static const struct enum_map keymap_table[] = {
1509 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1510         KEYMAP_INFO
1511 #undef  KEYMAP_
1512 };
1514 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1516 struct keybinding_table {
1517         struct keybinding *data;
1518         size_t size;
1519 };
1521 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1523 static void
1524 add_keybinding(enum keymap keymap, enum request request, int key)
1526         struct keybinding_table *table = &keybindings[keymap];
1527         size_t i;
1529         for (i = 0; i < keybindings[keymap].size; i++) {
1530                 if (keybindings[keymap].data[i].alias == key) {
1531                         keybindings[keymap].data[i].request = request;
1532                         return;
1533                 }
1534         }
1536         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1537         if (!table->data)
1538                 die("Failed to allocate keybinding");
1539         table->data[table->size].alias = key;
1540         table->data[table->size++].request = request;
1542         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1543                 int i;
1545                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1546                         if (default_keybindings[i].alias == key)
1547                                 default_keybindings[i].request = REQ_NONE;
1548         }
1551 /* Looks for a key binding first in the given map, then in the generic map, and
1552  * lastly in the default keybindings. */
1553 static enum request
1554 get_keybinding(enum keymap keymap, int key)
1556         size_t i;
1558         for (i = 0; i < keybindings[keymap].size; i++)
1559                 if (keybindings[keymap].data[i].alias == key)
1560                         return keybindings[keymap].data[i].request;
1562         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1563                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1564                         return keybindings[KEYMAP_GENERIC].data[i].request;
1566         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1567                 if (default_keybindings[i].alias == key)
1568                         return default_keybindings[i].request;
1570         return (enum request) key;
1574 struct key {
1575         const char *name;
1576         int value;
1577 };
1579 static const struct key key_table[] = {
1580         { "Enter",      KEY_RETURN },
1581         { "Space",      ' ' },
1582         { "Backspace",  KEY_BACKSPACE },
1583         { "Tab",        KEY_TAB },
1584         { "Escape",     KEY_ESC },
1585         { "Left",       KEY_LEFT },
1586         { "Right",      KEY_RIGHT },
1587         { "Up",         KEY_UP },
1588         { "Down",       KEY_DOWN },
1589         { "Insert",     KEY_IC },
1590         { "Delete",     KEY_DC },
1591         { "Hash",       '#' },
1592         { "Home",       KEY_HOME },
1593         { "End",        KEY_END },
1594         { "PageUp",     KEY_PPAGE },
1595         { "PageDown",   KEY_NPAGE },
1596         { "F1",         KEY_F(1) },
1597         { "F2",         KEY_F(2) },
1598         { "F3",         KEY_F(3) },
1599         { "F4",         KEY_F(4) },
1600         { "F5",         KEY_F(5) },
1601         { "F6",         KEY_F(6) },
1602         { "F7",         KEY_F(7) },
1603         { "F8",         KEY_F(8) },
1604         { "F9",         KEY_F(9) },
1605         { "F10",        KEY_F(10) },
1606         { "F11",        KEY_F(11) },
1607         { "F12",        KEY_F(12) },
1608 };
1610 static int
1611 get_key_value(const char *name)
1613         int i;
1615         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1616                 if (!strcasecmp(key_table[i].name, name))
1617                         return key_table[i].value;
1619         if (strlen(name) == 1 && isprint(*name))
1620                 return (int) *name;
1622         return ERR;
1625 static const char *
1626 get_key_name(int key_value)
1628         static char key_char[] = "'X'";
1629         const char *seq = NULL;
1630         int key;
1632         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1633                 if (key_table[key].value == key_value)
1634                         seq = key_table[key].name;
1636         if (seq == NULL &&
1637             key_value < 127 &&
1638             isprint(key_value)) {
1639                 key_char[1] = (char) key_value;
1640                 seq = key_char;
1641         }
1643         return seq ? seq : "(no key)";
1646 static bool
1647 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1649         const char *sep = *pos > 0 ? ", " : "";
1650         const char *keyname = get_key_name(keybinding->alias);
1652         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1655 static bool
1656 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1657                            enum keymap keymap, bool all)
1659         int i;
1661         for (i = 0; i < keybindings[keymap].size; i++) {
1662                 if (keybindings[keymap].data[i].request == request) {
1663                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1664                                 return FALSE;
1665                         if (!all)
1666                                 break;
1667                 }
1668         }
1670         return TRUE;
1673 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1675 static const char *
1676 get_keys(enum keymap keymap, enum request request, bool all)
1678         static char buf[BUFSIZ];
1679         size_t pos = 0;
1680         int i;
1682         buf[pos] = 0;
1684         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1685                 return "Too many keybindings!";
1686         if (pos > 0 && !all)
1687                 return buf;
1689         if (keymap != KEYMAP_GENERIC) {
1690                 /* Only the generic keymap includes the default keybindings when
1691                  * listing all keys. */
1692                 if (all)
1693                         return buf;
1695                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1696                         return "Too many keybindings!";
1697                 if (pos)
1698                         return buf;
1699         }
1701         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1702                 if (default_keybindings[i].request == request) {
1703                         if (!append_key(buf, &pos, &default_keybindings[i]))
1704                                 return "Too many keybindings!";
1705                         if (!all)
1706                                 return buf;
1707                 }
1708         }
1710         return buf;
1713 struct run_request {
1714         enum keymap keymap;
1715         int key;
1716         const char *argv[SIZEOF_ARG];
1717 };
1719 static struct run_request *run_request;
1720 static size_t run_requests;
1722 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1724 static enum request
1725 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1727         struct run_request *req;
1729         if (argc >= ARRAY_SIZE(req->argv) - 1)
1730                 return REQ_NONE;
1732         if (!realloc_run_requests(&run_request, run_requests, 1))
1733                 return REQ_NONE;
1735         req = &run_request[run_requests];
1736         req->keymap = keymap;
1737         req->key = key;
1738         req->argv[0] = NULL;
1740         if (!format_argv(req->argv, argv, FORMAT_NONE))
1741                 return REQ_NONE;
1743         return REQ_NONE + ++run_requests;
1746 static struct run_request *
1747 get_run_request(enum request request)
1749         if (request <= REQ_NONE)
1750                 return NULL;
1751         return &run_request[request - REQ_NONE - 1];
1754 static void
1755 add_builtin_run_requests(void)
1757         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1758         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1759         const char *commit[] = { "git", "commit", NULL };
1760         const char *gc[] = { "git", "gc", NULL };
1761         struct {
1762                 enum keymap keymap;
1763                 int key;
1764                 int argc;
1765                 const char **argv;
1766         } reqs[] = {
1767                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1768                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1769                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1770                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1771         };
1772         int i;
1774         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1775                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1777                 if (req != reqs[i].key)
1778                         continue;
1779                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1780                 if (req != REQ_NONE)
1781                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1782         }
1785 /*
1786  * User config file handling.
1787  */
1789 static int   config_lineno;
1790 static bool  config_errors;
1791 static const char *config_msg;
1793 static const struct enum_map color_map[] = {
1794 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1795         COLOR_MAP(DEFAULT),
1796         COLOR_MAP(BLACK),
1797         COLOR_MAP(BLUE),
1798         COLOR_MAP(CYAN),
1799         COLOR_MAP(GREEN),
1800         COLOR_MAP(MAGENTA),
1801         COLOR_MAP(RED),
1802         COLOR_MAP(WHITE),
1803         COLOR_MAP(YELLOW),
1804 };
1806 static const struct enum_map attr_map[] = {
1807 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1808         ATTR_MAP(NORMAL),
1809         ATTR_MAP(BLINK),
1810         ATTR_MAP(BOLD),
1811         ATTR_MAP(DIM),
1812         ATTR_MAP(REVERSE),
1813         ATTR_MAP(STANDOUT),
1814         ATTR_MAP(UNDERLINE),
1815 };
1817 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1819 static int parse_step(double *opt, const char *arg)
1821         *opt = atoi(arg);
1822         if (!strchr(arg, '%'))
1823                 return OK;
1825         /* "Shift down" so 100% and 1 does not conflict. */
1826         *opt = (*opt - 1) / 100;
1827         if (*opt >= 1.0) {
1828                 *opt = 0.99;
1829                 config_msg = "Step value larger than 100%";
1830                 return ERR;
1831         }
1832         if (*opt < 0.0) {
1833                 *opt = 1;
1834                 config_msg = "Invalid step value";
1835                 return ERR;
1836         }
1837         return OK;
1840 static int
1841 parse_int(int *opt, const char *arg, int min, int max)
1843         int value = atoi(arg);
1845         if (min <= value && value <= max) {
1846                 *opt = value;
1847                 return OK;
1848         }
1850         config_msg = "Integer value out of bound";
1851         return ERR;
1854 static bool
1855 set_color(int *color, const char *name)
1857         if (map_enum(color, color_map, name))
1858                 return TRUE;
1859         if (!prefixcmp(name, "color"))
1860                 return parse_int(color, name + 5, 0, 255) == OK;
1861         return FALSE;
1864 /* Wants: object fgcolor bgcolor [attribute] */
1865 static int
1866 option_color_command(int argc, const char *argv[])
1868         struct line_info *info;
1870         if (argc < 3) {
1871                 config_msg = "Wrong number of arguments given to color command";
1872                 return ERR;
1873         }
1875         info = get_line_info(argv[0]);
1876         if (!info) {
1877                 static const struct enum_map obsolete[] = {
1878                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1879                         ENUM_MAP("main-date",   LINE_DATE),
1880                         ENUM_MAP("main-author", LINE_AUTHOR),
1881                 };
1882                 int index;
1884                 if (!map_enum(&index, obsolete, argv[0])) {
1885                         config_msg = "Unknown color name";
1886                         return ERR;
1887                 }
1888                 info = &line_info[index];
1889         }
1891         if (!set_color(&info->fg, argv[1]) ||
1892             !set_color(&info->bg, argv[2])) {
1893                 config_msg = "Unknown color";
1894                 return ERR;
1895         }
1897         info->attr = 0;
1898         while (argc-- > 3) {
1899                 int attr;
1901                 if (!set_attribute(&attr, argv[argc])) {
1902                         config_msg = "Unknown attribute";
1903                         return ERR;
1904                 }
1905                 info->attr |= attr;
1906         }
1908         return OK;
1911 static int parse_bool(bool *opt, const char *arg)
1913         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1914                 ? TRUE : FALSE;
1915         return OK;
1918 static int parse_enum_do(unsigned int *opt, const char *arg,
1919                          const struct enum_map *map, size_t map_size)
1921         bool is_true;
1923         assert(map_size > 1);
1925         if (map_enum_do(map, map_size, (int *) opt, arg))
1926                 return OK;
1928         if (parse_bool(&is_true, arg) != OK)
1929                 return ERR;
1931         *opt = is_true ? map[1].value : map[0].value;
1932         return OK;
1935 #define parse_enum(opt, arg, map) \
1936         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1938 static int
1939 parse_string(char *opt, const char *arg, size_t optsize)
1941         int arglen = strlen(arg);
1943         switch (arg[0]) {
1944         case '\"':
1945         case '\'':
1946                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1947                         config_msg = "Unmatched quotation";
1948                         return ERR;
1949                 }
1950                 arg += 1; arglen -= 2;
1951         default:
1952                 string_ncopy_do(opt, optsize, arg, arglen);
1953                 return OK;
1954         }
1957 /* Wants: name = value */
1958 static int
1959 option_set_command(int argc, const char *argv[])
1961         if (argc != 3) {
1962                 config_msg = "Wrong number of arguments given to set command";
1963                 return ERR;
1964         }
1966         if (strcmp(argv[1], "=")) {
1967                 config_msg = "No value assigned";
1968                 return ERR;
1969         }
1971         if (!strcmp(argv[0], "show-author"))
1972                 return parse_enum(&opt_author, argv[2], author_map);
1974         if (!strcmp(argv[0], "show-date"))
1975                 return parse_enum(&opt_date, argv[2], date_map);
1977         if (!strcmp(argv[0], "show-rev-graph"))
1978                 return parse_bool(&opt_rev_graph, argv[2]);
1980         if (!strcmp(argv[0], "show-refs"))
1981                 return parse_bool(&opt_show_refs, argv[2]);
1983         if (!strcmp(argv[0], "show-line-numbers"))
1984                 return parse_bool(&opt_line_number, argv[2]);
1986         if (!strcmp(argv[0], "line-graphics"))
1987                 return parse_bool(&opt_line_graphics, argv[2]);
1989         if (!strcmp(argv[0], "line-number-interval"))
1990                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1992         if (!strcmp(argv[0], "author-width"))
1993                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1995         if (!strcmp(argv[0], "horizontal-scroll"))
1996                 return parse_step(&opt_hscroll, argv[2]);
1998         if (!strcmp(argv[0], "split-view-height"))
1999                 return parse_step(&opt_scale_split_view, argv[2]);
2001         if (!strcmp(argv[0], "tab-size"))
2002                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2004         if (!strcmp(argv[0], "commit-encoding"))
2005                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2007         config_msg = "Unknown variable name";
2008         return ERR;
2011 /* Wants: mode request key */
2012 static int
2013 option_bind_command(int argc, const char *argv[])
2015         enum request request;
2016         int keymap = -1;
2017         int key;
2019         if (argc < 3) {
2020                 config_msg = "Wrong number of arguments given to bind command";
2021                 return ERR;
2022         }
2024         if (!set_keymap(&keymap, argv[0])) {
2025                 config_msg = "Unknown key map";
2026                 return ERR;
2027         }
2029         key = get_key_value(argv[1]);
2030         if (key == ERR) {
2031                 config_msg = "Unknown key";
2032                 return ERR;
2033         }
2035         request = get_request(argv[2]);
2036         if (request == REQ_UNKNOWN) {
2037                 static const struct enum_map obsolete[] = {
2038                         ENUM_MAP("cherry-pick",         REQ_NONE),
2039                         ENUM_MAP("screen-resize",       REQ_NONE),
2040                         ENUM_MAP("tree-parent",         REQ_PARENT),
2041                 };
2042                 int alias;
2044                 if (map_enum(&alias, obsolete, argv[2])) {
2045                         if (alias != REQ_NONE)
2046                                 add_keybinding(keymap, alias, key);
2047                         config_msg = "Obsolete request name";
2048                         return ERR;
2049                 }
2050         }
2051         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2052                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2053         if (request == REQ_UNKNOWN) {
2054                 config_msg = "Unknown request name";
2055                 return ERR;
2056         }
2058         add_keybinding(keymap, request, key);
2060         return OK;
2063 static int
2064 set_option(const char *opt, char *value)
2066         const char *argv[SIZEOF_ARG];
2067         int argc = 0;
2069         if (!argv_from_string(argv, &argc, value)) {
2070                 config_msg = "Too many option arguments";
2071                 return ERR;
2072         }
2074         if (!strcmp(opt, "color"))
2075                 return option_color_command(argc, argv);
2077         if (!strcmp(opt, "set"))
2078                 return option_set_command(argc, argv);
2080         if (!strcmp(opt, "bind"))
2081                 return option_bind_command(argc, argv);
2083         config_msg = "Unknown option command";
2084         return ERR;
2087 static int
2088 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2090         int status = OK;
2092         config_lineno++;
2093         config_msg = "Internal error";
2095         /* Check for comment markers, since read_properties() will
2096          * only ensure opt and value are split at first " \t". */
2097         optlen = strcspn(opt, "#");
2098         if (optlen == 0)
2099                 return OK;
2101         if (opt[optlen] != 0) {
2102                 config_msg = "No option value";
2103                 status = ERR;
2105         }  else {
2106                 /* Look for comment endings in the value. */
2107                 size_t len = strcspn(value, "#");
2109                 if (len < valuelen) {
2110                         valuelen = len;
2111                         value[valuelen] = 0;
2112                 }
2114                 status = set_option(opt, value);
2115         }
2117         if (status == ERR) {
2118                 warn("Error on line %d, near '%.*s': %s",
2119                      config_lineno, (int) optlen, opt, config_msg);
2120                 config_errors = TRUE;
2121         }
2123         /* Always keep going if errors are encountered. */
2124         return OK;
2127 static void
2128 load_option_file(const char *path)
2130         struct io io = {};
2132         /* It's OK that the file doesn't exist. */
2133         if (!io_open(&io, "%s", path))
2134                 return;
2136         config_lineno = 0;
2137         config_errors = FALSE;
2139         if (io_load(&io, " \t", read_option) == ERR ||
2140             config_errors == TRUE)
2141                 warn("Errors while loading %s.", path);
2144 static int
2145 load_options(void)
2147         const char *home = getenv("HOME");
2148         const char *tigrc_user = getenv("TIGRC_USER");
2149         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2150         char buf[SIZEOF_STR];
2152         if (!tigrc_system)
2153                 tigrc_system = SYSCONFDIR "/tigrc";
2154         load_option_file(tigrc_system);
2156         if (!tigrc_user) {
2157                 if (!home || !string_format(buf, "%s/.tigrc", home))
2158                         return ERR;
2159                 tigrc_user = buf;
2160         }
2161         load_option_file(tigrc_user);
2163         /* Add _after_ loading config files to avoid adding run requests
2164          * that conflict with keybindings. */
2165         add_builtin_run_requests();
2167         return OK;
2171 /*
2172  * The viewer
2173  */
2175 struct view;
2176 struct view_ops;
2178 /* The display array of active views and the index of the current view. */
2179 static struct view *display[2];
2180 static unsigned int current_view;
2182 #define foreach_displayed_view(view, i) \
2183         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2185 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2187 /* Current head and commit ID */
2188 static char ref_blob[SIZEOF_REF]        = "";
2189 static char ref_commit[SIZEOF_REF]      = "HEAD";
2190 static char ref_head[SIZEOF_REF]        = "HEAD";
2191 static char ref_branch[SIZEOF_REF]      = "";
2193 enum view_type {
2194         VIEW_MAIN,
2195         VIEW_DIFF,
2196         VIEW_LOG,
2197         VIEW_TREE,
2198         VIEW_BLOB,
2199         VIEW_BLAME,
2200         VIEW_BRANCH,
2201         VIEW_HELP,
2202         VIEW_PAGER,
2203         VIEW_STATUS,
2204         VIEW_STAGE,
2205 };
2207 struct view {
2208         enum view_type type;    /* View type */
2209         const char *name;       /* View name */
2210         const char *cmd_env;    /* Command line set via environment */
2211         const char *id;         /* Points to either of ref_{head,commit,blob} */
2213         struct view_ops *ops;   /* View operations */
2215         enum keymap keymap;     /* What keymap does this view have */
2216         bool git_dir;           /* Whether the view requires a git directory. */
2217         bool refresh;           /* Whether the view supports refreshing. */
2219         char ref[SIZEOF_REF];   /* Hovered commit reference */
2220         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2222         int height, width;      /* The width and height of the main window */
2223         WINDOW *win;            /* The main window */
2224         WINDOW *title;          /* The title window living below the main window */
2226         /* Navigation */
2227         unsigned long offset;   /* Offset of the window top */
2228         unsigned long yoffset;  /* Offset from the window side. */
2229         unsigned long lineno;   /* Current line number */
2230         unsigned long p_offset; /* Previous offset of the window top */
2231         unsigned long p_yoffset;/* Previous offset from the window side */
2232         unsigned long p_lineno; /* Previous current line number */
2233         bool p_restore;         /* Should the previous position be restored. */
2235         /* Searching */
2236         char grep[SIZEOF_STR];  /* Search string */
2237         regex_t *regex;         /* Pre-compiled regexp */
2239         /* If non-NULL, points to the view that opened this view. If this view
2240          * is closed tig will switch back to the parent view. */
2241         struct view *parent;
2242         struct view *prev;
2244         /* Buffering */
2245         size_t lines;           /* Total number of lines */
2246         struct line *line;      /* Line index */
2247         unsigned int digits;    /* Number of digits in the lines member. */
2249         /* Drawing */
2250         struct line *curline;   /* Line currently being drawn. */
2251         enum line_type curtype; /* Attribute currently used for drawing. */
2252         unsigned long col;      /* Column when drawing. */
2253         bool has_scrolled;      /* View was scrolled. */
2255         /* Loading */
2256         struct io io;
2257         struct io *pipe;
2258         time_t start_time;
2259         time_t update_secs;
2260 };
2262 struct view_ops {
2263         /* What type of content being displayed. Used in the title bar. */
2264         const char *type;
2265         /* Default command arguments. */
2266         const char **argv;
2267         /* Open and reads in all view content. */
2268         bool (*open)(struct view *view);
2269         /* Read one line; updates view->line. */
2270         bool (*read)(struct view *view, char *data);
2271         /* Draw one line; @lineno must be < view->height. */
2272         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2273         /* Depending on view handle a special requests. */
2274         enum request (*request)(struct view *view, enum request request, struct line *line);
2275         /* Search for regexp in a line. */
2276         bool (*grep)(struct view *view, struct line *line);
2277         /* Select line */
2278         void (*select)(struct view *view, struct line *line);
2279         /* Prepare view for loading */
2280         bool (*prepare)(struct view *view);
2281 };
2283 static struct view_ops blame_ops;
2284 static struct view_ops blob_ops;
2285 static struct view_ops diff_ops;
2286 static struct view_ops help_ops;
2287 static struct view_ops log_ops;
2288 static struct view_ops main_ops;
2289 static struct view_ops pager_ops;
2290 static struct view_ops stage_ops;
2291 static struct view_ops status_ops;
2292 static struct view_ops tree_ops;
2293 static struct view_ops branch_ops;
2295 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2296         { type, name, #env, ref, ops, map, git, refresh }
2298 #define VIEW_(id, name, ops, git, refresh, ref) \
2299         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2301 static struct view views[] = {
2302         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  TRUE,  ref_head),
2303         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  FALSE, ref_commit),
2304         VIEW_(LOG,    "log",    &log_ops,    TRUE,  TRUE,  ref_head),
2305         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  FALSE, ref_commit),
2306         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  FALSE, ref_blob),
2307         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  FALSE, ref_commit),
2308         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  TRUE,  ref_head),
2309         VIEW_(HELP,   "help",   &help_ops,   FALSE, FALSE, ""),
2310         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, FALSE, "stdin"),
2311         VIEW_(STATUS, "status", &status_ops, TRUE,  TRUE,  ""),
2312         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  TRUE,  ""),
2313 };
2315 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2317 #define foreach_view(view, i) \
2318         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2320 #define view_is_displayed(view) \
2321         (view == display[0] || view == display[1])
2323 static inline void
2324 set_view_attr(struct view *view, enum line_type type)
2326         if (!view->curline->selected && view->curtype != type) {
2327                 (void) wattrset(view->win, get_line_attr(type));
2328                 wchgat(view->win, -1, 0, type, NULL);
2329                 view->curtype = type;
2330         }
2333 static int
2334 draw_chars(struct view *view, enum line_type type, const char *string,
2335            int max_len, bool use_tilde)
2337         static char out_buffer[BUFSIZ * 2];
2338         int len = 0;
2339         int col = 0;
2340         int trimmed = FALSE;
2341         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2343         if (max_len <= 0)
2344                 return 0;
2346         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2348         set_view_attr(view, type);
2349         if (len > 0) {
2350                 if (opt_iconv_out != ICONV_NONE) {
2351                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2352                         size_t inlen = len + 1;
2354                         char *outbuf = out_buffer;
2355                         size_t outlen = sizeof(out_buffer);
2357                         size_t ret;
2359                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2360                         if (ret != (size_t) -1) {
2361                                 string = out_buffer;
2362                                 len = sizeof(out_buffer) - outlen;
2363                         }
2364                 }
2366                 waddnstr(view->win, string, len);
2367         }
2368         if (trimmed && use_tilde) {
2369                 set_view_attr(view, LINE_DELIMITER);
2370                 waddch(view->win, '~');
2371                 col++;
2372         }
2374         return col;
2377 static int
2378 draw_space(struct view *view, enum line_type type, int max, int spaces)
2380         static char space[] = "                    ";
2381         int col = 0;
2383         spaces = MIN(max, spaces);
2385         while (spaces > 0) {
2386                 int len = MIN(spaces, sizeof(space) - 1);
2388                 col += draw_chars(view, type, space, len, FALSE);
2389                 spaces -= len;
2390         }
2392         return col;
2395 static bool
2396 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2398         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2399         return view->width + view->yoffset <= view->col;
2402 static bool
2403 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2405         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2406         int max = view->width + view->yoffset - view->col;
2407         int i;
2409         if (max < size)
2410                 size = max;
2412         set_view_attr(view, type);
2413         /* Using waddch() instead of waddnstr() ensures that
2414          * they'll be rendered correctly for the cursor line. */
2415         for (i = skip; i < size; i++)
2416                 waddch(view->win, graphic[i]);
2418         view->col += size;
2419         if (size < max && skip <= size)
2420                 waddch(view->win, ' ');
2421         view->col++;
2423         return view->width + view->yoffset <= view->col;
2426 static bool
2427 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2429         int max = MIN(view->width + view->yoffset - view->col, len);
2430         int col;
2432         if (text)
2433                 col = draw_chars(view, type, text, max - 1, trim);
2434         else
2435                 col = draw_space(view, type, max - 1, max - 1);
2437         view->col += col;
2438         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2439         return view->width + view->yoffset <= view->col;
2442 static bool
2443 draw_date(struct view *view, struct time *time)
2445         const char *date = mkdate(time, opt_date);
2446         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2448         return draw_field(view, LINE_DATE, date, cols, FALSE);
2451 static bool
2452 draw_author(struct view *view, const char *author)
2454         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2455         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2457         if (abbreviate && author)
2458                 author = get_author_initials(author);
2460         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2463 static bool
2464 draw_mode(struct view *view, mode_t mode)
2466         const char *str;
2468         if (S_ISDIR(mode))
2469                 str = "drwxr-xr-x";
2470         else if (S_ISLNK(mode))
2471                 str = "lrwxrwxrwx";
2472         else if (S_ISGITLINK(mode))
2473                 str = "m---------";
2474         else if (S_ISREG(mode) && mode & S_IXUSR)
2475                 str = "-rwxr-xr-x";
2476         else if (S_ISREG(mode))
2477                 str = "-rw-r--r--";
2478         else
2479                 str = "----------";
2481         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2484 static bool
2485 draw_lineno(struct view *view, unsigned int lineno)
2487         char number[10];
2488         int digits3 = view->digits < 3 ? 3 : view->digits;
2489         int max = MIN(view->width + view->yoffset - view->col, digits3);
2490         char *text = NULL;
2491         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2493         lineno += view->offset + 1;
2494         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2495                 static char fmt[] = "%1ld";
2497                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2498                 if (string_format(number, fmt, lineno))
2499                         text = number;
2500         }
2501         if (text)
2502                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2503         else
2504                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2505         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2508 static bool
2509 draw_view_line(struct view *view, unsigned int lineno)
2511         struct line *line;
2512         bool selected = (view->offset + lineno == view->lineno);
2514         assert(view_is_displayed(view));
2516         if (view->offset + lineno >= view->lines)
2517                 return FALSE;
2519         line = &view->line[view->offset + lineno];
2521         wmove(view->win, lineno, 0);
2522         if (line->cleareol)
2523                 wclrtoeol(view->win);
2524         view->col = 0;
2525         view->curline = line;
2526         view->curtype = LINE_NONE;
2527         line->selected = FALSE;
2528         line->dirty = line->cleareol = 0;
2530         if (selected) {
2531                 set_view_attr(view, LINE_CURSOR);
2532                 line->selected = TRUE;
2533                 view->ops->select(view, line);
2534         }
2536         return view->ops->draw(view, line, lineno);
2539 static void
2540 redraw_view_dirty(struct view *view)
2542         bool dirty = FALSE;
2543         int lineno;
2545         for (lineno = 0; lineno < view->height; lineno++) {
2546                 if (view->offset + lineno >= view->lines)
2547                         break;
2548                 if (!view->line[view->offset + lineno].dirty)
2549                         continue;
2550                 dirty = TRUE;
2551                 if (!draw_view_line(view, lineno))
2552                         break;
2553         }
2555         if (!dirty)
2556                 return;
2557         wnoutrefresh(view->win);
2560 static void
2561 redraw_view_from(struct view *view, int lineno)
2563         assert(0 <= lineno && lineno < view->height);
2565         for (; lineno < view->height; lineno++) {
2566                 if (!draw_view_line(view, lineno))
2567                         break;
2568         }
2570         wnoutrefresh(view->win);
2573 static void
2574 redraw_view(struct view *view)
2576         werase(view->win);
2577         redraw_view_from(view, 0);
2581 static void
2582 update_view_title(struct view *view)
2584         char buf[SIZEOF_STR];
2585         char state[SIZEOF_STR];
2586         size_t bufpos = 0, statelen = 0;
2588         assert(view_is_displayed(view));
2590         if (view->type != VIEW_STATUS && view->lines) {
2591                 unsigned int view_lines = view->offset + view->height;
2592                 unsigned int lines = view->lines
2593                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2594                                    : 0;
2596                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2597                                    view->ops->type,
2598                                    view->lineno + 1,
2599                                    view->lines,
2600                                    lines);
2602         }
2604         if (view->pipe) {
2605                 time_t secs = time(NULL) - view->start_time;
2607                 /* Three git seconds are a long time ... */
2608                 if (secs > 2)
2609                         string_format_from(state, &statelen, " loading %lds", secs);
2610         }
2612         string_format_from(buf, &bufpos, "[%s]", view->name);
2613         if (*view->ref && bufpos < view->width) {
2614                 size_t refsize = strlen(view->ref);
2615                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2617                 if (minsize < view->width)
2618                         refsize = view->width - minsize + 7;
2619                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2620         }
2622         if (statelen && bufpos < view->width) {
2623                 string_format_from(buf, &bufpos, "%s", state);
2624         }
2626         if (view == display[current_view])
2627                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2628         else
2629                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2631         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2632         wclrtoeol(view->title);
2633         wnoutrefresh(view->title);
2636 static int
2637 apply_step(double step, int value)
2639         if (step >= 1)
2640                 return (int) step;
2641         value *= step + 0.01;
2642         return value ? value : 1;
2645 static void
2646 resize_display(void)
2648         int offset, i;
2649         struct view *base = display[0];
2650         struct view *view = display[1] ? display[1] : display[0];
2652         /* Setup window dimensions */
2654         getmaxyx(stdscr, base->height, base->width);
2656         /* Make room for the status window. */
2657         base->height -= 1;
2659         if (view != base) {
2660                 /* Horizontal split. */
2661                 view->width   = base->width;
2662                 view->height  = apply_step(opt_scale_split_view, base->height);
2663                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2664                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2665                 base->height -= view->height;
2667                 /* Make room for the title bar. */
2668                 view->height -= 1;
2669         }
2671         /* Make room for the title bar. */
2672         base->height -= 1;
2674         offset = 0;
2676         foreach_displayed_view (view, i) {
2677                 if (!view->win) {
2678                         view->win = newwin(view->height, 0, offset, 0);
2679                         if (!view->win)
2680                                 die("Failed to create %s view", view->name);
2682                         scrollok(view->win, FALSE);
2684                         view->title = newwin(1, 0, offset + view->height, 0);
2685                         if (!view->title)
2686                                 die("Failed to create title window");
2688                 } else {
2689                         wresize(view->win, view->height, view->width);
2690                         mvwin(view->win,   offset, 0);
2691                         mvwin(view->title, offset + view->height, 0);
2692                 }
2694                 offset += view->height + 1;
2695         }
2698 static void
2699 redraw_display(bool clear)
2701         struct view *view;
2702         int i;
2704         foreach_displayed_view (view, i) {
2705                 if (clear)
2706                         wclear(view->win);
2707                 redraw_view(view);
2708                 update_view_title(view);
2709         }
2712 static void
2713 toggle_enum_option_do(unsigned int *opt, const char *help,
2714                       const struct enum_map *map, size_t size)
2716         *opt = (*opt + 1) % size;
2717         redraw_display(FALSE);
2718         report("Displaying %s %s", enum_name(map[*opt]), help);
2721 #define toggle_enum_option(opt, help, map) \
2722         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2724 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2725 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2727 static void
2728 toggle_view_option(bool *option, const char *help)
2730         *option = !*option;
2731         redraw_display(FALSE);
2732         report("%sabling %s", *option ? "En" : "Dis", help);
2735 static void
2736 open_option_menu(void)
2738         const struct menu_item menu[] = {
2739                 { '.', "line numbers", &opt_line_number },
2740                 { 'D', "date display", &opt_date },
2741                 { 'A', "author display", &opt_author },
2742                 { 'g', "revision graph display", &opt_rev_graph },
2743                 { 'F', "reference display", &opt_show_refs },
2744                 { 0 }
2745         };
2746         int selected = 0;
2748         if (prompt_menu("Toggle option", menu, &selected)) {
2749                 if (menu[selected].data == &opt_date)
2750                         toggle_date();
2751                 else if (menu[selected].data == &opt_author)
2752                         toggle_author();
2753                 else
2754                         toggle_view_option(menu[selected].data, menu[selected].text);
2755         }
2758 static void
2759 maximize_view(struct view *view)
2761         memset(display, 0, sizeof(display));
2762         current_view = 0;
2763         display[current_view] = view;
2764         resize_display();
2765         redraw_display(FALSE);
2766         report("");
2770 /*
2771  * Navigation
2772  */
2774 static bool
2775 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2777         if (lineno >= view->lines)
2778                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2780         if (offset > lineno || offset + view->height <= lineno) {
2781                 unsigned long half = view->height / 2;
2783                 if (lineno > half)
2784                         offset = lineno - half;
2785                 else
2786                         offset = 0;
2787         }
2789         if (offset != view->offset || lineno != view->lineno) {
2790                 view->offset = offset;
2791                 view->lineno = lineno;
2792                 return TRUE;
2793         }
2795         return FALSE;
2798 /* Scrolling backend */
2799 static void
2800 do_scroll_view(struct view *view, int lines)
2802         bool redraw_current_line = FALSE;
2804         /* The rendering expects the new offset. */
2805         view->offset += lines;
2807         assert(0 <= view->offset && view->offset < view->lines);
2808         assert(lines);
2810         /* Move current line into the view. */
2811         if (view->lineno < view->offset) {
2812                 view->lineno = view->offset;
2813                 redraw_current_line = TRUE;
2814         } else if (view->lineno >= view->offset + view->height) {
2815                 view->lineno = view->offset + view->height - 1;
2816                 redraw_current_line = TRUE;
2817         }
2819         assert(view->offset <= view->lineno && view->lineno < view->lines);
2821         /* Redraw the whole screen if scrolling is pointless. */
2822         if (view->height < ABS(lines)) {
2823                 redraw_view(view);
2825         } else {
2826                 int line = lines > 0 ? view->height - lines : 0;
2827                 int end = line + ABS(lines);
2829                 scrollok(view->win, TRUE);
2830                 wscrl(view->win, lines);
2831                 scrollok(view->win, FALSE);
2833                 while (line < end && draw_view_line(view, line))
2834                         line++;
2836                 if (redraw_current_line)
2837                         draw_view_line(view, view->lineno - view->offset);
2838                 wnoutrefresh(view->win);
2839         }
2841         view->has_scrolled = TRUE;
2842         report("");
2845 /* Scroll frontend */
2846 static void
2847 scroll_view(struct view *view, enum request request)
2849         int lines = 1;
2851         assert(view_is_displayed(view));
2853         switch (request) {
2854         case REQ_SCROLL_LEFT:
2855                 if (view->yoffset == 0) {
2856                         report("Cannot scroll beyond the first column");
2857                         return;
2858                 }
2859                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2860                         view->yoffset = 0;
2861                 else
2862                         view->yoffset -= apply_step(opt_hscroll, view->width);
2863                 redraw_view_from(view, 0);
2864                 report("");
2865                 return;
2866         case REQ_SCROLL_RIGHT:
2867                 view->yoffset += apply_step(opt_hscroll, view->width);
2868                 redraw_view(view);
2869                 report("");
2870                 return;
2871         case REQ_SCROLL_PAGE_DOWN:
2872                 lines = view->height;
2873         case REQ_SCROLL_LINE_DOWN:
2874                 if (view->offset + lines > view->lines)
2875                         lines = view->lines - view->offset;
2877                 if (lines == 0 || view->offset + view->height >= view->lines) {
2878                         report("Cannot scroll beyond the last line");
2879                         return;
2880                 }
2881                 break;
2883         case REQ_SCROLL_PAGE_UP:
2884                 lines = view->height;
2885         case REQ_SCROLL_LINE_UP:
2886                 if (lines > view->offset)
2887                         lines = view->offset;
2889                 if (lines == 0) {
2890                         report("Cannot scroll beyond the first line");
2891                         return;
2892                 }
2894                 lines = -lines;
2895                 break;
2897         default:
2898                 die("request %d not handled in switch", request);
2899         }
2901         do_scroll_view(view, lines);
2904 /* Cursor moving */
2905 static void
2906 move_view(struct view *view, enum request request)
2908         int scroll_steps = 0;
2909         int steps;
2911         switch (request) {
2912         case REQ_MOVE_FIRST_LINE:
2913                 steps = -view->lineno;
2914                 break;
2916         case REQ_MOVE_LAST_LINE:
2917                 steps = view->lines - view->lineno - 1;
2918                 break;
2920         case REQ_MOVE_PAGE_UP:
2921                 steps = view->height > view->lineno
2922                       ? -view->lineno : -view->height;
2923                 break;
2925         case REQ_MOVE_PAGE_DOWN:
2926                 steps = view->lineno + view->height >= view->lines
2927                       ? view->lines - view->lineno - 1 : view->height;
2928                 break;
2930         case REQ_MOVE_UP:
2931                 steps = -1;
2932                 break;
2934         case REQ_MOVE_DOWN:
2935                 steps = 1;
2936                 break;
2938         default:
2939                 die("request %d not handled in switch", request);
2940         }
2942         if (steps <= 0 && view->lineno == 0) {
2943                 report("Cannot move beyond the first line");
2944                 return;
2946         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2947                 report("Cannot move beyond the last line");
2948                 return;
2949         }
2951         /* Move the current line */
2952         view->lineno += steps;
2953         assert(0 <= view->lineno && view->lineno < view->lines);
2955         /* Check whether the view needs to be scrolled */
2956         if (view->lineno < view->offset ||
2957             view->lineno >= view->offset + view->height) {
2958                 scroll_steps = steps;
2959                 if (steps < 0 && -steps > view->offset) {
2960                         scroll_steps = -view->offset;
2962                 } else if (steps > 0) {
2963                         if (view->lineno == view->lines - 1 &&
2964                             view->lines > view->height) {
2965                                 scroll_steps = view->lines - view->offset - 1;
2966                                 if (scroll_steps >= view->height)
2967                                         scroll_steps -= view->height - 1;
2968                         }
2969                 }
2970         }
2972         if (!view_is_displayed(view)) {
2973                 view->offset += scroll_steps;
2974                 assert(0 <= view->offset && view->offset < view->lines);
2975                 view->ops->select(view, &view->line[view->lineno]);
2976                 return;
2977         }
2979         /* Repaint the old "current" line if we be scrolling */
2980         if (ABS(steps) < view->height)
2981                 draw_view_line(view, view->lineno - steps - view->offset);
2983         if (scroll_steps) {
2984                 do_scroll_view(view, scroll_steps);
2985                 return;
2986         }
2988         /* Draw the current line */
2989         draw_view_line(view, view->lineno - view->offset);
2991         wnoutrefresh(view->win);
2992         report("");
2996 /*
2997  * Searching
2998  */
3000 static void search_view(struct view *view, enum request request);
3002 static bool
3003 grep_text(struct view *view, const char *text[])
3005         regmatch_t pmatch;
3006         size_t i;
3008         for (i = 0; text[i]; i++)
3009                 if (*text[i] &&
3010                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3011                         return TRUE;
3012         return FALSE;
3015 static void
3016 select_view_line(struct view *view, unsigned long lineno)
3018         unsigned long old_lineno = view->lineno;
3019         unsigned long old_offset = view->offset;
3021         if (goto_view_line(view, view->offset, lineno)) {
3022                 if (view_is_displayed(view)) {
3023                         if (old_offset != view->offset) {
3024                                 redraw_view(view);
3025                         } else {
3026                                 draw_view_line(view, old_lineno - view->offset);
3027                                 draw_view_line(view, view->lineno - view->offset);
3028                                 wnoutrefresh(view->win);
3029                         }
3030                 } else {
3031                         view->ops->select(view, &view->line[view->lineno]);
3032                 }
3033         }
3036 static void
3037 find_next(struct view *view, enum request request)
3039         unsigned long lineno = view->lineno;
3040         int direction;
3042         if (!*view->grep) {
3043                 if (!*opt_search)
3044                         report("No previous search");
3045                 else
3046                         search_view(view, request);
3047                 return;
3048         }
3050         switch (request) {
3051         case REQ_SEARCH:
3052         case REQ_FIND_NEXT:
3053                 direction = 1;
3054                 break;
3056         case REQ_SEARCH_BACK:
3057         case REQ_FIND_PREV:
3058                 direction = -1;
3059                 break;
3061         default:
3062                 return;
3063         }
3065         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3066                 lineno += direction;
3068         /* Note, lineno is unsigned long so will wrap around in which case it
3069          * will become bigger than view->lines. */
3070         for (; lineno < view->lines; lineno += direction) {
3071                 if (view->ops->grep(view, &view->line[lineno])) {
3072                         select_view_line(view, lineno);
3073                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3074                         return;
3075                 }
3076         }
3078         report("No match found for '%s'", view->grep);
3081 static void
3082 search_view(struct view *view, enum request request)
3084         int regex_err;
3086         if (view->regex) {
3087                 regfree(view->regex);
3088                 *view->grep = 0;
3089         } else {
3090                 view->regex = calloc(1, sizeof(*view->regex));
3091                 if (!view->regex)
3092                         return;
3093         }
3095         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3096         if (regex_err != 0) {
3097                 char buf[SIZEOF_STR] = "unknown error";
3099                 regerror(regex_err, view->regex, buf, sizeof(buf));
3100                 report("Search failed: %s", buf);
3101                 return;
3102         }
3104         string_copy(view->grep, opt_search);
3106         find_next(view, request);
3109 /*
3110  * Incremental updating
3111  */
3113 static void
3114 reset_view(struct view *view)
3116         int i;
3118         for (i = 0; i < view->lines; i++)
3119                 free(view->line[i].data);
3120         free(view->line);
3122         view->p_offset = view->offset;
3123         view->p_yoffset = view->yoffset;
3124         view->p_lineno = view->lineno;
3126         view->line = NULL;
3127         view->offset = 0;
3128         view->yoffset = 0;
3129         view->lines  = 0;
3130         view->lineno = 0;
3131         view->vid[0] = 0;
3132         view->update_secs = 0;
3135 static void
3136 free_argv(const char *argv[])
3138         int argc;
3140         for (argc = 0; argv[argc]; argc++)
3141                 free((void *) argv[argc]);
3144 static const char *
3145 format_arg(const char *name)
3147         static struct {
3148                 const char *name;
3149                 size_t namelen;
3150                 const char *value;
3151                 const char *value_if_empty;
3152         } vars[] = {
3153 #define FORMAT_VAR(name, value, value_if_empty) \
3154         { name, STRING_SIZE(name), value, value_if_empty }
3155                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3156                 FORMAT_VAR("%(file)",           opt_file,       ""),
3157                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3158                 FORMAT_VAR("%(head)",           ref_head,       ""),
3159                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3160                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3161                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3162         };
3163         int i;
3165         for (i = 0; i < ARRAY_SIZE(vars); i++)
3166                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3167                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3169         report("Unknown replacement: `%s`", name);
3170         return NULL;
3173 static bool
3174 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3176         char buf[SIZEOF_STR];
3177         int argc;
3178         bool noreplace = flags == FORMAT_NONE;
3180         free_argv(dst_argv);
3182         for (argc = 0; src_argv[argc]; argc++) {
3183                 const char *arg = src_argv[argc];
3184                 size_t bufpos = 0;
3186                 while (arg) {
3187                         char *next = strstr(arg, "%(");
3188                         int len = next - arg;
3189                         const char *value;
3191                         if (!next || noreplace) {
3192                                 len = strlen(arg);
3193                                 value = "";
3195                         } else {
3196                                 value = format_arg(next);
3198                                 if (!value) {
3199                                         return FALSE;
3200                                 }
3201                         }
3203                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3204                                 return FALSE;
3206                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3207                 }
3209                 dst_argv[argc] = strdup(buf);
3210                 if (!dst_argv[argc])
3211                         break;
3212         }
3214         dst_argv[argc] = NULL;
3216         return src_argv[argc] == NULL;
3219 static bool
3220 restore_view_position(struct view *view)
3222         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3223                 return FALSE;
3225         /* Changing the view position cancels the restoring. */
3226         /* FIXME: Changing back to the first line is not detected. */
3227         if (view->offset != 0 || view->lineno != 0) {
3228                 view->p_restore = FALSE;
3229                 return FALSE;
3230         }
3232         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3233             view_is_displayed(view))
3234                 werase(view->win);
3236         view->yoffset = view->p_yoffset;
3237         view->p_restore = FALSE;
3239         return TRUE;
3242 static void
3243 end_update(struct view *view, bool force)
3245         if (!view->pipe)
3246                 return;
3247         while (!view->ops->read(view, NULL))
3248                 if (!force)
3249                         return;
3250         if (force)
3251                 io_kill(view->pipe);
3252         io_done(view->pipe);
3253         view->pipe = NULL;
3256 static void
3257 setup_update(struct view *view, const char *vid)
3259         reset_view(view);
3260         string_copy_rev(view->vid, vid);
3261         view->pipe = &view->io;
3262         view->start_time = time(NULL);
3265 static bool
3266 prepare_update(struct view *view, const char *argv[], const char *dir)
3268         if (view->pipe)
3269                 end_update(view, TRUE);
3270         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3273 static bool
3274 prepare_update_file(struct view *view, const char *name)
3276         if (view->pipe)
3277                 end_update(view, TRUE);
3278         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3281 static bool
3282 begin_update(struct view *view, bool refresh)
3284         if (view->pipe)
3285                 end_update(view, TRUE);
3287         if (!refresh) {
3288                 if (view->ops->prepare) {
3289                         if (!view->ops->prepare(view))
3290                                 return FALSE;
3291                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3292                         return FALSE;
3293                 }
3295                 /* Put the current ref_* value to the view title ref
3296                  * member. This is needed by the blob view. Most other
3297                  * views sets it automatically after loading because the
3298                  * first line is a commit line. */
3299                 string_copy_rev(view->ref, view->id);
3300         }
3302         if (!io_start(&view->io))
3303                 return FALSE;
3305         setup_update(view, view->id);
3307         return TRUE;
3310 static bool
3311 update_view(struct view *view)
3313         char out_buffer[BUFSIZ * 2];
3314         char *line;
3315         /* Clear the view and redraw everything since the tree sorting
3316          * might have rearranged things. */
3317         bool redraw = view->lines == 0;
3318         bool can_read = TRUE;
3320         if (!view->pipe)
3321                 return TRUE;
3323         if (!io_can_read(view->pipe)) {
3324                 if (view->lines == 0 && view_is_displayed(view)) {
3325                         time_t secs = time(NULL) - view->start_time;
3327                         if (secs > 1 && secs > view->update_secs) {
3328                                 if (view->update_secs == 0)
3329                                         redraw_view(view);
3330                                 update_view_title(view);
3331                                 view->update_secs = secs;
3332                         }
3333                 }
3334                 return TRUE;
3335         }
3337         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3338                 if (opt_iconv_in != ICONV_NONE) {
3339                         ICONV_CONST char *inbuf = line;
3340                         size_t inlen = strlen(line) + 1;
3342                         char *outbuf = out_buffer;
3343                         size_t outlen = sizeof(out_buffer);
3345                         size_t ret;
3347                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3348                         if (ret != (size_t) -1)
3349                                 line = out_buffer;
3350                 }
3352                 if (!view->ops->read(view, line)) {
3353                         report("Allocation failure");
3354                         end_update(view, TRUE);
3355                         return FALSE;
3356                 }
3357         }
3359         {
3360                 unsigned long lines = view->lines;
3361                 int digits;
3363                 for (digits = 0; lines; digits++)
3364                         lines /= 10;
3366                 /* Keep the displayed view in sync with line number scaling. */
3367                 if (digits != view->digits) {
3368                         view->digits = digits;
3369                         if (opt_line_number || view->type == VIEW_BLAME)
3370                                 redraw = TRUE;
3371                 }
3372         }
3374         if (io_error(view->pipe)) {
3375                 report("Failed to read: %s", io_strerror(view->pipe));
3376                 end_update(view, TRUE);
3378         } else if (io_eof(view->pipe)) {
3379                 report("");
3380                 end_update(view, FALSE);
3381         }
3383         if (restore_view_position(view))
3384                 redraw = TRUE;
3386         if (!view_is_displayed(view))
3387                 return TRUE;
3389         if (redraw)
3390                 redraw_view_from(view, 0);
3391         else
3392                 redraw_view_dirty(view);
3394         /* Update the title _after_ the redraw so that if the redraw picks up a
3395          * commit reference in view->ref it'll be available here. */
3396         update_view_title(view);
3397         return TRUE;
3400 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3402 static struct line *
3403 add_line_data(struct view *view, void *data, enum line_type type)
3405         struct line *line;
3407         if (!realloc_lines(&view->line, view->lines, 1))
3408                 return NULL;
3410         line = &view->line[view->lines++];
3411         memset(line, 0, sizeof(*line));
3412         line->type = type;
3413         line->data = data;
3414         line->dirty = 1;
3416         return line;
3419 static struct line *
3420 add_line_text(struct view *view, const char *text, enum line_type type)
3422         char *data = text ? strdup(text) : NULL;
3424         return data ? add_line_data(view, data, type) : NULL;
3427 static struct line *
3428 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3430         char buf[SIZEOF_STR];
3431         va_list args;
3433         va_start(args, fmt);
3434         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3435                 buf[0] = 0;
3436         va_end(args);
3438         return buf[0] ? add_line_text(view, buf, type) : NULL;
3441 /*
3442  * View opening
3443  */
3445 enum open_flags {
3446         OPEN_DEFAULT = 0,       /* Use default view switching. */
3447         OPEN_SPLIT = 1,         /* Split current view. */
3448         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3449         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3450         OPEN_PREPARED = 32,     /* Open already prepared command. */
3451 };
3453 static void
3454 open_view(struct view *prev, enum request request, enum open_flags flags)
3456         bool split = !!(flags & OPEN_SPLIT);
3457         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3458         bool nomaximize = !!(flags & OPEN_REFRESH);
3459         struct view *view = VIEW(request);
3460         int nviews = displayed_views();
3461         struct view *base_view = display[0];
3463         if (view == prev && nviews == 1 && !reload) {
3464                 report("Already in %s view", view->name);
3465                 return;
3466         }
3468         if (view->git_dir && !opt_git_dir[0]) {
3469                 report("The %s view is disabled in pager view", view->name);
3470                 return;
3471         }
3473         if (split) {
3474                 display[1] = view;
3475                 current_view = 1;
3476                 view->parent = prev;
3477         } else if (!nomaximize) {
3478                 /* Maximize the current view. */
3479                 memset(display, 0, sizeof(display));
3480                 current_view = 0;
3481                 display[current_view] = view;
3482         }
3484         /* No prev signals that this is the first loaded view. */
3485         if (prev && view != prev) {
3486                 view->prev = prev;
3487         }
3489         /* Resize the view when switching between split- and full-screen,
3490          * or when switching between two different full-screen views. */
3491         if (nviews != displayed_views() ||
3492             (nviews == 1 && base_view != display[0]))
3493                 resize_display();
3495         if (view->ops->open) {
3496                 if (view->pipe)
3497                         end_update(view, TRUE);
3498                 if (!view->ops->open(view)) {
3499                         report("Failed to load %s view", view->name);
3500                         return;
3501                 }
3502                 restore_view_position(view);
3504         } else if ((reload || strcmp(view->vid, view->id)) &&
3505                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3506                 report("Failed to load %s view", view->name);
3507                 return;
3508         }
3510         if (split && prev->lineno - prev->offset >= prev->height) {
3511                 /* Take the title line into account. */
3512                 int lines = prev->lineno - prev->offset - prev->height + 1;
3514                 /* Scroll the view that was split if the current line is
3515                  * outside the new limited view. */
3516                 do_scroll_view(prev, lines);
3517         }
3519         if (prev && view != prev && split && view_is_displayed(prev)) {
3520                 /* "Blur" the previous view. */
3521                 update_view_title(prev);
3522         }
3524         if (view->pipe && view->lines == 0) {
3525                 /* Clear the old view and let the incremental updating refill
3526                  * the screen. */
3527                 werase(view->win);
3528                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3529                 report("");
3530         } else if (view_is_displayed(view)) {
3531                 redraw_view(view);
3532                 report("");
3533         }
3536 static void
3537 open_external_viewer(const char *argv[], const char *dir)
3539         def_prog_mode();           /* save current tty modes */
3540         endwin();                  /* restore original tty modes */
3541         io_run_fg(argv, dir);
3542         fprintf(stderr, "Press Enter to continue");
3543         getc(opt_tty);
3544         reset_prog_mode();
3545         redraw_display(TRUE);
3548 static void
3549 open_mergetool(const char *file)
3551         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3553         open_external_viewer(mergetool_argv, opt_cdup);
3556 static void
3557 open_editor(const char *file)
3559         const char *editor_argv[] = { "vi", file, NULL };
3560         const char *editor;
3562         editor = getenv("GIT_EDITOR");
3563         if (!editor && *opt_editor)
3564                 editor = opt_editor;
3565         if (!editor)
3566                 editor = getenv("VISUAL");
3567         if (!editor)
3568                 editor = getenv("EDITOR");
3569         if (!editor)
3570                 editor = "vi";
3572         editor_argv[0] = editor;
3573         open_external_viewer(editor_argv, opt_cdup);
3576 static void
3577 open_run_request(enum request request)
3579         struct run_request *req = get_run_request(request);
3580         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3582         if (!req) {
3583                 report("Unknown run request");
3584                 return;
3585         }
3587         if (format_argv(argv, req->argv, FORMAT_ALL))
3588                 open_external_viewer(argv, NULL);
3589         free_argv(argv);
3592 /*
3593  * User request switch noodle
3594  */
3596 static int
3597 view_driver(struct view *view, enum request request)
3599         int i;
3601         if (request == REQ_NONE)
3602                 return TRUE;
3604         if (request > REQ_NONE) {
3605                 open_run_request(request);
3606                 /* FIXME: When all views can refresh always do this. */
3607                 if (view->refresh)
3608                         request = REQ_REFRESH;
3609                 else
3610                         return TRUE;
3611         }
3613         if (view && view->lines) {
3614                 request = view->ops->request(view, request, &view->line[view->lineno]);
3615                 if (request == REQ_NONE)
3616                         return TRUE;
3617         }
3619         switch (request) {
3620         case REQ_MOVE_UP:
3621         case REQ_MOVE_DOWN:
3622         case REQ_MOVE_PAGE_UP:
3623         case REQ_MOVE_PAGE_DOWN:
3624         case REQ_MOVE_FIRST_LINE:
3625         case REQ_MOVE_LAST_LINE:
3626                 move_view(view, request);
3627                 break;
3629         case REQ_SCROLL_LEFT:
3630         case REQ_SCROLL_RIGHT:
3631         case REQ_SCROLL_LINE_DOWN:
3632         case REQ_SCROLL_LINE_UP:
3633         case REQ_SCROLL_PAGE_DOWN:
3634         case REQ_SCROLL_PAGE_UP:
3635                 scroll_view(view, request);
3636                 break;
3638         case REQ_VIEW_BLAME:
3639                 if (!opt_file[0]) {
3640                         report("No file chosen, press %s to open tree view",
3641                                get_key(view->keymap, REQ_VIEW_TREE));
3642                         break;
3643                 }
3644                 open_view(view, request, OPEN_DEFAULT);
3645                 break;
3647         case REQ_VIEW_BLOB:
3648                 if (!ref_blob[0]) {
3649                         report("No file chosen, press %s to open tree view",
3650                                get_key(view->keymap, REQ_VIEW_TREE));
3651                         break;
3652                 }
3653                 open_view(view, request, OPEN_DEFAULT);
3654                 break;
3656         case REQ_VIEW_PAGER:
3657                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3658                         report("No pager content, press %s to run command from prompt",
3659                                get_key(view->keymap, REQ_PROMPT));
3660                         break;
3661                 }
3662                 open_view(view, request, OPEN_DEFAULT);
3663                 break;
3665         case REQ_VIEW_STAGE:
3666                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3667                         report("No stage content, press %s to open the status view and choose file",
3668                                get_key(view->keymap, REQ_VIEW_STATUS));
3669                         break;
3670                 }
3671                 open_view(view, request, OPEN_DEFAULT);
3672                 break;
3674         case REQ_VIEW_STATUS:
3675                 if (opt_is_inside_work_tree == FALSE) {
3676                         report("The status view requires a working tree");
3677                         break;
3678                 }
3679                 open_view(view, request, OPEN_DEFAULT);
3680                 break;
3682         case REQ_VIEW_MAIN:
3683         case REQ_VIEW_DIFF:
3684         case REQ_VIEW_LOG:
3685         case REQ_VIEW_TREE:
3686         case REQ_VIEW_HELP:
3687         case REQ_VIEW_BRANCH:
3688                 open_view(view, request, OPEN_DEFAULT);
3689                 break;
3691         case REQ_NEXT:
3692         case REQ_PREVIOUS:
3693                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3695                 if (view->parent) {
3696                         int line;
3698                         view = view->parent;
3699                         line = view->lineno;
3700                         move_view(view, request);
3701                         if (view_is_displayed(view))
3702                                 update_view_title(view);
3703                         if (line != view->lineno)
3704                                 view->ops->request(view, REQ_ENTER,
3705                                                    &view->line[view->lineno]);
3707                 } else {
3708                         move_view(view, request);
3709                 }
3710                 break;
3712         case REQ_VIEW_NEXT:
3713         {
3714                 int nviews = displayed_views();
3715                 int next_view = (current_view + 1) % nviews;
3717                 if (next_view == current_view) {
3718                         report("Only one view is displayed");
3719                         break;
3720                 }
3722                 current_view = next_view;
3723                 /* Blur out the title of the previous view. */
3724                 update_view_title(view);
3725                 report("");
3726                 break;
3727         }
3728         case REQ_REFRESH:
3729                 report("Refreshing is not yet supported for the %s view", view->name);
3730                 break;
3732         case REQ_MAXIMIZE:
3733                 if (displayed_views() == 2)
3734                         maximize_view(view);
3735                 break;
3737         case REQ_OPTIONS:
3738                 open_option_menu();
3739                 break;
3741         case REQ_TOGGLE_LINENO:
3742                 toggle_view_option(&opt_line_number, "line numbers");
3743                 break;
3745         case REQ_TOGGLE_DATE:
3746                 toggle_date();
3747                 break;
3749         case REQ_TOGGLE_AUTHOR:
3750                 toggle_author();
3751                 break;
3753         case REQ_TOGGLE_REV_GRAPH:
3754                 toggle_view_option(&opt_rev_graph, "revision graph display");
3755                 break;
3757         case REQ_TOGGLE_REFS:
3758                 toggle_view_option(&opt_show_refs, "reference display");
3759                 break;
3761         case REQ_TOGGLE_SORT_FIELD:
3762         case REQ_TOGGLE_SORT_ORDER:
3763                 report("Sorting is not yet supported for the %s view", view->name);
3764                 break;
3766         case REQ_SEARCH:
3767         case REQ_SEARCH_BACK:
3768                 search_view(view, request);
3769                 break;
3771         case REQ_FIND_NEXT:
3772         case REQ_FIND_PREV:
3773                 find_next(view, request);
3774                 break;
3776         case REQ_STOP_LOADING:
3777                 foreach_view(view, i) {
3778                         if (view->pipe)
3779                                 report("Stopped loading the %s view", view->name),
3780                         end_update(view, TRUE);
3781                 }
3782                 break;
3784         case REQ_SHOW_VERSION:
3785                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3786                 return TRUE;
3788         case REQ_SCREEN_REDRAW:
3789                 redraw_display(TRUE);
3790                 break;
3792         case REQ_EDIT:
3793                 report("Nothing to edit");
3794                 break;
3796         case REQ_ENTER:
3797                 report("Nothing to enter");
3798                 break;
3800         case REQ_VIEW_CLOSE:
3801                 /* XXX: Mark closed views by letting view->prev point to the
3802                  * view itself. Parents to closed view should never be
3803                  * followed. */
3804                 if (view->prev && view->prev != view) {
3805                         maximize_view(view->prev);
3806                         view->prev = view;
3807                         break;
3808                 }
3809                 /* Fall-through */
3810         case REQ_QUIT:
3811                 return FALSE;
3813         default:
3814                 report("Unknown key, press %s for help",
3815                        get_key(view->keymap, REQ_VIEW_HELP));
3816                 return TRUE;
3817         }
3819         return TRUE;
3823 /*
3824  * View backend utilities
3825  */
3827 enum sort_field {
3828         ORDERBY_NAME,
3829         ORDERBY_DATE,
3830         ORDERBY_AUTHOR,
3831 };
3833 struct sort_state {
3834         const enum sort_field *fields;
3835         size_t size, current;
3836         bool reverse;
3837 };
3839 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3840 #define get_sort_field(state) ((state).fields[(state).current])
3841 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3843 static void
3844 sort_view(struct view *view, enum request request, struct sort_state *state,
3845           int (*compare)(const void *, const void *))
3847         switch (request) {
3848         case REQ_TOGGLE_SORT_FIELD:
3849                 state->current = (state->current + 1) % state->size;
3850                 break;
3852         case REQ_TOGGLE_SORT_ORDER:
3853                 state->reverse = !state->reverse;
3854                 break;
3855         default:
3856                 die("Not a sort request");
3857         }
3859         qsort(view->line, view->lines, sizeof(*view->line), compare);
3860         redraw_view(view);
3863 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3865 /* Small author cache to reduce memory consumption. It uses binary
3866  * search to lookup or find place to position new entries. No entries
3867  * are ever freed. */
3868 static const char *
3869 get_author(const char *name)
3871         static const char **authors;
3872         static size_t authors_size;
3873         int from = 0, to = authors_size - 1;
3875         while (from <= to) {
3876                 size_t pos = (to + from) / 2;
3877                 int cmp = strcmp(name, authors[pos]);
3879                 if (!cmp)
3880                         return authors[pos];
3882                 if (cmp < 0)
3883                         to = pos - 1;
3884                 else
3885                         from = pos + 1;
3886         }
3888         if (!realloc_authors(&authors, authors_size, 1))
3889                 return NULL;
3890         name = strdup(name);
3891         if (!name)
3892                 return NULL;
3894         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3895         authors[from] = name;
3896         authors_size++;
3898         return name;
3901 static void
3902 parse_timesec(struct time *time, const char *sec)
3904         time->sec = (time_t) atol(sec);
3907 static void
3908 parse_timezone(struct time *time, const char *zone)
3910         long tz;
3912         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3913         tz += ('0' - zone[2]) * 60 * 60;
3914         tz += ('0' - zone[3]) * 60 * 10;
3915         tz += ('0' - zone[4]) * 60;
3917         if (zone[0] == '-')
3918                 tz = -tz;
3920         time->tz = tz;
3921         time->sec -= tz;
3924 /* Parse author lines where the name may be empty:
3925  *      author  <email@address.tld> 1138474660 +0100
3926  */
3927 static void
3928 parse_author_line(char *ident, const char **author, struct time *time)
3930         char *nameend = strchr(ident, '<');
3931         char *emailend = strchr(ident, '>');
3933         if (nameend && emailend)
3934                 *nameend = *emailend = 0;
3935         ident = chomp_string(ident);
3936         if (!*ident) {
3937                 if (nameend)
3938                         ident = chomp_string(nameend + 1);
3939                 if (!*ident)
3940                         ident = "Unknown";
3941         }
3943         *author = get_author(ident);
3945         /* Parse epoch and timezone */
3946         if (emailend && emailend[1] == ' ') {
3947                 char *secs = emailend + 2;
3948                 char *zone = strchr(secs, ' ');
3950                 parse_timesec(time, secs);
3952                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3953                         parse_timezone(time, zone + 1);
3954         }
3957 static bool
3958 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3960         char rev[SIZEOF_REV];
3961         const char *revlist_argv[] = {
3962                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3963         };
3964         struct menu_item *items;
3965         char text[SIZEOF_STR];
3966         bool ok = TRUE;
3967         int i;
3969         items = calloc(*parents + 1, sizeof(*items));
3970         if (!items)
3971                 return FALSE;
3973         for (i = 0; i < *parents; i++) {
3974                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3975                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3976                     !(items[i].text = strdup(text))) {
3977                         ok = FALSE;
3978                         break;
3979                 }
3980         }
3982         if (ok) {
3983                 *parents = 0;
3984                 ok = prompt_menu("Select parent", items, parents);
3985         }
3986         for (i = 0; items[i].text; i++)
3987                 free((char *) items[i].text);
3988         free(items);
3989         return ok;
3992 static bool
3993 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3995         char buf[SIZEOF_STR * 4];
3996         const char *revlist_argv[] = {
3997                 "git", "log", "--no-color", "-1",
3998                         "--pretty=format:%P", id, "--", path, NULL
3999         };
4000         int parents;
4002         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4003             (parents = strlen(buf) / 40) < 0) {
4004                 report("Failed to get parent information");
4005                 return FALSE;
4007         } else if (parents == 0) {
4008                 if (path)
4009                         report("Path '%s' does not exist in the parent", path);
4010                 else
4011                         report("The selected commit has no parents");
4012                 return FALSE;
4013         }
4015         if (parents == 1)
4016                 parents = 0;
4017         else if (!open_commit_parent_menu(buf, &parents))
4018                 return FALSE;
4020         string_copy_rev(rev, &buf[41 * parents]);
4021         return TRUE;
4024 /*
4025  * Pager backend
4026  */
4028 static bool
4029 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4031         char text[SIZEOF_STR];
4033         if (opt_line_number && draw_lineno(view, lineno))
4034                 return TRUE;
4036         string_expand(text, sizeof(text), line->data, opt_tab_size);
4037         draw_text(view, line->type, text, TRUE);
4038         return TRUE;
4041 static bool
4042 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4044         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4045         char ref[SIZEOF_STR];
4047         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4048                 return TRUE;
4050         /* This is the only fatal call, since it can "corrupt" the buffer. */
4051         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4052                 return FALSE;
4054         return TRUE;
4057 static void
4058 add_pager_refs(struct view *view, struct line *line)
4060         char buf[SIZEOF_STR];
4061         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4062         struct ref_list *list;
4063         size_t bufpos = 0, i;
4064         const char *sep = "Refs: ";
4065         bool is_tag = FALSE;
4067         assert(line->type == LINE_COMMIT);
4069         list = get_ref_list(commit_id);
4070         if (!list) {
4071                 if (view->type == VIEW_DIFF)
4072                         goto try_add_describe_ref;
4073                 return;
4074         }
4076         for (i = 0; i < list->size; i++) {
4077                 struct ref *ref = list->refs[i];
4078                 const char *fmt = ref->tag    ? "%s[%s]" :
4079                                   ref->remote ? "%s<%s>" : "%s%s";
4081                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4082                         return;
4083                 sep = ", ";
4084                 if (ref->tag)
4085                         is_tag = TRUE;
4086         }
4088         if (!is_tag && view->type == VIEW_DIFF) {
4089 try_add_describe_ref:
4090                 /* Add <tag>-g<commit_id> "fake" reference. */
4091                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4092                         return;
4093         }
4095         if (bufpos == 0)
4096                 return;
4098         add_line_text(view, buf, LINE_PP_REFS);
4101 static bool
4102 pager_read(struct view *view, char *data)
4104         struct line *line;
4106         if (!data)
4107                 return TRUE;
4109         line = add_line_text(view, data, get_line_type(data));
4110         if (!line)
4111                 return FALSE;
4113         if (line->type == LINE_COMMIT &&
4114             (view->type == VIEW_DIFF ||
4115              view->type == VIEW_LOG))
4116                 add_pager_refs(view, line);
4118         return TRUE;
4121 static enum request
4122 pager_request(struct view *view, enum request request, struct line *line)
4124         int split = 0;
4126         if (request != REQ_ENTER)
4127                 return request;
4129         if (line->type == LINE_COMMIT &&
4130            (view->type == VIEW_LOG ||
4131             view->type == VIEW_PAGER)) {
4132                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4133                 split = 1;
4134         }
4136         /* Always scroll the view even if it was split. That way
4137          * you can use Enter to scroll through the log view and
4138          * split open each commit diff. */
4139         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4141         /* FIXME: A minor workaround. Scrolling the view will call report("")
4142          * but if we are scrolling a non-current view this won't properly
4143          * update the view title. */
4144         if (split)
4145                 update_view_title(view);
4147         return REQ_NONE;
4150 static bool
4151 pager_grep(struct view *view, struct line *line)
4153         const char *text[] = { line->data, NULL };
4155         return grep_text(view, text);
4158 static void
4159 pager_select(struct view *view, struct line *line)
4161         if (line->type == LINE_COMMIT) {
4162                 char *text = (char *)line->data + STRING_SIZE("commit ");
4164                 if (view->type != VIEW_PAGER)
4165                         string_copy_rev(view->ref, text);
4166                 string_copy_rev(ref_commit, text);
4167         }
4170 static struct view_ops pager_ops = {
4171         "line",
4172         NULL,
4173         NULL,
4174         pager_read,
4175         pager_draw,
4176         pager_request,
4177         pager_grep,
4178         pager_select,
4179 };
4181 static const char *log_argv[SIZEOF_ARG] = {
4182         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4183 };
4185 static enum request
4186 log_request(struct view *view, enum request request, struct line *line)
4188         switch (request) {
4189         case REQ_REFRESH:
4190                 load_refs();
4191                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4192                 return REQ_NONE;
4193         default:
4194                 return pager_request(view, request, line);
4195         }
4198 static struct view_ops log_ops = {
4199         "line",
4200         log_argv,
4201         NULL,
4202         pager_read,
4203         pager_draw,
4204         log_request,
4205         pager_grep,
4206         pager_select,
4207 };
4209 static const char *diff_argv[SIZEOF_ARG] = {
4210         "git", "show", "--pretty=fuller", "--no-color", "--root",
4211                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4212 };
4214 static struct view_ops diff_ops = {
4215         "line",
4216         diff_argv,
4217         NULL,
4218         pager_read,
4219         pager_draw,
4220         pager_request,
4221         pager_grep,
4222         pager_select,
4223 };
4225 /*
4226  * Help backend
4227  */
4229 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4231 static bool
4232 help_open_keymap_title(struct view *view, enum keymap keymap)
4234         struct line *line;
4236         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4237                                help_keymap_hidden[keymap] ? '+' : '-',
4238                                enum_name(keymap_table[keymap]));
4239         if (line)
4240                 line->other = keymap;
4242         return help_keymap_hidden[keymap];
4245 static void
4246 help_open_keymap(struct view *view, enum keymap keymap)
4248         const char *group = NULL;
4249         char buf[SIZEOF_STR];
4250         size_t bufpos;
4251         bool add_title = TRUE;
4252         int i;
4254         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4255                 const char *key = NULL;
4257                 if (req_info[i].request == REQ_NONE)
4258                         continue;
4260                 if (!req_info[i].request) {
4261                         group = req_info[i].help;
4262                         continue;
4263                 }
4265                 key = get_keys(keymap, req_info[i].request, TRUE);
4266                 if (!key || !*key)
4267                         continue;
4269                 if (add_title && help_open_keymap_title(view, keymap))
4270                         return;
4271                 add_title = FALSE;
4273                 if (group) {
4274                         add_line_text(view, group, LINE_HELP_GROUP);
4275                         group = NULL;
4276                 }
4278                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4279                                 enum_name(req_info[i]), req_info[i].help);
4280         }
4282         group = "External commands:";
4284         for (i = 0; i < run_requests; i++) {
4285                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4286                 const char *key;
4287                 int argc;
4289                 if (!req || req->keymap != keymap)
4290                         continue;
4292                 key = get_key_name(req->key);
4293                 if (!*key)
4294                         key = "(no key defined)";
4296                 if (add_title && help_open_keymap_title(view, keymap))
4297                         return;
4298                 if (group) {
4299                         add_line_text(view, group, LINE_HELP_GROUP);
4300                         group = NULL;
4301                 }
4303                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4304                         if (!string_format_from(buf, &bufpos, "%s%s",
4305                                                 argc ? " " : "", req->argv[argc]))
4306                                 return;
4308                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4309         }
4312 static bool
4313 help_open(struct view *view)
4315         enum keymap keymap;
4317         reset_view(view);
4318         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4319         add_line_text(view, "", LINE_DEFAULT);
4321         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4322                 help_open_keymap(view, keymap);
4324         return TRUE;
4327 static enum request
4328 help_request(struct view *view, enum request request, struct line *line)
4330         switch (request) {
4331         case REQ_ENTER:
4332                 if (line->type == LINE_HELP_KEYMAP) {
4333                         help_keymap_hidden[line->other] =
4334                                 !help_keymap_hidden[line->other];
4335                         view->p_restore = TRUE;
4336                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4337                 }
4339                 return REQ_NONE;
4340         default:
4341                 return pager_request(view, request, line);
4342         }
4345 static struct view_ops help_ops = {
4346         "line",
4347         NULL,
4348         help_open,
4349         NULL,
4350         pager_draw,
4351         help_request,
4352         pager_grep,
4353         pager_select,
4354 };
4357 /*
4358  * Tree backend
4359  */
4361 struct tree_stack_entry {
4362         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4363         unsigned long lineno;           /* Line number to restore */
4364         char *name;                     /* Position of name in opt_path */
4365 };
4367 /* The top of the path stack. */
4368 static struct tree_stack_entry *tree_stack = NULL;
4369 unsigned long tree_lineno = 0;
4371 static void
4372 pop_tree_stack_entry(void)
4374         struct tree_stack_entry *entry = tree_stack;
4376         tree_lineno = entry->lineno;
4377         entry->name[0] = 0;
4378         tree_stack = entry->prev;
4379         free(entry);
4382 static void
4383 push_tree_stack_entry(const char *name, unsigned long lineno)
4385         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4386         size_t pathlen = strlen(opt_path);
4388         if (!entry)
4389                 return;
4391         entry->prev = tree_stack;
4392         entry->name = opt_path + pathlen;
4393         tree_stack = entry;
4395         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4396                 pop_tree_stack_entry();
4397                 return;
4398         }
4400         /* Move the current line to the first tree entry. */
4401         tree_lineno = 1;
4402         entry->lineno = lineno;
4405 /* Parse output from git-ls-tree(1):
4406  *
4407  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4408  */
4410 #define SIZEOF_TREE_ATTR \
4411         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4413 #define SIZEOF_TREE_MODE \
4414         STRING_SIZE("100644 ")
4416 #define TREE_ID_OFFSET \
4417         STRING_SIZE("100644 blob ")
4419 struct tree_entry {
4420         char id[SIZEOF_REV];
4421         mode_t mode;
4422         struct time time;               /* Date from the author ident. */
4423         const char *author;             /* Author of the commit. */
4424         char name[1];
4425 };
4427 static const char *
4428 tree_path(const struct line *line)
4430         return ((struct tree_entry *) line->data)->name;
4433 static int
4434 tree_compare_entry(const struct line *line1, const struct line *line2)
4436         if (line1->type != line2->type)
4437                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4438         return strcmp(tree_path(line1), tree_path(line2));
4441 static const enum sort_field tree_sort_fields[] = {
4442         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4443 };
4444 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4446 static int
4447 tree_compare(const void *l1, const void *l2)
4449         const struct line *line1 = (const struct line *) l1;
4450         const struct line *line2 = (const struct line *) l2;
4451         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4452         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4454         if (line1->type == LINE_TREE_HEAD)
4455                 return -1;
4456         if (line2->type == LINE_TREE_HEAD)
4457                 return 1;
4459         switch (get_sort_field(tree_sort_state)) {
4460         case ORDERBY_DATE:
4461                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4463         case ORDERBY_AUTHOR:
4464                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4466         case ORDERBY_NAME:
4467         default:
4468                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4469         }
4473 static struct line *
4474 tree_entry(struct view *view, enum line_type type, const char *path,
4475            const char *mode, const char *id)
4477         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4478         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4480         if (!entry || !line) {
4481                 free(entry);
4482                 return NULL;
4483         }
4485         strncpy(entry->name, path, strlen(path));
4486         if (mode)
4487                 entry->mode = strtoul(mode, NULL, 8);
4488         if (id)
4489                 string_copy_rev(entry->id, id);
4491         return line;
4494 static bool
4495 tree_read_date(struct view *view, char *text, bool *read_date)
4497         static const char *author_name;
4498         static struct time author_time;
4500         if (!text && *read_date) {
4501                 *read_date = FALSE;
4502                 return TRUE;
4504         } else if (!text) {
4505                 char *path = *opt_path ? opt_path : ".";
4506                 /* Find next entry to process */
4507                 const char *log_file[] = {
4508                         "git", "log", "--no-color", "--pretty=raw",
4509                                 "--cc", "--raw", view->id, "--", path, NULL
4510                 };
4511                 struct io io = {};
4513                 if (!view->lines) {
4514                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4515                         report("Tree is empty");
4516                         return TRUE;
4517                 }
4519                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4520                         report("Failed to load tree data");
4521                         return TRUE;
4522                 }
4524                 io_done(view->pipe);
4525                 view->io = io;
4526                 *read_date = TRUE;
4527                 return FALSE;
4529         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4530                 parse_author_line(text + STRING_SIZE("author "),
4531                                   &author_name, &author_time);
4533         } else if (*text == ':') {
4534                 char *pos;
4535                 size_t annotated = 1;
4536                 size_t i;
4538                 pos = strchr(text, '\t');
4539                 if (!pos)
4540                         return TRUE;
4541                 text = pos + 1;
4542                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4543                         text += strlen(opt_path);
4544                 pos = strchr(text, '/');
4545                 if (pos)
4546                         *pos = 0;
4548                 for (i = 1; i < view->lines; i++) {
4549                         struct line *line = &view->line[i];
4550                         struct tree_entry *entry = line->data;
4552                         annotated += !!entry->author;
4553                         if (entry->author || strcmp(entry->name, text))
4554                                 continue;
4556                         entry->author = author_name;
4557                         entry->time = author_time;
4558                         line->dirty = 1;
4559                         break;
4560                 }
4562                 if (annotated == view->lines)
4563                         io_kill(view->pipe);
4564         }
4565         return TRUE;
4568 static bool
4569 tree_read(struct view *view, char *text)
4571         static bool read_date = FALSE;
4572         struct tree_entry *data;
4573         struct line *entry, *line;
4574         enum line_type type;
4575         size_t textlen = text ? strlen(text) : 0;
4576         char *path = text + SIZEOF_TREE_ATTR;
4578         if (read_date || !text)
4579                 return tree_read_date(view, text, &read_date);
4581         if (textlen <= SIZEOF_TREE_ATTR)
4582                 return FALSE;
4583         if (view->lines == 0 &&
4584             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4585                 return FALSE;
4587         /* Strip the path part ... */
4588         if (*opt_path) {
4589                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4590                 size_t striplen = strlen(opt_path);
4592                 if (pathlen > striplen)
4593                         memmove(path, path + striplen,
4594                                 pathlen - striplen + 1);
4596                 /* Insert "link" to parent directory. */
4597                 if (view->lines == 1 &&
4598                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4599                         return FALSE;
4600         }
4602         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4603         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4604         if (!entry)
4605                 return FALSE;
4606         data = entry->data;
4608         /* Skip "Directory ..." and ".." line. */
4609         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4610                 if (tree_compare_entry(line, entry) <= 0)
4611                         continue;
4613                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4615                 line->data = data;
4616                 line->type = type;
4617                 for (; line <= entry; line++)
4618                         line->dirty = line->cleareol = 1;
4619                 return TRUE;
4620         }
4622         if (tree_lineno > view->lineno) {
4623                 view->lineno = tree_lineno;
4624                 tree_lineno = 0;
4625         }
4627         return TRUE;
4630 static bool
4631 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4633         struct tree_entry *entry = line->data;
4635         if (line->type == LINE_TREE_HEAD) {
4636                 if (draw_text(view, line->type, "Directory path /", TRUE))
4637                         return TRUE;
4638         } else {
4639                 if (draw_mode(view, entry->mode))
4640                         return TRUE;
4642                 if (opt_author && draw_author(view, entry->author))
4643                         return TRUE;
4645                 if (opt_date && draw_date(view, &entry->time))
4646                         return TRUE;
4647         }
4648         if (draw_text(view, line->type, entry->name, TRUE))
4649                 return TRUE;
4650         return TRUE;
4653 static void
4654 open_blob_editor()
4656         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4657         int fd = mkstemp(file);
4659         if (fd == -1)
4660                 report("Failed to create temporary file");
4661         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4662                 report("Failed to save blob data to file");
4663         else
4664                 open_editor(file);
4665         if (fd != -1)
4666                 unlink(file);
4669 static enum request
4670 tree_request(struct view *view, enum request request, struct line *line)
4672         enum open_flags flags;
4674         switch (request) {
4675         case REQ_VIEW_BLAME:
4676                 if (line->type != LINE_TREE_FILE) {
4677                         report("Blame only supported for files");
4678                         return REQ_NONE;
4679                 }
4681                 string_copy(opt_ref, view->vid);
4682                 return request;
4684         case REQ_EDIT:
4685                 if (line->type != LINE_TREE_FILE) {
4686                         report("Edit only supported for files");
4687                 } else if (!is_head_commit(view->vid)) {
4688                         open_blob_editor();
4689                 } else {
4690                         open_editor(opt_file);
4691                 }
4692                 return REQ_NONE;
4694         case REQ_TOGGLE_SORT_FIELD:
4695         case REQ_TOGGLE_SORT_ORDER:
4696                 sort_view(view, request, &tree_sort_state, tree_compare);
4697                 return REQ_NONE;
4699         case REQ_PARENT:
4700                 if (!*opt_path) {
4701                         /* quit view if at top of tree */
4702                         return REQ_VIEW_CLOSE;
4703                 }
4704                 /* fake 'cd  ..' */
4705                 line = &view->line[1];
4706                 break;
4708         case REQ_ENTER:
4709                 break;
4711         default:
4712                 return request;
4713         }
4715         /* Cleanup the stack if the tree view is at a different tree. */
4716         while (!*opt_path && tree_stack)
4717                 pop_tree_stack_entry();
4719         switch (line->type) {
4720         case LINE_TREE_DIR:
4721                 /* Depending on whether it is a subdirectory or parent link
4722                  * mangle the path buffer. */
4723                 if (line == &view->line[1] && *opt_path) {
4724                         pop_tree_stack_entry();
4726                 } else {
4727                         const char *basename = tree_path(line);
4729                         push_tree_stack_entry(basename, view->lineno);
4730                 }
4732                 /* Trees and subtrees share the same ID, so they are not not
4733                  * unique like blobs. */
4734                 flags = OPEN_RELOAD;
4735                 request = REQ_VIEW_TREE;
4736                 break;
4738         case LINE_TREE_FILE:
4739                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4740                 request = REQ_VIEW_BLOB;
4741                 break;
4743         default:
4744                 return REQ_NONE;
4745         }
4747         open_view(view, request, flags);
4748         if (request == REQ_VIEW_TREE)
4749                 view->lineno = tree_lineno;
4751         return REQ_NONE;
4754 static bool
4755 tree_grep(struct view *view, struct line *line)
4757         struct tree_entry *entry = line->data;
4758         const char *text[] = {
4759                 entry->name,
4760                 opt_author ? entry->author : "",
4761                 mkdate(&entry->time, opt_date),
4762                 NULL
4763         };
4765         return grep_text(view, text);
4768 static void
4769 tree_select(struct view *view, struct line *line)
4771         struct tree_entry *entry = line->data;
4773         if (line->type == LINE_TREE_FILE) {
4774                 string_copy_rev(ref_blob, entry->id);
4775                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4777         } else if (line->type != LINE_TREE_DIR) {
4778                 return;
4779         }
4781         string_copy_rev(view->ref, entry->id);
4784 static bool
4785 tree_prepare(struct view *view)
4787         if (view->lines == 0 && opt_prefix[0]) {
4788                 char *pos = opt_prefix;
4790                 while (pos && *pos) {
4791                         char *end = strchr(pos, '/');
4793                         if (end)
4794                                 *end = 0;
4795                         push_tree_stack_entry(pos, 0);
4796                         pos = end;
4797                         if (end) {
4798                                 *end = '/';
4799                                 pos++;
4800                         }
4801                 }
4803         } else if (strcmp(view->vid, view->id)) {
4804                 opt_path[0] = 0;
4805         }
4807         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4810 static const char *tree_argv[SIZEOF_ARG] = {
4811         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4812 };
4814 static struct view_ops tree_ops = {
4815         "file",
4816         tree_argv,
4817         NULL,
4818         tree_read,
4819         tree_draw,
4820         tree_request,
4821         tree_grep,
4822         tree_select,
4823         tree_prepare,
4824 };
4826 static bool
4827 blob_read(struct view *view, char *line)
4829         if (!line)
4830                 return TRUE;
4831         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4834 static enum request
4835 blob_request(struct view *view, enum request request, struct line *line)
4837         switch (request) {
4838         case REQ_EDIT:
4839                 open_blob_editor();
4840                 return REQ_NONE;
4841         default:
4842                 return pager_request(view, request, line);
4843         }
4846 static const char *blob_argv[SIZEOF_ARG] = {
4847         "git", "cat-file", "blob", "%(blob)", NULL
4848 };
4850 static struct view_ops blob_ops = {
4851         "line",
4852         blob_argv,
4853         NULL,
4854         blob_read,
4855         pager_draw,
4856         blob_request,
4857         pager_grep,
4858         pager_select,
4859 };
4861 /*
4862  * Blame backend
4863  *
4864  * Loading the blame view is a two phase job:
4865  *
4866  *  1. File content is read either using opt_file from the
4867  *     filesystem or using git-cat-file.
4868  *  2. Then blame information is incrementally added by
4869  *     reading output from git-blame.
4870  */
4872 static const char *blame_head_argv[] = {
4873         "git", "blame", "--incremental", "--", "%(file)", NULL
4874 };
4876 static const char *blame_ref_argv[] = {
4877         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4878 };
4880 static const char *blame_cat_file_argv[] = {
4881         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4882 };
4884 struct blame_commit {
4885         char id[SIZEOF_REV];            /* SHA1 ID. */
4886         char title[128];                /* First line of the commit message. */
4887         const char *author;             /* Author of the commit. */
4888         struct time time;               /* Date from the author ident. */
4889         char filename[128];             /* Name of file. */
4890         bool has_previous;              /* Was a "previous" line detected. */
4891 };
4893 struct blame {
4894         struct blame_commit *commit;
4895         unsigned long lineno;
4896         char text[1];
4897 };
4899 static bool
4900 blame_open(struct view *view)
4902         char path[SIZEOF_STR];
4904         if (!view->prev && *opt_prefix) {
4905                 string_copy(path, opt_file);
4906                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4907                         return FALSE;
4908         }
4910         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4911                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4912                         return FALSE;
4913         }
4915         setup_update(view, opt_file);
4916         string_format(view->ref, "%s ...", opt_file);
4918         return TRUE;
4921 static struct blame_commit *
4922 get_blame_commit(struct view *view, const char *id)
4924         size_t i;
4926         for (i = 0; i < view->lines; i++) {
4927                 struct blame *blame = view->line[i].data;
4929                 if (!blame->commit)
4930                         continue;
4932                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4933                         return blame->commit;
4934         }
4936         {
4937                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4939                 if (commit)
4940                         string_ncopy(commit->id, id, SIZEOF_REV);
4941                 return commit;
4942         }
4945 static bool
4946 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4948         const char *pos = *posref;
4950         *posref = NULL;
4951         pos = strchr(pos + 1, ' ');
4952         if (!pos || !isdigit(pos[1]))
4953                 return FALSE;
4954         *number = atoi(pos + 1);
4955         if (*number < min || *number > max)
4956                 return FALSE;
4958         *posref = pos;
4959         return TRUE;
4962 static struct blame_commit *
4963 parse_blame_commit(struct view *view, const char *text, int *blamed)
4965         struct blame_commit *commit;
4966         struct blame *blame;
4967         const char *pos = text + SIZEOF_REV - 2;
4968         size_t orig_lineno = 0;
4969         size_t lineno;
4970         size_t group;
4972         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4973                 return NULL;
4975         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4976             !parse_number(&pos, &lineno, 1, view->lines) ||
4977             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4978                 return NULL;
4980         commit = get_blame_commit(view, text);
4981         if (!commit)
4982                 return NULL;
4984         *blamed += group;
4985         while (group--) {
4986                 struct line *line = &view->line[lineno + group - 1];
4988                 blame = line->data;
4989                 blame->commit = commit;
4990                 blame->lineno = orig_lineno + group - 1;
4991                 line->dirty = 1;
4992         }
4994         return commit;
4997 static bool
4998 blame_read_file(struct view *view, const char *line, bool *read_file)
5000         if (!line) {
5001                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
5002                 struct io io = {};
5004                 if (view->lines == 0 && !view->prev)
5005                         die("No blame exist for %s", view->vid);
5007                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
5008                         report("Failed to load blame data");
5009                         return TRUE;
5010                 }
5012                 io_done(view->pipe);
5013                 view->io = io;
5014                 *read_file = FALSE;
5015                 return FALSE;
5017         } else {
5018                 size_t linelen = strlen(line);
5019                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5021                 if (!blame)
5022                         return FALSE;
5024                 blame->commit = NULL;
5025                 strncpy(blame->text, line, linelen);
5026                 blame->text[linelen] = 0;
5027                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5028         }
5031 static bool
5032 match_blame_header(const char *name, char **line)
5034         size_t namelen = strlen(name);
5035         bool matched = !strncmp(name, *line, namelen);
5037         if (matched)
5038                 *line += namelen;
5040         return matched;
5043 static bool
5044 blame_read(struct view *view, char *line)
5046         static struct blame_commit *commit = NULL;
5047         static int blamed = 0;
5048         static bool read_file = TRUE;
5050         if (read_file)
5051                 return blame_read_file(view, line, &read_file);
5053         if (!line) {
5054                 /* Reset all! */
5055                 commit = NULL;
5056                 blamed = 0;
5057                 read_file = TRUE;
5058                 string_format(view->ref, "%s", view->vid);
5059                 if (view_is_displayed(view)) {
5060                         update_view_title(view);
5061                         redraw_view_from(view, 0);
5062                 }
5063                 return TRUE;
5064         }
5066         if (!commit) {
5067                 commit = parse_blame_commit(view, line, &blamed);
5068                 string_format(view->ref, "%s %2d%%", view->vid,
5069                               view->lines ? blamed * 100 / view->lines : 0);
5071         } else if (match_blame_header("author ", &line)) {
5072                 commit->author = get_author(line);
5074         } else if (match_blame_header("author-time ", &line)) {
5075                 parse_timesec(&commit->time, line);
5077         } else if (match_blame_header("author-tz ", &line)) {
5078                 parse_timezone(&commit->time, line);
5080         } else if (match_blame_header("summary ", &line)) {
5081                 string_ncopy(commit->title, line, strlen(line));
5083         } else if (match_blame_header("previous ", &line)) {
5084                 commit->has_previous = TRUE;
5086         } else if (match_blame_header("filename ", &line)) {
5087                 string_ncopy(commit->filename, line, strlen(line));
5088                 commit = NULL;
5089         }
5091         return TRUE;
5094 static bool
5095 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5097         struct blame *blame = line->data;
5098         struct time *time = NULL;
5099         const char *id = NULL, *author = NULL;
5100         char text[SIZEOF_STR];
5102         if (blame->commit && *blame->commit->filename) {
5103                 id = blame->commit->id;
5104                 author = blame->commit->author;
5105                 time = &blame->commit->time;
5106         }
5108         if (opt_date && draw_date(view, time))
5109                 return TRUE;
5111         if (opt_author && draw_author(view, author))
5112                 return TRUE;
5114         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5115                 return TRUE;
5117         if (draw_lineno(view, lineno))
5118                 return TRUE;
5120         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5121         draw_text(view, LINE_DEFAULT, text, TRUE);
5122         return TRUE;
5125 static bool
5126 check_blame_commit(struct blame *blame, bool check_null_id)
5128         if (!blame->commit)
5129                 report("Commit data not loaded yet");
5130         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5131                 report("No commit exist for the selected line");
5132         else
5133                 return TRUE;
5134         return FALSE;
5137 static void
5138 setup_blame_parent_line(struct view *view, struct blame *blame)
5140         const char *diff_tree_argv[] = {
5141                 "git", "diff-tree", "-U0", blame->commit->id,
5142                         "--", blame->commit->filename, NULL
5143         };
5144         struct io io = {};
5145         int parent_lineno = -1;
5146         int blamed_lineno = -1;
5147         char *line;
5149         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5150                 return;
5152         while ((line = io_get(&io, '\n', TRUE))) {
5153                 if (*line == '@') {
5154                         char *pos = strchr(line, '+');
5156                         parent_lineno = atoi(line + 4);
5157                         if (pos)
5158                                 blamed_lineno = atoi(pos + 1);
5160                 } else if (*line == '+' && parent_lineno != -1) {
5161                         if (blame->lineno == blamed_lineno - 1 &&
5162                             !strcmp(blame->text, line + 1)) {
5163                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5164                                 break;
5165                         }
5166                         blamed_lineno++;
5167                 }
5168         }
5170         io_done(&io);
5173 static enum request
5174 blame_request(struct view *view, enum request request, struct line *line)
5176         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5177         struct blame *blame = line->data;
5179         switch (request) {
5180         case REQ_VIEW_BLAME:
5181                 if (check_blame_commit(blame, TRUE)) {
5182                         string_copy(opt_ref, blame->commit->id);
5183                         string_copy(opt_file, blame->commit->filename);
5184                         if (blame->lineno)
5185                                 view->lineno = blame->lineno;
5186                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5187                 }
5188                 break;
5190         case REQ_PARENT:
5191                 if (check_blame_commit(blame, TRUE) &&
5192                     select_commit_parent(blame->commit->id, opt_ref,
5193                                          blame->commit->filename)) {
5194                         string_copy(opt_file, blame->commit->filename);
5195                         setup_blame_parent_line(view, blame);
5196                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5197                 }
5198                 break;
5200         case REQ_ENTER:
5201                 if (!check_blame_commit(blame, FALSE))
5202                         break;
5204                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5205                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5206                         break;
5208                 if (!strcmp(blame->commit->id, NULL_ID)) {
5209                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5210                         const char *diff_index_argv[] = {
5211                                 "git", "diff-index", "--root", "--patch-with-stat",
5212                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5213                         };
5215                         if (!blame->commit->has_previous) {
5216                                 diff_index_argv[1] = "diff";
5217                                 diff_index_argv[2] = "--no-color";
5218                                 diff_index_argv[6] = "--";
5219                                 diff_index_argv[7] = "/dev/null";
5220                         }
5222                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5223                                 report("Failed to allocate diff command");
5224                                 break;
5225                         }
5226                         flags |= OPEN_PREPARED;
5227                 }
5229                 open_view(view, REQ_VIEW_DIFF, flags);
5230                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5231                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5232                 break;
5234         default:
5235                 return request;
5236         }
5238         return REQ_NONE;
5241 static bool
5242 blame_grep(struct view *view, struct line *line)
5244         struct blame *blame = line->data;
5245         struct blame_commit *commit = blame->commit;
5246         const char *text[] = {
5247                 blame->text,
5248                 commit ? commit->title : "",
5249                 commit ? commit->id : "",
5250                 commit && opt_author ? commit->author : "",
5251                 commit ? mkdate(&commit->time, opt_date) : "",
5252                 NULL
5253         };
5255         return grep_text(view, text);
5258 static void
5259 blame_select(struct view *view, struct line *line)
5261         struct blame *blame = line->data;
5262         struct blame_commit *commit = blame->commit;
5264         if (!commit)
5265                 return;
5267         if (!strcmp(commit->id, NULL_ID))
5268                 string_ncopy(ref_commit, "HEAD", 4);
5269         else
5270                 string_copy_rev(ref_commit, commit->id);
5273 static struct view_ops blame_ops = {
5274         "line",
5275         NULL,
5276         blame_open,
5277         blame_read,
5278         blame_draw,
5279         blame_request,
5280         blame_grep,
5281         blame_select,
5282 };
5284 /*
5285  * Branch backend
5286  */
5288 struct branch {
5289         const char *author;             /* Author of the last commit. */
5290         struct time time;               /* Date of the last activity. */
5291         const struct ref *ref;          /* Name and commit ID information. */
5292 };
5294 static const struct ref branch_all;
5296 static const enum sort_field branch_sort_fields[] = {
5297         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5298 };
5299 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5301 static int
5302 branch_compare(const void *l1, const void *l2)
5304         const struct branch *branch1 = ((const struct line *) l1)->data;
5305         const struct branch *branch2 = ((const struct line *) l2)->data;
5307         switch (get_sort_field(branch_sort_state)) {
5308         case ORDERBY_DATE:
5309                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5311         case ORDERBY_AUTHOR:
5312                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5314         case ORDERBY_NAME:
5315         default:
5316                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5317         }
5320 static bool
5321 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5323         struct branch *branch = line->data;
5324         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5326         if (opt_date && draw_date(view, &branch->time))
5327                 return TRUE;
5329         if (opt_author && draw_author(view, branch->author))
5330                 return TRUE;
5332         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5333         return TRUE;
5336 static enum request
5337 branch_request(struct view *view, enum request request, struct line *line)
5339         struct branch *branch = line->data;
5341         switch (request) {
5342         case REQ_REFRESH:
5343                 load_refs();
5344                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5345                 return REQ_NONE;
5347         case REQ_TOGGLE_SORT_FIELD:
5348         case REQ_TOGGLE_SORT_ORDER:
5349                 sort_view(view, request, &branch_sort_state, branch_compare);
5350                 return REQ_NONE;
5352         case REQ_ENTER:
5353                 if (branch->ref == &branch_all) {
5354                         const char *all_branches_argv[] = {
5355                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5356                                       "--topo-order", "--all", NULL
5357                         };
5358                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5360                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5361                                 report("Failed to load view of all branches");
5362                                 return REQ_NONE;
5363                         }
5364                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5365                 } else {
5366                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5367                 }
5368                 return REQ_NONE;
5370         default:
5371                 return request;
5372         }
5375 static bool
5376 branch_read(struct view *view, char *line)
5378         static char id[SIZEOF_REV];
5379         struct branch *reference;
5380         size_t i;
5382         if (!line)
5383                 return TRUE;
5385         switch (get_line_type(line)) {
5386         case LINE_COMMIT:
5387                 string_copy_rev(id, line + STRING_SIZE("commit "));
5388                 return TRUE;
5390         case LINE_AUTHOR:
5391                 for (i = 0, reference = NULL; i < view->lines; i++) {
5392                         struct branch *branch = view->line[i].data;
5394                         if (strcmp(branch->ref->id, id))
5395                                 continue;
5397                         view->line[i].dirty = TRUE;
5398                         if (reference) {
5399                                 branch->author = reference->author;
5400                                 branch->time = reference->time;
5401                                 continue;
5402                         }
5404                         parse_author_line(line + STRING_SIZE("author "),
5405                                           &branch->author, &branch->time);
5406                         reference = branch;
5407                 }
5408                 return TRUE;
5410         default:
5411                 return TRUE;
5412         }
5416 static bool
5417 branch_open_visitor(void *data, const struct ref *ref)
5419         struct view *view = data;
5420         struct branch *branch;
5422         if (ref->tag || ref->ltag || ref->remote)
5423                 return TRUE;
5425         branch = calloc(1, sizeof(*branch));
5426         if (!branch)
5427                 return FALSE;
5429         branch->ref = ref;
5430         return !!add_line_data(view, branch, LINE_DEFAULT);
5433 static bool
5434 branch_open(struct view *view)
5436         const char *branch_log[] = {
5437                 "git", "log", "--no-color", "--pretty=raw",
5438                         "--simplify-by-decoration", "--all", NULL
5439         };
5441         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5442                 report("Failed to load branch data");
5443                 return TRUE;
5444         }
5446         setup_update(view, view->id);
5447         branch_open_visitor(view, &branch_all);
5448         foreach_ref(branch_open_visitor, view);
5449         view->p_restore = TRUE;
5451         return TRUE;
5454 static bool
5455 branch_grep(struct view *view, struct line *line)
5457         struct branch *branch = line->data;
5458         const char *text[] = {
5459                 branch->ref->name,
5460                 branch->author,
5461                 NULL
5462         };
5464         return grep_text(view, text);
5467 static void
5468 branch_select(struct view *view, struct line *line)
5470         struct branch *branch = line->data;
5472         string_copy_rev(view->ref, branch->ref->id);
5473         string_copy_rev(ref_commit, branch->ref->id);
5474         string_copy_rev(ref_head, branch->ref->id);
5475         string_copy_rev(ref_branch, branch->ref->name);
5478 static struct view_ops branch_ops = {
5479         "branch",
5480         NULL,
5481         branch_open,
5482         branch_read,
5483         branch_draw,
5484         branch_request,
5485         branch_grep,
5486         branch_select,
5487 };
5489 /*
5490  * Status backend
5491  */
5493 struct status {
5494         char status;
5495         struct {
5496                 mode_t mode;
5497                 char rev[SIZEOF_REV];
5498                 char name[SIZEOF_STR];
5499         } old;
5500         struct {
5501                 mode_t mode;
5502                 char rev[SIZEOF_REV];
5503                 char name[SIZEOF_STR];
5504         } new;
5505 };
5507 static char status_onbranch[SIZEOF_STR];
5508 static struct status stage_status;
5509 static enum line_type stage_line_type;
5510 static size_t stage_chunks;
5511 static int *stage_chunk;
5513 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5515 /* This should work even for the "On branch" line. */
5516 static inline bool
5517 status_has_none(struct view *view, struct line *line)
5519         return line < view->line + view->lines && !line[1].data;
5522 /* Get fields from the diff line:
5523  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5524  */
5525 static inline bool
5526 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5528         const char *old_mode = buf +  1;
5529         const char *new_mode = buf +  8;
5530         const char *old_rev  = buf + 15;
5531         const char *new_rev  = buf + 56;
5532         const char *status   = buf + 97;
5534         if (bufsize < 98 ||
5535             old_mode[-1] != ':' ||
5536             new_mode[-1] != ' ' ||
5537             old_rev[-1]  != ' ' ||
5538             new_rev[-1]  != ' ' ||
5539             status[-1]   != ' ')
5540                 return FALSE;
5542         file->status = *status;
5544         string_copy_rev(file->old.rev, old_rev);
5545         string_copy_rev(file->new.rev, new_rev);
5547         file->old.mode = strtoul(old_mode, NULL, 8);
5548         file->new.mode = strtoul(new_mode, NULL, 8);
5550         file->old.name[0] = file->new.name[0] = 0;
5552         return TRUE;
5555 static bool
5556 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5558         struct status *unmerged = NULL;
5559         char *buf;
5560         struct io io = {};
5562         if (!io_run(&io, argv, opt_cdup, IO_RD))
5563                 return FALSE;
5565         add_line_data(view, NULL, type);
5567         while ((buf = io_get(&io, 0, TRUE))) {
5568                 struct status *file = unmerged;
5570                 if (!file) {
5571                         file = calloc(1, sizeof(*file));
5572                         if (!file || !add_line_data(view, file, type))
5573                                 goto error_out;
5574                 }
5576                 /* Parse diff info part. */
5577                 if (status) {
5578                         file->status = status;
5579                         if (status == 'A')
5580                                 string_copy(file->old.rev, NULL_ID);
5582                 } else if (!file->status || file == unmerged) {
5583                         if (!status_get_diff(file, buf, strlen(buf)))
5584                                 goto error_out;
5586                         buf = io_get(&io, 0, TRUE);
5587                         if (!buf)
5588                                 break;
5590                         /* Collapse all modified entries that follow an
5591                          * associated unmerged entry. */
5592                         if (unmerged == file) {
5593                                 unmerged->status = 'U';
5594                                 unmerged = NULL;
5595                         } else if (file->status == 'U') {
5596                                 unmerged = file;
5597                         }
5598                 }
5600                 /* Grab the old name for rename/copy. */
5601                 if (!*file->old.name &&
5602                     (file->status == 'R' || file->status == 'C')) {
5603                         string_ncopy(file->old.name, buf, strlen(buf));
5605                         buf = io_get(&io, 0, TRUE);
5606                         if (!buf)
5607                                 break;
5608                 }
5610                 /* git-ls-files just delivers a NUL separated list of
5611                  * file names similar to the second half of the
5612                  * git-diff-* output. */
5613                 string_ncopy(file->new.name, buf, strlen(buf));
5614                 if (!*file->old.name)
5615                         string_copy(file->old.name, file->new.name);
5616                 file = NULL;
5617         }
5619         if (io_error(&io)) {
5620 error_out:
5621                 io_done(&io);
5622                 return FALSE;
5623         }
5625         if (!view->line[view->lines - 1].data)
5626                 add_line_data(view, NULL, LINE_STAT_NONE);
5628         io_done(&io);
5629         return TRUE;
5632 /* Don't show unmerged entries in the staged section. */
5633 static const char *status_diff_index_argv[] = {
5634         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5635                              "--cached", "-M", "HEAD", NULL
5636 };
5638 static const char *status_diff_files_argv[] = {
5639         "git", "diff-files", "-z", NULL
5640 };
5642 static const char *status_list_other_argv[] = {
5643         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5644 };
5646 static const char *status_list_no_head_argv[] = {
5647         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5648 };
5650 static const char *update_index_argv[] = {
5651         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5652 };
5654 /* Restore the previous line number to stay in the context or select a
5655  * line with something that can be updated. */
5656 static void
5657 status_restore(struct view *view)
5659         if (view->p_lineno >= view->lines)
5660                 view->p_lineno = view->lines - 1;
5661         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5662                 view->p_lineno++;
5663         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5664                 view->p_lineno--;
5666         /* If the above fails, always skip the "On branch" line. */
5667         if (view->p_lineno < view->lines)
5668                 view->lineno = view->p_lineno;
5669         else
5670                 view->lineno = 1;
5672         if (view->lineno < view->offset)
5673                 view->offset = view->lineno;
5674         else if (view->offset + view->height <= view->lineno)
5675                 view->offset = view->lineno - view->height + 1;
5677         view->p_restore = FALSE;
5680 static void
5681 status_update_onbranch(void)
5683         static const char *paths[][2] = {
5684                 { "rebase-apply/rebasing",      "Rebasing" },
5685                 { "rebase-apply/applying",      "Applying mailbox" },
5686                 { "rebase-apply/",              "Rebasing mailbox" },
5687                 { "rebase-merge/interactive",   "Interactive rebase" },
5688                 { "rebase-merge/",              "Rebase merge" },
5689                 { "MERGE_HEAD",                 "Merging" },
5690                 { "BISECT_LOG",                 "Bisecting" },
5691                 { "HEAD",                       "On branch" },
5692         };
5693         char buf[SIZEOF_STR];
5694         struct stat stat;
5695         int i;
5697         if (is_initial_commit()) {
5698                 string_copy(status_onbranch, "Initial commit");
5699                 return;
5700         }
5702         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5703                 char *head = opt_head;
5705                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5706                     lstat(buf, &stat) < 0)
5707                         continue;
5709                 if (!*opt_head) {
5710                         struct io io = {};
5712                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5713                             io_read_buf(&io, buf, sizeof(buf))) {
5714                                 head = buf;
5715                                 if (!prefixcmp(head, "refs/heads/"))
5716                                         head += STRING_SIZE("refs/heads/");
5717                         }
5718                 }
5720                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5721                         string_copy(status_onbranch, opt_head);
5722                 return;
5723         }
5725         string_copy(status_onbranch, "Not currently on any branch");
5728 /* First parse staged info using git-diff-index(1), then parse unstaged
5729  * info using git-diff-files(1), and finally untracked files using
5730  * git-ls-files(1). */
5731 static bool
5732 status_open(struct view *view)
5734         reset_view(view);
5736         add_line_data(view, NULL, LINE_STAT_HEAD);
5737         status_update_onbranch();
5739         io_run_bg(update_index_argv);
5741         if (is_initial_commit()) {
5742                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5743                         return FALSE;
5744         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5745                 return FALSE;
5746         }
5748         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5749             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5750                 return FALSE;
5752         /* Restore the exact position or use the specialized restore
5753          * mode? */
5754         if (!view->p_restore)
5755                 status_restore(view);
5756         return TRUE;
5759 static bool
5760 status_draw(struct view *view, struct line *line, unsigned int lineno)
5762         struct status *status = line->data;
5763         enum line_type type;
5764         const char *text;
5766         if (!status) {
5767                 switch (line->type) {
5768                 case LINE_STAT_STAGED:
5769                         type = LINE_STAT_SECTION;
5770                         text = "Changes to be committed:";
5771                         break;
5773                 case LINE_STAT_UNSTAGED:
5774                         type = LINE_STAT_SECTION;
5775                         text = "Changed but not updated:";
5776                         break;
5778                 case LINE_STAT_UNTRACKED:
5779                         type = LINE_STAT_SECTION;
5780                         text = "Untracked files:";
5781                         break;
5783                 case LINE_STAT_NONE:
5784                         type = LINE_DEFAULT;
5785                         text = "  (no files)";
5786                         break;
5788                 case LINE_STAT_HEAD:
5789                         type = LINE_STAT_HEAD;
5790                         text = status_onbranch;
5791                         break;
5793                 default:
5794                         return FALSE;
5795                 }
5796         } else {
5797                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5799                 buf[0] = status->status;
5800                 if (draw_text(view, line->type, buf, TRUE))
5801                         return TRUE;
5802                 type = LINE_DEFAULT;
5803                 text = status->new.name;
5804         }
5806         draw_text(view, type, text, TRUE);
5807         return TRUE;
5810 static enum request
5811 status_load_error(struct view *view, struct view *stage, const char *path)
5813         if (displayed_views() == 2 || display[current_view] != view)
5814                 maximize_view(view);
5815         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5816         return REQ_NONE;
5819 static enum request
5820 status_enter(struct view *view, struct line *line)
5822         struct status *status = line->data;
5823         const char *oldpath = status ? status->old.name : NULL;
5824         /* Diffs for unmerged entries are empty when passing the new
5825          * path, so leave it empty. */
5826         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5827         const char *info;
5828         enum open_flags split;
5829         struct view *stage = VIEW(REQ_VIEW_STAGE);
5831         if (line->type == LINE_STAT_NONE ||
5832             (!status && line[1].type == LINE_STAT_NONE)) {
5833                 report("No file to diff");
5834                 return REQ_NONE;
5835         }
5837         switch (line->type) {
5838         case LINE_STAT_STAGED:
5839                 if (is_initial_commit()) {
5840                         const char *no_head_diff_argv[] = {
5841                                 "git", "diff", "--no-color", "--patch-with-stat",
5842                                         "--", "/dev/null", newpath, NULL
5843                         };
5845                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5846                                 return status_load_error(view, stage, newpath);
5847                 } else {
5848                         const char *index_show_argv[] = {
5849                                 "git", "diff-index", "--root", "--patch-with-stat",
5850                                         "-C", "-M", "--cached", "HEAD", "--",
5851                                         oldpath, newpath, NULL
5852                         };
5854                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5855                                 return status_load_error(view, stage, newpath);
5856                 }
5858                 if (status)
5859                         info = "Staged changes to %s";
5860                 else
5861                         info = "Staged changes";
5862                 break;
5864         case LINE_STAT_UNSTAGED:
5865         {
5866                 const char *files_show_argv[] = {
5867                         "git", "diff-files", "--root", "--patch-with-stat",
5868                                 "-C", "-M", "--", oldpath, newpath, NULL
5869                 };
5871                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5872                         return status_load_error(view, stage, newpath);
5873                 if (status)
5874                         info = "Unstaged changes to %s";
5875                 else
5876                         info = "Unstaged changes";
5877                 break;
5878         }
5879         case LINE_STAT_UNTRACKED:
5880                 if (!newpath) {
5881                         report("No file to show");
5882                         return REQ_NONE;
5883                 }
5885                 if (!suffixcmp(status->new.name, -1, "/")) {
5886                         report("Cannot display a directory");
5887                         return REQ_NONE;
5888                 }
5890                 if (!prepare_update_file(stage, newpath))
5891                         return status_load_error(view, stage, newpath);
5892                 info = "Untracked file %s";
5893                 break;
5895         case LINE_STAT_HEAD:
5896                 return REQ_NONE;
5898         default:
5899                 die("line type %d not handled in switch", line->type);
5900         }
5902         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5903         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5904         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5905                 if (status) {
5906                         stage_status = *status;
5907                 } else {
5908                         memset(&stage_status, 0, sizeof(stage_status));
5909                 }
5911                 stage_line_type = line->type;
5912                 stage_chunks = 0;
5913                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5914         }
5916         return REQ_NONE;
5919 static bool
5920 status_exists(struct status *status, enum line_type type)
5922         struct view *view = VIEW(REQ_VIEW_STATUS);
5923         unsigned long lineno;
5925         for (lineno = 0; lineno < view->lines; lineno++) {
5926                 struct line *line = &view->line[lineno];
5927                 struct status *pos = line->data;
5929                 if (line->type != type)
5930                         continue;
5931                 if (!pos && (!status || !status->status) && line[1].data) {
5932                         select_view_line(view, lineno);
5933                         return TRUE;
5934                 }
5935                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5936                         select_view_line(view, lineno);
5937                         return TRUE;
5938                 }
5939         }
5941         return FALSE;
5945 static bool
5946 status_update_prepare(struct io *io, enum line_type type)
5948         const char *staged_argv[] = {
5949                 "git", "update-index", "-z", "--index-info", NULL
5950         };
5951         const char *others_argv[] = {
5952                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5953         };
5955         switch (type) {
5956         case LINE_STAT_STAGED:
5957                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5959         case LINE_STAT_UNSTAGED:
5960         case LINE_STAT_UNTRACKED:
5961                 return io_run(io, others_argv, opt_cdup, IO_WR);
5963         default:
5964                 die("line type %d not handled in switch", type);
5965                 return FALSE;
5966         }
5969 static bool
5970 status_update_write(struct io *io, struct status *status, enum line_type type)
5972         char buf[SIZEOF_STR];
5973         size_t bufsize = 0;
5975         switch (type) {
5976         case LINE_STAT_STAGED:
5977                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5978                                         status->old.mode,
5979                                         status->old.rev,
5980                                         status->old.name, 0))
5981                         return FALSE;
5982                 break;
5984         case LINE_STAT_UNSTAGED:
5985         case LINE_STAT_UNTRACKED:
5986                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5987                         return FALSE;
5988                 break;
5990         default:
5991                 die("line type %d not handled in switch", type);
5992         }
5994         return io_write(io, buf, bufsize);
5997 static bool
5998 status_update_file(struct status *status, enum line_type type)
6000         struct io io = {};
6001         bool result;
6003         if (!status_update_prepare(&io, type))
6004                 return FALSE;
6006         result = status_update_write(&io, status, type);
6007         return io_done(&io) && result;
6010 static bool
6011 status_update_files(struct view *view, struct line *line)
6013         char buf[sizeof(view->ref)];
6014         struct io io = {};
6015         bool result = TRUE;
6016         struct line *pos = view->line + view->lines;
6017         int files = 0;
6018         int file, done;
6019         int cursor_y = -1, cursor_x = -1;
6021         if (!status_update_prepare(&io, line->type))
6022                 return FALSE;
6024         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6025                 files++;
6027         string_copy(buf, view->ref);
6028         getsyx(cursor_y, cursor_x);
6029         for (file = 0, done = 5; result && file < files; line++, file++) {
6030                 int almost_done = file * 100 / files;
6032                 if (almost_done > done) {
6033                         done = almost_done;
6034                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6035                                       file, files, done);
6036                         update_view_title(view);
6037                         setsyx(cursor_y, cursor_x);
6038                         doupdate();
6039                 }
6040                 result = status_update_write(&io, line->data, line->type);
6041         }
6042         string_copy(view->ref, buf);
6044         return io_done(&io) && result;
6047 static bool
6048 status_update(struct view *view)
6050         struct line *line = &view->line[view->lineno];
6052         assert(view->lines);
6054         if (!line->data) {
6055                 /* This should work even for the "On branch" line. */
6056                 if (line < view->line + view->lines && !line[1].data) {
6057                         report("Nothing to update");
6058                         return FALSE;
6059                 }
6061                 if (!status_update_files(view, line + 1)) {
6062                         report("Failed to update file status");
6063                         return FALSE;
6064                 }
6066         } else if (!status_update_file(line->data, line->type)) {
6067                 report("Failed to update file status");
6068                 return FALSE;
6069         }
6071         return TRUE;
6074 static bool
6075 status_revert(struct status *status, enum line_type type, bool has_none)
6077         if (!status || type != LINE_STAT_UNSTAGED) {
6078                 if (type == LINE_STAT_STAGED) {
6079                         report("Cannot revert changes to staged files");
6080                 } else if (type == LINE_STAT_UNTRACKED) {
6081                         report("Cannot revert changes to untracked files");
6082                 } else if (has_none) {
6083                         report("Nothing to revert");
6084                 } else {
6085                         report("Cannot revert changes to multiple files");
6086                 }
6088         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6089                 char mode[10] = "100644";
6090                 const char *reset_argv[] = {
6091                         "git", "update-index", "--cacheinfo", mode,
6092                                 status->old.rev, status->old.name, NULL
6093                 };
6094                 const char *checkout_argv[] = {
6095                         "git", "checkout", "--", status->old.name, NULL
6096                 };
6098                 if (status->status == 'U') {
6099                         string_format(mode, "%5o", status->old.mode);
6101                         if (status->old.mode == 0 && status->new.mode == 0) {
6102                                 reset_argv[2] = "--force-remove";
6103                                 reset_argv[3] = status->old.name;
6104                                 reset_argv[4] = NULL;
6105                         }
6107                         if (!io_run_fg(reset_argv, opt_cdup))
6108                                 return FALSE;
6109                         if (status->old.mode == 0 && status->new.mode == 0)
6110                                 return TRUE;
6111                 }
6113                 return io_run_fg(checkout_argv, opt_cdup);
6114         }
6116         return FALSE;
6119 static enum request
6120 status_request(struct view *view, enum request request, struct line *line)
6122         struct status *status = line->data;
6124         switch (request) {
6125         case REQ_STATUS_UPDATE:
6126                 if (!status_update(view))
6127                         return REQ_NONE;
6128                 break;
6130         case REQ_STATUS_REVERT:
6131                 if (!status_revert(status, line->type, status_has_none(view, line)))
6132                         return REQ_NONE;
6133                 break;
6135         case REQ_STATUS_MERGE:
6136                 if (!status || status->status != 'U') {
6137                         report("Merging only possible for files with unmerged status ('U').");
6138                         return REQ_NONE;
6139                 }
6140                 open_mergetool(status->new.name);
6141                 break;
6143         case REQ_EDIT:
6144                 if (!status)
6145                         return request;
6146                 if (status->status == 'D') {
6147                         report("File has been deleted.");
6148                         return REQ_NONE;
6149                 }
6151                 open_editor(status->new.name);
6152                 break;
6154         case REQ_VIEW_BLAME:
6155                 if (status)
6156                         opt_ref[0] = 0;
6157                 return request;
6159         case REQ_ENTER:
6160                 /* After returning the status view has been split to
6161                  * show the stage view. No further reloading is
6162                  * necessary. */
6163                 return status_enter(view, line);
6165         case REQ_REFRESH:
6166                 /* Simply reload the view. */
6167                 break;
6169         default:
6170                 return request;
6171         }
6173         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6175         return REQ_NONE;
6178 static void
6179 status_select(struct view *view, struct line *line)
6181         struct status *status = line->data;
6182         char file[SIZEOF_STR] = "all files";
6183         const char *text;
6184         const char *key;
6186         if (status && !string_format(file, "'%s'", status->new.name))
6187                 return;
6189         if (!status && line[1].type == LINE_STAT_NONE)
6190                 line++;
6192         switch (line->type) {
6193         case LINE_STAT_STAGED:
6194                 text = "Press %s to unstage %s for commit";
6195                 break;
6197         case LINE_STAT_UNSTAGED:
6198                 text = "Press %s to stage %s for commit";
6199                 break;
6201         case LINE_STAT_UNTRACKED:
6202                 text = "Press %s to stage %s for addition";
6203                 break;
6205         case LINE_STAT_HEAD:
6206         case LINE_STAT_NONE:
6207                 text = "Nothing to update";
6208                 break;
6210         default:
6211                 die("line type %d not handled in switch", line->type);
6212         }
6214         if (status && status->status == 'U') {
6215                 text = "Press %s to resolve conflict in %s";
6216                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6218         } else {
6219                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6220         }
6222         string_format(view->ref, text, key, file);
6223         if (status)
6224                 string_copy(opt_file, status->new.name);
6227 static bool
6228 status_grep(struct view *view, struct line *line)
6230         struct status *status = line->data;
6232         if (status) {
6233                 const char buf[2] = { status->status, 0 };
6234                 const char *text[] = { status->new.name, buf, NULL };
6236                 return grep_text(view, text);
6237         }
6239         return FALSE;
6242 static struct view_ops status_ops = {
6243         "file",
6244         NULL,
6245         status_open,
6246         NULL,
6247         status_draw,
6248         status_request,
6249         status_grep,
6250         status_select,
6251 };
6254 static bool
6255 stage_diff_write(struct io *io, struct line *line, struct line *end)
6257         while (line < end) {
6258                 if (!io_write(io, line->data, strlen(line->data)) ||
6259                     !io_write(io, "\n", 1))
6260                         return FALSE;
6261                 line++;
6262                 if (line->type == LINE_DIFF_CHUNK ||
6263                     line->type == LINE_DIFF_HEADER)
6264                         break;
6265         }
6267         return TRUE;
6270 static struct line *
6271 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6273         for (; view->line < line; line--)
6274                 if (line->type == type)
6275                         return line;
6277         return NULL;
6280 static bool
6281 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6283         const char *apply_argv[SIZEOF_ARG] = {
6284                 "git", "apply", "--whitespace=nowarn", NULL
6285         };
6286         struct line *diff_hdr;
6287         struct io io = {};
6288         int argc = 3;
6290         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6291         if (!diff_hdr)
6292                 return FALSE;
6294         if (!revert)
6295                 apply_argv[argc++] = "--cached";
6296         if (revert || stage_line_type == LINE_STAT_STAGED)
6297                 apply_argv[argc++] = "-R";
6298         apply_argv[argc++] = "-";
6299         apply_argv[argc++] = NULL;
6300         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6301                 return FALSE;
6303         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6304             !stage_diff_write(&io, chunk, view->line + view->lines))
6305                 chunk = NULL;
6307         io_done(&io);
6308         io_run_bg(update_index_argv);
6310         return chunk ? TRUE : FALSE;
6313 static bool
6314 stage_update(struct view *view, struct line *line)
6316         struct line *chunk = NULL;
6318         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6319                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6321         if (chunk) {
6322                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6323                         report("Failed to apply chunk");
6324                         return FALSE;
6325                 }
6327         } else if (!stage_status.status) {
6328                 view = VIEW(REQ_VIEW_STATUS);
6330                 for (line = view->line; line < view->line + view->lines; line++)
6331                         if (line->type == stage_line_type)
6332                                 break;
6334                 if (!status_update_files(view, line + 1)) {
6335                         report("Failed to update files");
6336                         return FALSE;
6337                 }
6339         } else if (!status_update_file(&stage_status, stage_line_type)) {
6340                 report("Failed to update file");
6341                 return FALSE;
6342         }
6344         return TRUE;
6347 static bool
6348 stage_revert(struct view *view, struct line *line)
6350         struct line *chunk = NULL;
6352         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6353                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6355         if (chunk) {
6356                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6357                         return FALSE;
6359                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6360                         report("Failed to revert chunk");
6361                         return FALSE;
6362                 }
6363                 return TRUE;
6365         } else {
6366                 return status_revert(stage_status.status ? &stage_status : NULL,
6367                                      stage_line_type, FALSE);
6368         }
6372 static void
6373 stage_next(struct view *view, struct line *line)
6375         int i;
6377         if (!stage_chunks) {
6378                 for (line = view->line; line < view->line + view->lines; line++) {
6379                         if (line->type != LINE_DIFF_CHUNK)
6380                                 continue;
6382                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6383                                 report("Allocation failure");
6384                                 return;
6385                         }
6387                         stage_chunk[stage_chunks++] = line - view->line;
6388                 }
6389         }
6391         for (i = 0; i < stage_chunks; i++) {
6392                 if (stage_chunk[i] > view->lineno) {
6393                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6394                         report("Chunk %d of %d", i + 1, stage_chunks);
6395                         return;
6396                 }
6397         }
6399         report("No next chunk found");
6402 static enum request
6403 stage_request(struct view *view, enum request request, struct line *line)
6405         switch (request) {
6406         case REQ_STATUS_UPDATE:
6407                 if (!stage_update(view, line))
6408                         return REQ_NONE;
6409                 break;
6411         case REQ_STATUS_REVERT:
6412                 if (!stage_revert(view, line))
6413                         return REQ_NONE;
6414                 break;
6416         case REQ_STAGE_NEXT:
6417                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6418                         report("File is untracked; press %s to add",
6419                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6420                         return REQ_NONE;
6421                 }
6422                 stage_next(view, line);
6423                 return REQ_NONE;
6425         case REQ_EDIT:
6426                 if (!stage_status.new.name[0])
6427                         return request;
6428                 if (stage_status.status == 'D') {
6429                         report("File has been deleted.");
6430                         return REQ_NONE;
6431                 }
6433                 open_editor(stage_status.new.name);
6434                 break;
6436         case REQ_REFRESH:
6437                 /* Reload everything ... */
6438                 break;
6440         case REQ_VIEW_BLAME:
6441                 if (stage_status.new.name[0]) {
6442                         string_copy(opt_file, stage_status.new.name);
6443                         opt_ref[0] = 0;
6444                 }
6445                 return request;
6447         case REQ_ENTER:
6448                 return pager_request(view, request, line);
6450         default:
6451                 return request;
6452         }
6454         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6455         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6457         /* Check whether the staged entry still exists, and close the
6458          * stage view if it doesn't. */
6459         if (!status_exists(&stage_status, stage_line_type)) {
6460                 status_restore(VIEW(REQ_VIEW_STATUS));
6461                 return REQ_VIEW_CLOSE;
6462         }
6464         if (stage_line_type == LINE_STAT_UNTRACKED) {
6465                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6466                         report("Cannot display a directory");
6467                         return REQ_NONE;
6468                 }
6470                 if (!prepare_update_file(view, stage_status.new.name)) {
6471                         report("Failed to open file: %s", strerror(errno));
6472                         return REQ_NONE;
6473                 }
6474         }
6475         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6477         return REQ_NONE;
6480 static struct view_ops stage_ops = {
6481         "line",
6482         NULL,
6483         NULL,
6484         pager_read,
6485         pager_draw,
6486         stage_request,
6487         pager_grep,
6488         pager_select,
6489 };
6492 /*
6493  * Revision graph
6494  */
6496 struct commit {
6497         char id[SIZEOF_REV];            /* SHA1 ID. */
6498         char title[128];                /* First line of the commit message. */
6499         const char *author;             /* Author of the commit. */
6500         struct time time;               /* Date from the author ident. */
6501         struct ref_list *refs;          /* Repository references. */
6502         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6503         size_t graph_size;              /* The width of the graph array. */
6504         bool has_parents;               /* Rewritten --parents seen. */
6505 };
6507 /* Size of rev graph with no  "padding" columns */
6508 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6510 struct rev_graph {
6511         struct rev_graph *prev, *next, *parents;
6512         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6513         size_t size;
6514         struct commit *commit;
6515         size_t pos;
6516         unsigned int boundary:1;
6517 };
6519 /* Parents of the commit being visualized. */
6520 static struct rev_graph graph_parents[4];
6522 /* The current stack of revisions on the graph. */
6523 static struct rev_graph graph_stacks[4] = {
6524         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6525         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6526         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6527         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6528 };
6530 static inline bool
6531 graph_parent_is_merge(struct rev_graph *graph)
6533         return graph->parents->size > 1;
6536 static inline void
6537 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6539         struct commit *commit = graph->commit;
6541         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6542                 commit->graph[commit->graph_size++] = symbol;
6545 static void
6546 clear_rev_graph(struct rev_graph *graph)
6548         graph->boundary = 0;
6549         graph->size = graph->pos = 0;
6550         graph->commit = NULL;
6551         memset(graph->parents, 0, sizeof(*graph->parents));
6554 static void
6555 done_rev_graph(struct rev_graph *graph)
6557         if (graph_parent_is_merge(graph) &&
6558             graph->pos < graph->size - 1 &&
6559             graph->next->size == graph->size + graph->parents->size - 1) {
6560                 size_t i = graph->pos + graph->parents->size - 1;
6562                 graph->commit->graph_size = i * 2;
6563                 while (i < graph->next->size - 1) {
6564                         append_to_rev_graph(graph, ' ');
6565                         append_to_rev_graph(graph, '\\');
6566                         i++;
6567                 }
6568         }
6570         clear_rev_graph(graph);
6573 static void
6574 push_rev_graph(struct rev_graph *graph, const char *parent)
6576         int i;
6578         /* "Collapse" duplicate parents lines.
6579          *
6580          * FIXME: This needs to also update update the drawn graph but
6581          * for now it just serves as a method for pruning graph lines. */
6582         for (i = 0; i < graph->size; i++)
6583                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6584                         return;
6586         if (graph->size < SIZEOF_REVITEMS) {
6587                 string_copy_rev(graph->rev[graph->size++], parent);
6588         }
6591 static chtype
6592 get_rev_graph_symbol(struct rev_graph *graph)
6594         chtype symbol;
6596         if (graph->boundary)
6597                 symbol = REVGRAPH_BOUND;
6598         else if (graph->parents->size == 0)
6599                 symbol = REVGRAPH_INIT;
6600         else if (graph_parent_is_merge(graph))
6601                 symbol = REVGRAPH_MERGE;
6602         else if (graph->pos >= graph->size)
6603                 symbol = REVGRAPH_BRANCH;
6604         else
6605                 symbol = REVGRAPH_COMMIT;
6607         return symbol;
6610 static void
6611 draw_rev_graph(struct rev_graph *graph)
6613         struct rev_filler {
6614                 chtype separator, line;
6615         };
6616         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6617         static struct rev_filler fillers[] = {
6618                 { ' ',  '|' },
6619                 { '`',  '.' },
6620                 { '\'', ' ' },
6621                 { '/',  ' ' },
6622         };
6623         chtype symbol = get_rev_graph_symbol(graph);
6624         struct rev_filler *filler;
6625         size_t i;
6627         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6628         filler = &fillers[DEFAULT];
6630         for (i = 0; i < graph->pos; i++) {
6631                 append_to_rev_graph(graph, filler->line);
6632                 if (graph_parent_is_merge(graph->prev) &&
6633                     graph->prev->pos == i)
6634                         filler = &fillers[RSHARP];
6636                 append_to_rev_graph(graph, filler->separator);
6637         }
6639         /* Place the symbol for this revision. */
6640         append_to_rev_graph(graph, symbol);
6642         if (graph->prev->size > graph->size)
6643                 filler = &fillers[RDIAG];
6644         else
6645                 filler = &fillers[DEFAULT];
6647         i++;
6649         for (; i < graph->size; i++) {
6650                 append_to_rev_graph(graph, filler->separator);
6651                 append_to_rev_graph(graph, filler->line);
6652                 if (graph_parent_is_merge(graph->prev) &&
6653                     i < graph->prev->pos + graph->parents->size)
6654                         filler = &fillers[RSHARP];
6655                 if (graph->prev->size > graph->size)
6656                         filler = &fillers[LDIAG];
6657         }
6659         if (graph->prev->size > graph->size) {
6660                 append_to_rev_graph(graph, filler->separator);
6661                 if (filler->line != ' ')
6662                         append_to_rev_graph(graph, filler->line);
6663         }
6666 /* Prepare the next rev graph */
6667 static void
6668 prepare_rev_graph(struct rev_graph *graph)
6670         size_t i;
6672         /* First, traverse all lines of revisions up to the active one. */
6673         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6674                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6675                         break;
6677                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6678         }
6680         /* Interleave the new revision parent(s). */
6681         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6682                 push_rev_graph(graph->next, graph->parents->rev[i]);
6684         /* Lastly, put any remaining revisions. */
6685         for (i = graph->pos + 1; i < graph->size; i++)
6686                 push_rev_graph(graph->next, graph->rev[i]);
6689 static void
6690 update_rev_graph(struct view *view, struct rev_graph *graph)
6692         /* If this is the finalizing update ... */
6693         if (graph->commit)
6694                 prepare_rev_graph(graph);
6696         /* Graph visualization needs a one rev look-ahead,
6697          * so the first update doesn't visualize anything. */
6698         if (!graph->prev->commit)
6699                 return;
6701         if (view->lines > 2)
6702                 view->line[view->lines - 3].dirty = 1;
6703         if (view->lines > 1)
6704                 view->line[view->lines - 2].dirty = 1;
6705         draw_rev_graph(graph->prev);
6706         done_rev_graph(graph->prev->prev);
6710 /*
6711  * Main view backend
6712  */
6714 static const char *main_argv[SIZEOF_ARG] = {
6715         "git", "log", "--no-color", "--pretty=raw", "--parents",
6716                       "--topo-order", "%(head)", NULL
6717 };
6719 static bool
6720 main_draw(struct view *view, struct line *line, unsigned int lineno)
6722         struct commit *commit = line->data;
6724         if (!commit->author)
6725                 return FALSE;
6727         if (opt_date && draw_date(view, &commit->time))
6728                 return TRUE;
6730         if (opt_author && draw_author(view, commit->author))
6731                 return TRUE;
6733         if (opt_rev_graph && commit->graph_size &&
6734             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6735                 return TRUE;
6737         if (opt_show_refs && commit->refs) {
6738                 size_t i;
6740                 for (i = 0; i < commit->refs->size; i++) {
6741                         struct ref *ref = commit->refs->refs[i];
6742                         enum line_type type;
6744                         if (ref->head)
6745                                 type = LINE_MAIN_HEAD;
6746                         else if (ref->ltag)
6747                                 type = LINE_MAIN_LOCAL_TAG;
6748                         else if (ref->tag)
6749                                 type = LINE_MAIN_TAG;
6750                         else if (ref->tracked)
6751                                 type = LINE_MAIN_TRACKED;
6752                         else if (ref->remote)
6753                                 type = LINE_MAIN_REMOTE;
6754                         else
6755                                 type = LINE_MAIN_REF;
6757                         if (draw_text(view, type, "[", TRUE) ||
6758                             draw_text(view, type, ref->name, TRUE) ||
6759                             draw_text(view, type, "]", TRUE))
6760                                 return TRUE;
6762                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6763                                 return TRUE;
6764                 }
6765         }
6767         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6768         return TRUE;
6771 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6772 static bool
6773 main_read(struct view *view, char *line)
6775         static struct rev_graph *graph = graph_stacks;
6776         enum line_type type;
6777         struct commit *commit;
6779         if (!line) {
6780                 int i;
6782                 if (!view->lines && !view->prev)
6783                         die("No revisions match the given arguments.");
6784                 if (view->lines > 0) {
6785                         commit = view->line[view->lines - 1].data;
6786                         view->line[view->lines - 1].dirty = 1;
6787                         if (!commit->author) {
6788                                 view->lines--;
6789                                 free(commit);
6790                                 graph->commit = NULL;
6791                         }
6792                 }
6793                 update_rev_graph(view, graph);
6795                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6796                         clear_rev_graph(&graph_stacks[i]);
6797                 return TRUE;
6798         }
6800         type = get_line_type(line);
6801         if (type == LINE_COMMIT) {
6802                 commit = calloc(1, sizeof(struct commit));
6803                 if (!commit)
6804                         return FALSE;
6806                 line += STRING_SIZE("commit ");
6807                 if (*line == '-') {
6808                         graph->boundary = 1;
6809                         line++;
6810                 }
6812                 string_copy_rev(commit->id, line);
6813                 commit->refs = get_ref_list(commit->id);
6814                 graph->commit = commit;
6815                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6817                 while ((line = strchr(line, ' '))) {
6818                         line++;
6819                         push_rev_graph(graph->parents, line);
6820                         commit->has_parents = TRUE;
6821                 }
6822                 return TRUE;
6823         }
6825         if (!view->lines)
6826                 return TRUE;
6827         commit = view->line[view->lines - 1].data;
6829         switch (type) {
6830         case LINE_PARENT:
6831                 if (commit->has_parents)
6832                         break;
6833                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6834                 break;
6836         case LINE_AUTHOR:
6837                 parse_author_line(line + STRING_SIZE("author "),
6838                                   &commit->author, &commit->time);
6839                 update_rev_graph(view, graph);
6840                 graph = graph->next;
6841                 break;
6843         default:
6844                 /* Fill in the commit title if it has not already been set. */
6845                 if (commit->title[0])
6846                         break;
6848                 /* Require titles to start with a non-space character at the
6849                  * offset used by git log. */
6850                 if (strncmp(line, "    ", 4))
6851                         break;
6852                 line += 4;
6853                 /* Well, if the title starts with a whitespace character,
6854                  * try to be forgiving.  Otherwise we end up with no title. */
6855                 while (isspace(*line))
6856                         line++;
6857                 if (*line == '\0')
6858                         break;
6859                 /* FIXME: More graceful handling of titles; append "..." to
6860                  * shortened titles, etc. */
6862                 string_expand(commit->title, sizeof(commit->title), line, 1);
6863                 view->line[view->lines - 1].dirty = 1;
6864         }
6866         return TRUE;
6869 static enum request
6870 main_request(struct view *view, enum request request, struct line *line)
6872         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6874         switch (request) {
6875         case REQ_ENTER:
6876                 open_view(view, REQ_VIEW_DIFF, flags);
6877                 break;
6878         case REQ_REFRESH:
6879                 load_refs();
6880                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6881                 break;
6882         default:
6883                 return request;
6884         }
6886         return REQ_NONE;
6889 static bool
6890 grep_refs(struct ref_list *list, regex_t *regex)
6892         regmatch_t pmatch;
6893         size_t i;
6895         if (!opt_show_refs || !list)
6896                 return FALSE;
6898         for (i = 0; i < list->size; i++) {
6899                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6900                         return TRUE;
6901         }
6903         return FALSE;
6906 static bool
6907 main_grep(struct view *view, struct line *line)
6909         struct commit *commit = line->data;
6910         const char *text[] = {
6911                 commit->title,
6912                 opt_author ? commit->author : "",
6913                 mkdate(&commit->time, opt_date),
6914                 NULL
6915         };
6917         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6920 static void
6921 main_select(struct view *view, struct line *line)
6923         struct commit *commit = line->data;
6925         string_copy_rev(view->ref, commit->id);
6926         string_copy_rev(ref_commit, view->ref);
6929 static struct view_ops main_ops = {
6930         "commit",
6931         main_argv,
6932         NULL,
6933         main_read,
6934         main_draw,
6935         main_request,
6936         main_grep,
6937         main_select,
6938 };
6941 /*
6942  * Status management
6943  */
6945 /* Whether or not the curses interface has been initialized. */
6946 static bool cursed = FALSE;
6948 /* Terminal hacks and workarounds. */
6949 static bool use_scroll_redrawwin;
6950 static bool use_scroll_status_wclear;
6952 /* The status window is used for polling keystrokes. */
6953 static WINDOW *status_win;
6955 /* Reading from the prompt? */
6956 static bool input_mode = FALSE;
6958 static bool status_empty = FALSE;
6960 /* Update status and title window. */
6961 static void
6962 report(const char *msg, ...)
6964         struct view *view = display[current_view];
6966         if (input_mode)
6967                 return;
6969         if (!view) {
6970                 char buf[SIZEOF_STR];
6971                 va_list args;
6973                 va_start(args, msg);
6974                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6975                         buf[sizeof(buf) - 1] = 0;
6976                         buf[sizeof(buf) - 2] = '.';
6977                         buf[sizeof(buf) - 3] = '.';
6978                         buf[sizeof(buf) - 4] = '.';
6979                 }
6980                 va_end(args);
6981                 die("%s", buf);
6982         }
6984         if (!status_empty || *msg) {
6985                 va_list args;
6987                 va_start(args, msg);
6989                 wmove(status_win, 0, 0);
6990                 if (view->has_scrolled && use_scroll_status_wclear)
6991                         wclear(status_win);
6992                 if (*msg) {
6993                         vwprintw(status_win, msg, args);
6994                         status_empty = FALSE;
6995                 } else {
6996                         status_empty = TRUE;
6997                 }
6998                 wclrtoeol(status_win);
6999                 wnoutrefresh(status_win);
7001                 va_end(args);
7002         }
7004         update_view_title(view);
7007 static void
7008 init_display(void)
7010         const char *term;
7011         int x, y;
7013         /* Initialize the curses library */
7014         if (isatty(STDIN_FILENO)) {
7015                 cursed = !!initscr();
7016                 opt_tty = stdin;
7017         } else {
7018                 /* Leave stdin and stdout alone when acting as a pager. */
7019                 opt_tty = fopen("/dev/tty", "r+");
7020                 if (!opt_tty)
7021                         die("Failed to open /dev/tty");
7022                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7023         }
7025         if (!cursed)
7026                 die("Failed to initialize curses");
7028         nonl();         /* Disable conversion and detect newlines from input. */
7029         cbreak();       /* Take input chars one at a time, no wait for \n */
7030         noecho();       /* Don't echo input */
7031         leaveok(stdscr, FALSE);
7033         if (has_colors())
7034                 init_colors();
7036         getmaxyx(stdscr, y, x);
7037         status_win = newwin(1, 0, y - 1, 0);
7038         if (!status_win)
7039                 die("Failed to create status window");
7041         /* Enable keyboard mapping */
7042         keypad(status_win, TRUE);
7043         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7045         TABSIZE = opt_tab_size;
7047         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7048         if (term && !strcmp(term, "gnome-terminal")) {
7049                 /* In the gnome-terminal-emulator, the message from
7050                  * scrolling up one line when impossible followed by
7051                  * scrolling down one line causes corruption of the
7052                  * status line. This is fixed by calling wclear. */
7053                 use_scroll_status_wclear = TRUE;
7054                 use_scroll_redrawwin = FALSE;
7056         } else if (term && !strcmp(term, "xrvt-xpm")) {
7057                 /* No problems with full optimizations in xrvt-(unicode)
7058                  * and aterm. */
7059                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7061         } else {
7062                 /* When scrolling in (u)xterm the last line in the
7063                  * scrolling direction will update slowly. */
7064                 use_scroll_redrawwin = TRUE;
7065                 use_scroll_status_wclear = FALSE;
7066         }
7069 static int
7070 get_input(int prompt_position)
7072         struct view *view;
7073         int i, key, cursor_y, cursor_x;
7074         bool loading = FALSE;
7076         if (prompt_position)
7077                 input_mode = TRUE;
7079         while (TRUE) {
7080                 foreach_view (view, i) {
7081                         update_view(view);
7082                         if (view_is_displayed(view) && view->has_scrolled &&
7083                             use_scroll_redrawwin)
7084                                 redrawwin(view->win);
7085                         view->has_scrolled = FALSE;
7086                         if (view->pipe)
7087                                 loading = TRUE;
7088                 }
7090                 /* Update the cursor position. */
7091                 if (prompt_position) {
7092                         getbegyx(status_win, cursor_y, cursor_x);
7093                         cursor_x = prompt_position;
7094                 } else {
7095                         view = display[current_view];
7096                         getbegyx(view->win, cursor_y, cursor_x);
7097                         cursor_x = view->width - 1;
7098                         cursor_y += view->lineno - view->offset;
7099                 }
7100                 setsyx(cursor_y, cursor_x);
7102                 /* Refresh, accept single keystroke of input */
7103                 doupdate();
7104                 nodelay(status_win, loading);
7105                 key = wgetch(status_win);
7107                 /* wgetch() with nodelay() enabled returns ERR when
7108                  * there's no input. */
7109                 if (key == ERR) {
7111                 } else if (key == KEY_RESIZE) {
7112                         int height, width;
7114                         getmaxyx(stdscr, height, width);
7116                         wresize(status_win, 1, width);
7117                         mvwin(status_win, height - 1, 0);
7118                         wnoutrefresh(status_win);
7119                         resize_display();
7120                         redraw_display(TRUE);
7122                 } else {
7123                         input_mode = FALSE;
7124                         return key;
7125                 }
7126         }
7129 static char *
7130 prompt_input(const char *prompt, input_handler handler, void *data)
7132         enum input_status status = INPUT_OK;
7133         static char buf[SIZEOF_STR];
7134         size_t pos = 0;
7136         buf[pos] = 0;
7138         while (status == INPUT_OK || status == INPUT_SKIP) {
7139                 int key;
7141                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7142                 wclrtoeol(status_win);
7144                 key = get_input(pos + 1);
7145                 switch (key) {
7146                 case KEY_RETURN:
7147                 case KEY_ENTER:
7148                 case '\n':
7149                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7150                         break;
7152                 case KEY_BACKSPACE:
7153                         if (pos > 0)
7154                                 buf[--pos] = 0;
7155                         else
7156                                 status = INPUT_CANCEL;
7157                         break;
7159                 case KEY_ESC:
7160                         status = INPUT_CANCEL;
7161                         break;
7163                 default:
7164                         if (pos >= sizeof(buf)) {
7165                                 report("Input string too long");
7166                                 return NULL;
7167                         }
7169                         status = handler(data, buf, key);
7170                         if (status == INPUT_OK)
7171                                 buf[pos++] = (char) key;
7172                 }
7173         }
7175         /* Clear the status window */
7176         status_empty = FALSE;
7177         report("");
7179         if (status == INPUT_CANCEL)
7180                 return NULL;
7182         buf[pos++] = 0;
7184         return buf;
7187 static enum input_status
7188 prompt_yesno_handler(void *data, char *buf, int c)
7190         if (c == 'y' || c == 'Y')
7191                 return INPUT_STOP;
7192         if (c == 'n' || c == 'N')
7193                 return INPUT_CANCEL;
7194         return INPUT_SKIP;
7197 static bool
7198 prompt_yesno(const char *prompt)
7200         char prompt2[SIZEOF_STR];
7202         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7203                 return FALSE;
7205         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7208 static enum input_status
7209 read_prompt_handler(void *data, char *buf, int c)
7211         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7214 static char *
7215 read_prompt(const char *prompt)
7217         return prompt_input(prompt, read_prompt_handler, NULL);
7220 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7222         enum input_status status = INPUT_OK;
7223         int size = 0;
7225         while (items[size].text)
7226                 size++;
7228         while (status == INPUT_OK) {
7229                 const struct menu_item *item = &items[*selected];
7230                 int key;
7231                 int i;
7233                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7234                           prompt, *selected + 1, size);
7235                 if (item->hotkey)
7236                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7237                 wprintw(status_win, "%s", item->text);
7238                 wclrtoeol(status_win);
7240                 key = get_input(COLS - 1);
7241                 switch (key) {
7242                 case KEY_RETURN:
7243                 case KEY_ENTER:
7244                 case '\n':
7245                         status = INPUT_STOP;
7246                         break;
7248                 case KEY_LEFT:
7249                 case KEY_UP:
7250                         *selected = *selected - 1;
7251                         if (*selected < 0)
7252                                 *selected = size - 1;
7253                         break;
7255                 case KEY_RIGHT:
7256                 case KEY_DOWN:
7257                         *selected = (*selected + 1) % size;
7258                         break;
7260                 case KEY_ESC:
7261                         status = INPUT_CANCEL;
7262                         break;
7264                 default:
7265                         for (i = 0; items[i].text; i++)
7266                                 if (items[i].hotkey == key) {
7267                                         *selected = i;
7268                                         status = INPUT_STOP;
7269                                         break;
7270                                 }
7271                 }
7272         }
7274         /* Clear the status window */
7275         status_empty = FALSE;
7276         report("");
7278         return status != INPUT_CANCEL;
7281 /*
7282  * Repository properties
7283  */
7285 static struct ref **refs = NULL;
7286 static size_t refs_size = 0;
7287 static struct ref *refs_head = NULL;
7289 static struct ref_list **ref_lists = NULL;
7290 static size_t ref_lists_size = 0;
7292 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7293 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7294 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7296 static int
7297 compare_refs(const void *ref1_, const void *ref2_)
7299         const struct ref *ref1 = *(const struct ref **)ref1_;
7300         const struct ref *ref2 = *(const struct ref **)ref2_;
7302         if (ref1->tag != ref2->tag)
7303                 return ref2->tag - ref1->tag;
7304         if (ref1->ltag != ref2->ltag)
7305                 return ref2->ltag - ref2->ltag;
7306         if (ref1->head != ref2->head)
7307                 return ref2->head - ref1->head;
7308         if (ref1->tracked != ref2->tracked)
7309                 return ref2->tracked - ref1->tracked;
7310         if (ref1->remote != ref2->remote)
7311                 return ref2->remote - ref1->remote;
7312         return strcmp(ref1->name, ref2->name);
7315 static void
7316 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7318         size_t i;
7320         for (i = 0; i < refs_size; i++)
7321                 if (!visitor(data, refs[i]))
7322                         break;
7325 static struct ref *
7326 get_ref_head()
7328         return refs_head;
7331 static struct ref_list *
7332 get_ref_list(const char *id)
7334         struct ref_list *list;
7335         size_t i;
7337         for (i = 0; i < ref_lists_size; i++)
7338                 if (!strcmp(id, ref_lists[i]->id))
7339                         return ref_lists[i];
7341         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7342                 return NULL;
7343         list = calloc(1, sizeof(*list));
7344         if (!list)
7345                 return NULL;
7347         for (i = 0; i < refs_size; i++) {
7348                 if (!strcmp(id, refs[i]->id) &&
7349                     realloc_refs_list(&list->refs, list->size, 1))
7350                         list->refs[list->size++] = refs[i];
7351         }
7353         if (!list->refs) {
7354                 free(list);
7355                 return NULL;
7356         }
7358         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7359         ref_lists[ref_lists_size++] = list;
7360         return list;
7363 static int
7364 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7366         struct ref *ref = NULL;
7367         bool tag = FALSE;
7368         bool ltag = FALSE;
7369         bool remote = FALSE;
7370         bool tracked = FALSE;
7371         bool head = FALSE;
7372         int from = 0, to = refs_size - 1;
7374         if (!prefixcmp(name, "refs/tags/")) {
7375                 if (!suffixcmp(name, namelen, "^{}")) {
7376                         namelen -= 3;
7377                         name[namelen] = 0;
7378                 } else {
7379                         ltag = TRUE;
7380                 }
7382                 tag = TRUE;
7383                 namelen -= STRING_SIZE("refs/tags/");
7384                 name    += STRING_SIZE("refs/tags/");
7386         } else if (!prefixcmp(name, "refs/remotes/")) {
7387                 remote = TRUE;
7388                 namelen -= STRING_SIZE("refs/remotes/");
7389                 name    += STRING_SIZE("refs/remotes/");
7390                 tracked  = !strcmp(opt_remote, name);
7392         } else if (!prefixcmp(name, "refs/heads/")) {
7393                 namelen -= STRING_SIZE("refs/heads/");
7394                 name    += STRING_SIZE("refs/heads/");
7395                 if (!strncmp(opt_head, name, namelen))
7396                         return OK;
7398         } else if (!strcmp(name, "HEAD")) {
7399                 head     = TRUE;
7400                 if (*opt_head) {
7401                         namelen  = strlen(opt_head);
7402                         name     = opt_head;
7403                 }
7404         }
7406         /* If we are reloading or it's an annotated tag, replace the
7407          * previous SHA1 with the resolved commit id; relies on the fact
7408          * git-ls-remote lists the commit id of an annotated tag right
7409          * before the commit id it points to. */
7410         while (from <= to) {
7411                 size_t pos = (to + from) / 2;
7412                 int cmp = strcmp(name, refs[pos]->name);
7414                 if (!cmp) {
7415                         ref = refs[pos];
7416                         break;
7417                 }
7419                 if (cmp < 0)
7420                         to = pos - 1;
7421                 else
7422                         from = pos + 1;
7423         }
7425         if (!ref) {
7426                 if (!realloc_refs(&refs, refs_size, 1))
7427                         return ERR;
7428                 ref = calloc(1, sizeof(*ref) + namelen);
7429                 if (!ref)
7430                         return ERR;
7431                 memmove(refs + from + 1, refs + from,
7432                         (refs_size - from) * sizeof(*refs));
7433                 refs[from] = ref;
7434                 strncpy(ref->name, name, namelen);
7435                 refs_size++;
7436         }
7438         ref->head = head;
7439         ref->tag = tag;
7440         ref->ltag = ltag;
7441         ref->remote = remote;
7442         ref->tracked = tracked;
7443         string_copy_rev(ref->id, id);
7445         if (head)
7446                 refs_head = ref;
7447         return OK;
7450 static int
7451 load_refs(void)
7453         const char *head_argv[] = {
7454                 "git", "symbolic-ref", "HEAD", NULL
7455         };
7456         static const char *ls_remote_argv[SIZEOF_ARG] = {
7457                 "git", "ls-remote", opt_git_dir, NULL
7458         };
7459         static bool init = FALSE;
7460         size_t i;
7462         if (!init) {
7463                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7464                         die("TIG_LS_REMOTE contains too many arguments");
7465                 init = TRUE;
7466         }
7468         if (!*opt_git_dir)
7469                 return OK;
7471         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7472             !prefixcmp(opt_head, "refs/heads/")) {
7473                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7475                 memmove(opt_head, offset, strlen(offset) + 1);
7476         }
7478         refs_head = NULL;
7479         for (i = 0; i < refs_size; i++)
7480                 refs[i]->id[0] = 0;
7482         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7483                 return ERR;
7485         /* Update the ref lists to reflect changes. */
7486         for (i = 0; i < ref_lists_size; i++) {
7487                 struct ref_list *list = ref_lists[i];
7488                 size_t old, new;
7490                 for (old = new = 0; old < list->size; old++)
7491                         if (!strcmp(list->id, list->refs[old]->id))
7492                                 list->refs[new++] = list->refs[old];
7493                 list->size = new;
7494         }
7496         return OK;
7499 static void
7500 set_remote_branch(const char *name, const char *value, size_t valuelen)
7502         if (!strcmp(name, ".remote")) {
7503                 string_ncopy(opt_remote, value, valuelen);
7505         } else if (*opt_remote && !strcmp(name, ".merge")) {
7506                 size_t from = strlen(opt_remote);
7508                 if (!prefixcmp(value, "refs/heads/"))
7509                         value += STRING_SIZE("refs/heads/");
7511                 if (!string_format_from(opt_remote, &from, "/%s", value))
7512                         opt_remote[0] = 0;
7513         }
7516 static void
7517 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7519         const char *argv[SIZEOF_ARG] = { name, "=" };
7520         int argc = 1 + (cmd == option_set_command);
7521         int error = ERR;
7523         if (!argv_from_string(argv, &argc, value))
7524                 config_msg = "Too many option arguments";
7525         else
7526                 error = cmd(argc, argv);
7528         if (error == ERR)
7529                 warn("Option 'tig.%s': %s", name, config_msg);
7532 static bool
7533 set_environment_variable(const char *name, const char *value)
7535         size_t len = strlen(name) + 1 + strlen(value) + 1;
7536         char *env = malloc(len);
7538         if (env &&
7539             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7540             putenv(env) == 0)
7541                 return TRUE;
7542         free(env);
7543         return FALSE;
7546 static void
7547 set_work_tree(const char *value)
7549         char cwd[SIZEOF_STR];
7551         if (!getcwd(cwd, sizeof(cwd)))
7552                 die("Failed to get cwd path: %s", strerror(errno));
7553         if (chdir(opt_git_dir) < 0)
7554                 die("Failed to chdir(%s): %s", strerror(errno));
7555         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7556                 die("Failed to get git path: %s", strerror(errno));
7557         if (chdir(cwd) < 0)
7558                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7559         if (chdir(value) < 0)
7560                 die("Failed to chdir(%s): %s", value, strerror(errno));
7561         if (!getcwd(cwd, sizeof(cwd)))
7562                 die("Failed to get cwd path: %s", strerror(errno));
7563         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7564                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7565         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7566                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7567         opt_is_inside_work_tree = TRUE;
7570 static int
7571 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7573         if (!strcmp(name, "i18n.commitencoding"))
7574                 string_ncopy(opt_encoding, value, valuelen);
7576         else if (!strcmp(name, "core.editor"))
7577                 string_ncopy(opt_editor, value, valuelen);
7579         else if (!strcmp(name, "core.worktree"))
7580                 set_work_tree(value);
7582         else if (!prefixcmp(name, "tig.color."))
7583                 set_repo_config_option(name + 10, value, option_color_command);
7585         else if (!prefixcmp(name, "tig.bind."))
7586                 set_repo_config_option(name + 9, value, option_bind_command);
7588         else if (!prefixcmp(name, "tig."))
7589                 set_repo_config_option(name + 4, value, option_set_command);
7591         else if (*opt_head && !prefixcmp(name, "branch.") &&
7592                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7593                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7595         return OK;
7598 static int
7599 load_git_config(void)
7601         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7603         return io_run_load(config_list_argv, "=", read_repo_config_option);
7606 static int
7607 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7609         if (!opt_git_dir[0]) {
7610                 string_ncopy(opt_git_dir, name, namelen);
7612         } else if (opt_is_inside_work_tree == -1) {
7613                 /* This can be 3 different values depending on the
7614                  * version of git being used. If git-rev-parse does not
7615                  * understand --is-inside-work-tree it will simply echo
7616                  * the option else either "true" or "false" is printed.
7617                  * Default to true for the unknown case. */
7618                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7620         } else if (*name == '.') {
7621                 string_ncopy(opt_cdup, name, namelen);
7623         } else {
7624                 string_ncopy(opt_prefix, name, namelen);
7625         }
7627         return OK;
7630 static int
7631 load_repo_info(void)
7633         const char *rev_parse_argv[] = {
7634                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7635                         "--show-cdup", "--show-prefix", NULL
7636         };
7638         return io_run_load(rev_parse_argv, "=", read_repo_info);
7642 /*
7643  * Main
7644  */
7646 static const char usage[] =
7647 "tig " TIG_VERSION " (" __DATE__ ")\n"
7648 "\n"
7649 "Usage: tig        [options] [revs] [--] [paths]\n"
7650 "   or: tig show   [options] [revs] [--] [paths]\n"
7651 "   or: tig blame  [rev] path\n"
7652 "   or: tig status\n"
7653 "   or: tig <      [git command output]\n"
7654 "\n"
7655 "Options:\n"
7656 "  -v, --version   Show version and exit\n"
7657 "  -h, --help      Show help message and exit";
7659 static void __NORETURN
7660 quit(int sig)
7662         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7663         if (cursed)
7664                 endwin();
7665         exit(0);
7668 static void __NORETURN
7669 die(const char *err, ...)
7671         va_list args;
7673         endwin();
7675         va_start(args, err);
7676         fputs("tig: ", stderr);
7677         vfprintf(stderr, err, args);
7678         fputs("\n", stderr);
7679         va_end(args);
7681         exit(1);
7684 static void
7685 warn(const char *msg, ...)
7687         va_list args;
7689         va_start(args, msg);
7690         fputs("tig warning: ", stderr);
7691         vfprintf(stderr, msg, args);
7692         fputs("\n", stderr);
7693         va_end(args);
7696 static enum request
7697 parse_options(int argc, const char *argv[])
7699         enum request request = REQ_VIEW_MAIN;
7700         const char *subcommand;
7701         bool seen_dashdash = FALSE;
7702         /* XXX: This is vulnerable to the user overriding options
7703          * required for the main view parser. */
7704         const char *custom_argv[SIZEOF_ARG] = {
7705                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7706                         "--topo-order", NULL
7707         };
7708         int i, j = 6;
7710         if (!isatty(STDIN_FILENO)) {
7711                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7712                 return REQ_VIEW_PAGER;
7713         }
7715         if (argc <= 1)
7716                 return REQ_NONE;
7718         subcommand = argv[1];
7719         if (!strcmp(subcommand, "status")) {
7720                 if (argc > 2)
7721                         warn("ignoring arguments after `%s'", subcommand);
7722                 return REQ_VIEW_STATUS;
7724         } else if (!strcmp(subcommand, "blame")) {
7725                 if (argc <= 2 || argc > 4)
7726                         die("invalid number of options to blame\n\n%s", usage);
7728                 i = 2;
7729                 if (argc == 4) {
7730                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7731                         i++;
7732                 }
7734                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7735                 return REQ_VIEW_BLAME;
7737         } else if (!strcmp(subcommand, "show")) {
7738                 request = REQ_VIEW_DIFF;
7740         } else {
7741                 subcommand = NULL;
7742         }
7744         if (subcommand) {
7745                 custom_argv[1] = subcommand;
7746                 j = 2;
7747         }
7749         for (i = 1 + !!subcommand; i < argc; i++) {
7750                 const char *opt = argv[i];
7752                 if (seen_dashdash || !strcmp(opt, "--")) {
7753                         seen_dashdash = TRUE;
7755                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7756                         printf("tig version %s\n", TIG_VERSION);
7757                         quit(0);
7759                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7760                         printf("%s\n", usage);
7761                         quit(0);
7762                 }
7764                 custom_argv[j++] = opt;
7765                 if (j >= ARRAY_SIZE(custom_argv))
7766                         die("command too long");
7767         }
7769         if (!prepare_update(VIEW(request), custom_argv, NULL))
7770                 die("Failed to format arguments");
7772         return request;
7775 int
7776 main(int argc, const char *argv[])
7778         const char *codeset = "UTF-8";
7779         enum request request = parse_options(argc, argv);
7780         struct view *view;
7781         size_t i;
7783         signal(SIGINT, quit);
7784         signal(SIGPIPE, SIG_IGN);
7786         if (setlocale(LC_ALL, "")) {
7787                 codeset = nl_langinfo(CODESET);
7788         }
7790         if (load_repo_info() == ERR)
7791                 die("Failed to load repo info.");
7793         if (load_options() == ERR)
7794                 die("Failed to load user config.");
7796         if (load_git_config() == ERR)
7797                 die("Failed to load repo config.");
7799         /* Require a git repository unless when running in pager mode. */
7800         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7801                 die("Not a git repository");
7803         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7804                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7805                 if (opt_iconv_in == ICONV_NONE)
7806                         die("Failed to initialize character set conversion");
7807         }
7809         if (codeset && strcmp(codeset, "UTF-8")) {
7810                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7811                 if (opt_iconv_out == ICONV_NONE)
7812                         die("Failed to initialize character set conversion");
7813         }
7815         if (load_refs() == ERR)
7816                 die("Failed to load refs.");
7818         foreach_view (view, i)
7819                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7820                         die("Too many arguments in the `%s` environment variable",
7821                             view->cmd_env);
7823         init_display();
7825         if (request != REQ_NONE)
7826                 open_view(NULL, request, OPEN_PREPARED);
7827         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7829         while (view_driver(display[current_view], request)) {
7830                 int key = get_input(0);
7832                 view = display[current_view];
7833                 request = get_keybinding(view->keymap, key);
7835                 /* Some low-level request handling. This keeps access to
7836                  * status_win restricted. */
7837                 switch (request) {
7838                 case REQ_NONE:
7839                         report("Unknown key, press %s for help",
7840                                get_key(view->keymap, REQ_VIEW_HELP));
7841                         break;
7842                 case REQ_PROMPT:
7843                 {
7844                         char *cmd = read_prompt(":");
7846                         if (cmd && isdigit(*cmd)) {
7847                                 int lineno = view->lineno + 1;
7849                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7850                                         select_view_line(view, lineno - 1);
7851                                         report("");
7852                                 } else {
7853                                         report("Unable to parse '%s' as a line number", cmd);
7854                                 }
7856                         } else if (cmd) {
7857                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7858                                 const char *argv[SIZEOF_ARG] = { "git" };
7859                                 int argc = 1;
7861                                 /* When running random commands, initially show the
7862                                  * command in the title. However, it maybe later be
7863                                  * overwritten if a commit line is selected. */
7864                                 string_ncopy(next->ref, cmd, strlen(cmd));
7866                                 if (!argv_from_string(argv, &argc, cmd)) {
7867                                         report("Too many arguments");
7868                                 } else if (!prepare_update(next, argv, NULL)) {
7869                                         report("Failed to format command");
7870                                 } else {
7871                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7872                                 }
7873                         }
7875                         request = REQ_NONE;
7876                         break;
7877                 }
7878                 case REQ_SEARCH:
7879                 case REQ_SEARCH_BACK:
7880                 {
7881                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7882                         char *search = read_prompt(prompt);
7884                         if (search)
7885                                 string_ncopy(opt_search, search, strlen(search));
7886                         else if (*opt_search)
7887                                 request = request == REQ_SEARCH ?
7888                                         REQ_FIND_NEXT :
7889                                         REQ_FIND_PREV;
7890                         else
7891                                 request = REQ_NONE;
7892                         break;
7893                 }
7894                 default:
7895                         break;
7896                 }
7897         }
7899         quit(0);
7901         return 0;