Code

Remove unused VIEW_REQ() macro
[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_(RELATIVE), \
523         DATE_(SHORT)
525 enum date {
526 #define DATE_(name) DATE_##name
527         DATE_INFO
528 #undef  DATE_
529 };
531 static const struct enum_map date_map[] = {
532 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
533         DATE_INFO
534 #undef  DATE_
535 };
537 struct time {
538         time_t sec;
539         int tz;
540 };
542 static inline int timecmp(const struct time *t1, const struct time *t2)
544         return t1->sec - t2->sec;
547 static const char *
548 mkdate(const struct time *time, enum date date)
550         static char buf[DATE_COLS + 1];
551         static const struct enum_map reldate[] = {
552                 { "second", 1,                  60 * 2 },
553                 { "minute", 60,                 60 * 60 * 2 },
554                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
555                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
556                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
557                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
558         };
559         struct tm tm;
561         if (!date || !time || !time->sec)
562                 return "";
564         if (date == DATE_RELATIVE) {
565                 struct timeval now;
566                 time_t date = time->sec + time->tz;
567                 time_t seconds;
568                 int i;
570                 gettimeofday(&now, NULL);
571                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
572                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
573                         if (seconds >= reldate[i].value)
574                                 continue;
576                         seconds /= reldate[i].namelen;
577                         if (!string_format(buf, "%ld %s%s %s",
578                                            seconds, reldate[i].name,
579                                            seconds > 1 ? "s" : "",
580                                            now.tv_sec >= date ? "ago" : "ahead"))
581                                 break;
582                         return buf;
583                 }
584         }
586         gmtime_r(&time->sec, &tm);
587         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
591 #define AUTHOR_VALUES \
592         AUTHOR_(NO), \
593         AUTHOR_(FULL), \
594         AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598         AUTHOR_VALUES,
599 #undef  AUTHOR_
600         AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605         AUTHOR_VALUES
606 #undef  AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
612         static char initials[AUTHOR_COLS * 6 + 1];
613         size_t pos = 0;
614         const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618         memset(initials, 0, sizeof(initials));
619         while (author < end) {
620                 unsigned char bytes;
621                 size_t i;
623                 while (is_initial_sep(*author))
624                         author++;
626                 bytes = utf8_char_length(author, end);
627                 if (bytes < sizeof(initials) - 1 - pos) {
628                         while (bytes--) {
629                                 initials[pos++] = *author++;
630                         }
631                 }
633                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634                         if (i < sizeof(initials) - 1)
635                                 initials[i++] = *author;
636                 }
638                 initials[i++] = 0;
639         }
641         return initials;
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
648         int valuelen;
650         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651                 bool advance = cmd[valuelen] != 0;
653                 cmd[valuelen] = 0;
654                 argv[(*argc)++] = chomp_string(cmd);
655                 cmd = chomp_string(cmd + valuelen + advance);
656         }
658         if (*argc < SIZEOF_ARG)
659                 argv[*argc] = NULL;
660         return *argc < SIZEOF_ARG;
663 static bool
664 argv_from_env(const char **argv, const char *name)
666         char *env = argv ? getenv(name) : NULL;
667         int argc = 0;
669         if (env && *env)
670                 env = strdup(env);
671         return !env || argv_from_string(argv, &argc, env);
675 /*
676  * Executing external commands.
677  */
679 enum io_type {
680         IO_FD,                  /* File descriptor based IO. */
681         IO_BG,                  /* Execute command in the background. */
682         IO_FG,                  /* Execute command with same std{in,out,err}. */
683         IO_RD,                  /* Read only fork+exec IO. */
684         IO_WR,                  /* Write only fork+exec IO. */
685         IO_AP,                  /* Append fork+exec output to file. */
686 };
688 struct io {
689         enum io_type type;      /* The requested type of pipe. */
690         const char *dir;        /* Directory from which to execute. */
691         pid_t pid;              /* PID of spawned process. */
692         int pipe;               /* Pipe end for reading or writing. */
693         int error;              /* Error status. */
694         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
695         char *buf;              /* Read buffer. */
696         size_t bufalloc;        /* Allocated buffer size. */
697         size_t bufsize;         /* Buffer content size. */
698         char *bufpos;           /* Current buffer position. */
699         unsigned int eof:1;     /* Has end of file been reached. */
700 };
702 static void
703 io_reset(struct io *io)
705         io->pipe = -1;
706         io->pid = 0;
707         io->buf = io->bufpos = NULL;
708         io->bufalloc = io->bufsize = 0;
709         io->error = 0;
710         io->eof = 0;
713 static void
714 io_init(struct io *io, const char *dir, enum io_type type)
716         io_reset(io);
717         io->type = type;
718         io->dir = dir;
721 static bool
722 io_format(struct io *io, const char *dir, enum io_type type,
723           const char *argv[], enum format_flags flags)
725         io_init(io, dir, type);
726         return format_argv(io->argv, argv, flags);
729 static bool
730 io_open(struct io *io, const char *fmt, ...)
732         char name[SIZEOF_STR] = "";
733         bool fits;
734         va_list args;
736         io_init(io, NULL, IO_FD);
738         va_start(args, fmt);
739         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
740         va_end(args);
742         if (!fits) {
743                 io->error = ENAMETOOLONG;
744                 return FALSE;
745         }
746         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
747         if (io->pipe == -1)
748                 io->error = errno;
749         return io->pipe != -1;
752 static bool
753 io_kill(struct io *io)
755         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
758 static bool
759 io_done(struct io *io)
761         pid_t pid = io->pid;
763         if (io->pipe != -1)
764                 close(io->pipe);
765         free(io->buf);
766         io_reset(io);
768         while (pid > 0) {
769                 int status;
770                 pid_t waiting = waitpid(pid, &status, 0);
772                 if (waiting < 0) {
773                         if (errno == EINTR)
774                                 continue;
775                         io->error = errno;
776                         return FALSE;
777                 }
779                 return waiting == pid &&
780                        !WIFSIGNALED(status) &&
781                        WIFEXITED(status) &&
782                        !WEXITSTATUS(status);
783         }
785         return TRUE;
788 static bool
789 io_start(struct io *io)
791         int pipefds[2] = { -1, -1 };
793         if (io->type == IO_FD)
794                 return TRUE;
796         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
797                 io->error = errno;
798                 return FALSE;
799         } else if (io->type == IO_AP) {
800                 pipefds[1] = io->pipe;
801         }
803         if ((io->pid = fork())) {
804                 if (io->pid == -1)
805                         io->error = errno;
806                 if (pipefds[!(io->type == IO_WR)] != -1)
807                         close(pipefds[!(io->type == IO_WR)]);
808                 if (io->pid != -1) {
809                         io->pipe = pipefds[!!(io->type == IO_WR)];
810                         return TRUE;
811                 }
813         } else {
814                 if (io->type != IO_FG) {
815                         int devnull = open("/dev/null", O_RDWR);
816                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
817                         int writefd = (io->type == IO_RD || io->type == IO_AP)
818                                                         ? pipefds[1] : devnull;
820                         dup2(readfd,  STDIN_FILENO);
821                         dup2(writefd, STDOUT_FILENO);
822                         dup2(devnull, STDERR_FILENO);
824                         close(devnull);
825                         if (pipefds[0] != -1)
826                                 close(pipefds[0]);
827                         if (pipefds[1] != -1)
828                                 close(pipefds[1]);
829                 }
831                 if (io->dir && *io->dir && chdir(io->dir) == -1)
832                         exit(errno);
834                 execvp(io->argv[0], (char *const*) io->argv);
835                 exit(errno);
836         }
838         if (pipefds[!!(io->type == IO_WR)] != -1)
839                 close(pipefds[!!(io->type == IO_WR)]);
840         return FALSE;
843 static bool
844 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
846         io_init(io, dir, type);
847         if (!format_argv(io->argv, argv, FORMAT_NONE))
848                 return FALSE;
849         return io_start(io);
852 static int
853 io_complete(struct io *io)
855         return io_start(io) && io_done(io);
858 static int
859 io_run_bg(const char **argv)
861         struct io io = {};
863         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
864                 return FALSE;
865         return io_complete(&io);
868 static bool
869 io_run_fg(const char **argv, const char *dir)
871         struct io io = {};
873         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
874                 return FALSE;
875         return io_complete(&io);
878 static bool
879 io_run_append(const char **argv, enum format_flags flags, int fd)
881         struct io io = {};
883         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
884                 close(fd);
885                 return FALSE;
886         }
888         io.pipe = fd;
889         return io_complete(&io);
892 static bool
893 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
895         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
898 static bool
899 io_eof(struct io *io)
901         return io->eof;
904 static int
905 io_error(struct io *io)
907         return io->error;
910 static char *
911 io_strerror(struct io *io)
913         return strerror(io->error);
916 static bool
917 io_can_read(struct io *io)
919         struct timeval tv = { 0, 500 };
920         fd_set fds;
922         FD_ZERO(&fds);
923         FD_SET(io->pipe, &fds);
925         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
928 static ssize_t
929 io_read(struct io *io, void *buf, size_t bufsize)
931         do {
932                 ssize_t readsize = read(io->pipe, buf, bufsize);
934                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
935                         continue;
936                 else if (readsize == -1)
937                         io->error = errno;
938                 else if (readsize == 0)
939                         io->eof = 1;
940                 return readsize;
941         } while (1);
944 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
946 static char *
947 io_get(struct io *io, int c, bool can_read)
949         char *eol;
950         ssize_t readsize;
952         while (TRUE) {
953                 if (io->bufsize > 0) {
954                         eol = memchr(io->bufpos, c, io->bufsize);
955                         if (eol) {
956                                 char *line = io->bufpos;
958                                 *eol = 0;
959                                 io->bufpos = eol + 1;
960                                 io->bufsize -= io->bufpos - line;
961                                 return line;
962                         }
963                 }
965                 if (io_eof(io)) {
966                         if (io->bufsize) {
967                                 io->bufpos[io->bufsize] = 0;
968                                 io->bufsize = 0;
969                                 return io->bufpos;
970                         }
971                         return NULL;
972                 }
974                 if (!can_read)
975                         return NULL;
977                 if (io->bufsize > 0 && io->bufpos > io->buf)
978                         memmove(io->buf, io->bufpos, io->bufsize);
980                 if (io->bufalloc == io->bufsize) {
981                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
982                                 return NULL;
983                         io->bufalloc += BUFSIZ;
984                 }
986                 io->bufpos = io->buf;
987                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
988                 if (io_error(io))
989                         return NULL;
990                 io->bufsize += readsize;
991         }
994 static bool
995 io_write(struct io *io, const void *buf, size_t bufsize)
997         size_t written = 0;
999         while (!io_error(io) && written < bufsize) {
1000                 ssize_t size;
1002                 size = write(io->pipe, buf + written, bufsize - written);
1003                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1004                         continue;
1005                 else if (size == -1)
1006                         io->error = errno;
1007                 else
1008                         written += size;
1009         }
1011         return written == bufsize;
1014 static bool
1015 io_read_buf(struct io *io, char buf[], size_t bufsize)
1017         char *result = io_get(io, '\n', TRUE);
1019         if (result) {
1020                 result = chomp_string(result);
1021                 string_ncopy_do(buf, bufsize, result, strlen(result));
1022         }
1024         return io_done(io) && result;
1027 static bool
1028 io_run_buf(const char **argv, char buf[], size_t bufsize)
1030         struct io io = {};
1032         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1033             && io_read_buf(&io, buf, bufsize);
1036 static int
1037 io_load(struct io *io, const char *separators,
1038         int (*read_property)(char *, size_t, char *, size_t))
1040         char *name;
1041         int state = OK;
1043         if (!io_start(io))
1044                 return ERR;
1046         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1047                 char *value;
1048                 size_t namelen;
1049                 size_t valuelen;
1051                 name = chomp_string(name);
1052                 namelen = strcspn(name, separators);
1054                 if (name[namelen]) {
1055                         name[namelen] = 0;
1056                         value = chomp_string(name + namelen + 1);
1057                         valuelen = strlen(value);
1059                 } else {
1060                         value = "";
1061                         valuelen = 0;
1062                 }
1064                 state = read_property(name, namelen, value, valuelen);
1065         }
1067         if (state != ERR && io_error(io))
1068                 state = ERR;
1069         io_done(io);
1071         return state;
1074 static int
1075 io_run_load(const char **argv, const char *separators,
1076             int (*read_property)(char *, size_t, char *, size_t))
1078         struct io io = {};
1080         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1081                 ? io_load(&io, separators, read_property) : ERR;
1085 /*
1086  * User requests
1087  */
1089 #define REQ_INFO \
1090         /* XXX: Keep the view request first and in sync with views[]. */ \
1091         REQ_GROUP("View switching") \
1092         REQ_(VIEW_MAIN,         "Show main view"), \
1093         REQ_(VIEW_DIFF,         "Show diff view"), \
1094         REQ_(VIEW_LOG,          "Show log view"), \
1095         REQ_(VIEW_TREE,         "Show tree view"), \
1096         REQ_(VIEW_BLOB,         "Show blob view"), \
1097         REQ_(VIEW_BLAME,        "Show blame view"), \
1098         REQ_(VIEW_BRANCH,       "Show branch view"), \
1099         REQ_(VIEW_HELP,         "Show help page"), \
1100         REQ_(VIEW_PAGER,        "Show pager view"), \
1101         REQ_(VIEW_STATUS,       "Show status view"), \
1102         REQ_(VIEW_STAGE,        "Show stage view"), \
1103         \
1104         REQ_GROUP("View manipulation") \
1105         REQ_(ENTER,             "Enter current line and scroll"), \
1106         REQ_(NEXT,              "Move to next"), \
1107         REQ_(PREVIOUS,          "Move to previous"), \
1108         REQ_(PARENT,            "Move to parent"), \
1109         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1110         REQ_(REFRESH,           "Reload and refresh"), \
1111         REQ_(MAXIMIZE,          "Maximize the current view"), \
1112         REQ_(VIEW_CLOSE,        "Close the current view"), \
1113         REQ_(QUIT,              "Close all views and quit"), \
1114         \
1115         REQ_GROUP("View specific requests") \
1116         REQ_(STATUS_UPDATE,     "Update file status"), \
1117         REQ_(STATUS_REVERT,     "Revert file changes"), \
1118         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1119         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1120         \
1121         REQ_GROUP("Cursor navigation") \
1122         REQ_(MOVE_UP,           "Move cursor one line up"), \
1123         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1124         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1125         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1126         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1127         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1128         \
1129         REQ_GROUP("Scrolling") \
1130         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1131         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1132         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1133         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1134         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1135         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1136         \
1137         REQ_GROUP("Searching") \
1138         REQ_(SEARCH,            "Search the view"), \
1139         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1140         REQ_(FIND_NEXT,         "Find next search match"), \
1141         REQ_(FIND_PREV,         "Find previous search match"), \
1142         \
1143         REQ_GROUP("Option manipulation") \
1144         REQ_(OPTIONS,           "Open option menu"), \
1145         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1146         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1147         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1149         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1150         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1151         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1153         \
1154         REQ_GROUP("Misc") \
1155         REQ_(PROMPT,            "Bring up the prompt"), \
1156         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1157         REQ_(SHOW_VERSION,      "Show version information"), \
1158         REQ_(STOP_LOADING,      "Stop all loading views"), \
1159         REQ_(EDIT,              "Open in editor"), \
1160         REQ_(NONE,              "Do nothing")
1163 /* User action requests. */
1164 enum request {
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168         /* Offset all requests to avoid conflicts with ncurses getch values. */
1169         REQ_OFFSET = KEY_MAX + 1,
1170         REQ_INFO
1172 #undef  REQ_GROUP
1173 #undef  REQ_
1174 };
1176 struct request_info {
1177         enum request request;
1178         const char *name;
1179         int namelen;
1180         const char *help;
1181 };
1183 static const struct request_info req_info[] = {
1184 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1185 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1186         REQ_INFO
1187 #undef  REQ_GROUP
1188 #undef  REQ_
1189 };
1191 static enum request
1192 get_request(const char *name)
1194         int namelen = strlen(name);
1195         int i;
1197         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1198                 if (enum_equals(req_info[i], name, namelen))
1199                         return req_info[i].request;
1201         return REQ_NONE;
1205 /*
1206  * Options
1207  */
1209 /* Option and state variables. */
1210 static enum date opt_date               = DATE_DEFAULT;
1211 static enum author opt_author           = AUTHOR_DEFAULT;
1212 static bool opt_line_number             = FALSE;
1213 static bool opt_line_graphics           = TRUE;
1214 static bool opt_rev_graph               = FALSE;
1215 static bool opt_show_refs               = TRUE;
1216 static int opt_num_interval             = 5;
1217 static double opt_hscroll               = 0.50;
1218 static double opt_scale_split_view      = 2.0 / 3.0;
1219 static int opt_tab_size                 = 8;
1220 static int opt_author_cols              = AUTHOR_COLS;
1221 static char opt_path[SIZEOF_STR]        = "";
1222 static char opt_file[SIZEOF_STR]        = "";
1223 static char opt_ref[SIZEOF_REF]         = "";
1224 static char opt_head[SIZEOF_REF]        = "";
1225 static char opt_remote[SIZEOF_REF]      = "";
1226 static char opt_encoding[20]            = "UTF-8";
1227 static iconv_t opt_iconv_in             = ICONV_NONE;
1228 static iconv_t opt_iconv_out            = ICONV_NONE;
1229 static char opt_search[SIZEOF_STR]      = "";
1230 static char opt_cdup[SIZEOF_STR]        = "";
1231 static char opt_prefix[SIZEOF_STR]      = "";
1232 static char opt_git_dir[SIZEOF_STR]     = "";
1233 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1234 static char opt_editor[SIZEOF_STR]      = "";
1235 static FILE *opt_tty                    = NULL;
1237 #define is_initial_commit()     (!get_ref_head())
1238 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1241 /*
1242  * Line-oriented content detection.
1243  */
1245 #define LINE_INFO \
1246 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1247 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1248 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1249 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1250 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1251 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1252 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1253 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1254 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1255 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1260 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1261 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1262 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1263 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1264 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1265 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1266 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1267 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1268 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1269 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1270 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1271 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1272 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1273 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1275 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1276 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1277 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1278 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1279 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1280 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1281 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1282 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1283 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1284 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1285 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1286 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1287 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1288 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1289 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1290 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1291 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1292 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1293 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1294 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1295 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1296 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1297 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1298 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1299 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1301 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1302 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1304 enum line_type {
1305 #define LINE(type, line, fg, bg, attr) \
1306         LINE_##type
1307         LINE_INFO,
1308         LINE_NONE
1309 #undef  LINE
1310 };
1312 struct line_info {
1313         const char *name;       /* Option name. */
1314         int namelen;            /* Size of option name. */
1315         const char *line;       /* The start of line to match. */
1316         int linelen;            /* Size of string to match. */
1317         int fg, bg, attr;       /* Color and text attributes for the lines. */
1318 };
1320 static struct line_info line_info[] = {
1321 #define LINE(type, line, fg, bg, attr) \
1322         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1323         LINE_INFO
1324 #undef  LINE
1325 };
1327 static enum line_type
1328 get_line_type(const char *line)
1330         int linelen = strlen(line);
1331         enum line_type type;
1333         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1334                 /* Case insensitive search matches Signed-off-by lines better. */
1335                 if (linelen >= line_info[type].linelen &&
1336                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1337                         return type;
1339         return LINE_DEFAULT;
1342 static inline int
1343 get_line_attr(enum line_type type)
1345         assert(type < ARRAY_SIZE(line_info));
1346         return COLOR_PAIR(type) | line_info[type].attr;
1349 static struct line_info *
1350 get_line_info(const char *name)
1352         size_t namelen = strlen(name);
1353         enum line_type type;
1355         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1356                 if (enum_equals(line_info[type], name, namelen))
1357                         return &line_info[type];
1359         return NULL;
1362 static void
1363 init_colors(void)
1365         int default_bg = line_info[LINE_DEFAULT].bg;
1366         int default_fg = line_info[LINE_DEFAULT].fg;
1367         enum line_type type;
1369         start_color();
1371         if (assume_default_colors(default_fg, default_bg) == ERR) {
1372                 default_bg = COLOR_BLACK;
1373                 default_fg = COLOR_WHITE;
1374         }
1376         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1377                 struct line_info *info = &line_info[type];
1378                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1379                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1381                 init_pair(type, fg, bg);
1382         }
1385 struct line {
1386         enum line_type type;
1388         /* State flags */
1389         unsigned int selected:1;
1390         unsigned int dirty:1;
1391         unsigned int cleareol:1;
1392         unsigned int other:16;
1394         void *data;             /* User data */
1395 };
1398 /*
1399  * Keys
1400  */
1402 struct keybinding {
1403         int alias;
1404         enum request request;
1405 };
1407 static const struct keybinding default_keybindings[] = {
1408         /* View switching */
1409         { 'm',          REQ_VIEW_MAIN },
1410         { 'd',          REQ_VIEW_DIFF },
1411         { 'l',          REQ_VIEW_LOG },
1412         { 't',          REQ_VIEW_TREE },
1413         { 'f',          REQ_VIEW_BLOB },
1414         { 'B',          REQ_VIEW_BLAME },
1415         { 'H',          REQ_VIEW_BRANCH },
1416         { 'p',          REQ_VIEW_PAGER },
1417         { 'h',          REQ_VIEW_HELP },
1418         { 'S',          REQ_VIEW_STATUS },
1419         { 'c',          REQ_VIEW_STAGE },
1421         /* View manipulation */
1422         { 'q',          REQ_VIEW_CLOSE },
1423         { KEY_TAB,      REQ_VIEW_NEXT },
1424         { KEY_RETURN,   REQ_ENTER },
1425         { KEY_UP,       REQ_PREVIOUS },
1426         { KEY_DOWN,     REQ_NEXT },
1427         { 'R',          REQ_REFRESH },
1428         { KEY_F(5),     REQ_REFRESH },
1429         { 'O',          REQ_MAXIMIZE },
1431         /* Cursor navigation */
1432         { 'k',          REQ_MOVE_UP },
1433         { 'j',          REQ_MOVE_DOWN },
1434         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1435         { KEY_END,      REQ_MOVE_LAST_LINE },
1436         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1437         { ' ',          REQ_MOVE_PAGE_DOWN },
1438         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1439         { 'b',          REQ_MOVE_PAGE_UP },
1440         { '-',          REQ_MOVE_PAGE_UP },
1442         /* Scrolling */
1443         { KEY_LEFT,     REQ_SCROLL_LEFT },
1444         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1445         { KEY_IC,       REQ_SCROLL_LINE_UP },
1446         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1447         { 'w',          REQ_SCROLL_PAGE_UP },
1448         { 's',          REQ_SCROLL_PAGE_DOWN },
1450         /* Searching */
1451         { '/',          REQ_SEARCH },
1452         { '?',          REQ_SEARCH_BACK },
1453         { 'n',          REQ_FIND_NEXT },
1454         { 'N',          REQ_FIND_PREV },
1456         /* Misc */
1457         { 'Q',          REQ_QUIT },
1458         { 'z',          REQ_STOP_LOADING },
1459         { 'v',          REQ_SHOW_VERSION },
1460         { 'r',          REQ_SCREEN_REDRAW },
1461         { 'o',          REQ_OPTIONS },
1462         { '.',          REQ_TOGGLE_LINENO },
1463         { 'D',          REQ_TOGGLE_DATE },
1464         { 'A',          REQ_TOGGLE_AUTHOR },
1465         { 'g',          REQ_TOGGLE_REV_GRAPH },
1466         { 'F',          REQ_TOGGLE_REFS },
1467         { 'I',          REQ_TOGGLE_SORT_ORDER },
1468         { 'i',          REQ_TOGGLE_SORT_FIELD },
1469         { ':',          REQ_PROMPT },
1470         { 'u',          REQ_STATUS_UPDATE },
1471         { '!',          REQ_STATUS_REVERT },
1472         { 'M',          REQ_STATUS_MERGE },
1473         { '@',          REQ_STAGE_NEXT },
1474         { ',',          REQ_PARENT },
1475         { 'e',          REQ_EDIT },
1476 };
1478 #define KEYMAP_INFO \
1479         KEYMAP_(GENERIC), \
1480         KEYMAP_(MAIN), \
1481         KEYMAP_(DIFF), \
1482         KEYMAP_(LOG), \
1483         KEYMAP_(TREE), \
1484         KEYMAP_(BLOB), \
1485         KEYMAP_(BLAME), \
1486         KEYMAP_(BRANCH), \
1487         KEYMAP_(PAGER), \
1488         KEYMAP_(HELP), \
1489         KEYMAP_(STATUS), \
1490         KEYMAP_(STAGE)
1492 enum keymap {
1493 #define KEYMAP_(name) KEYMAP_##name
1494         KEYMAP_INFO
1495 #undef  KEYMAP_
1496 };
1498 static const struct enum_map keymap_table[] = {
1499 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1500         KEYMAP_INFO
1501 #undef  KEYMAP_
1502 };
1504 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1506 struct keybinding_table {
1507         struct keybinding *data;
1508         size_t size;
1509 };
1511 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1513 static void
1514 add_keybinding(enum keymap keymap, enum request request, int key)
1516         struct keybinding_table *table = &keybindings[keymap];
1518         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1519         if (!table->data)
1520                 die("Failed to allocate keybinding");
1521         table->data[table->size].alias = key;
1522         table->data[table->size++].request = request;
1525 /* Looks for a key binding first in the given map, then in the generic map, and
1526  * lastly in the default keybindings. */
1527 static enum request
1528 get_keybinding(enum keymap keymap, int key)
1530         size_t i;
1532         for (i = 0; i < keybindings[keymap].size; i++)
1533                 if (keybindings[keymap].data[i].alias == key)
1534                         return keybindings[keymap].data[i].request;
1536         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1537                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1538                         return keybindings[KEYMAP_GENERIC].data[i].request;
1540         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1541                 if (default_keybindings[i].alias == key)
1542                         return default_keybindings[i].request;
1544         return (enum request) key;
1548 struct key {
1549         const char *name;
1550         int value;
1551 };
1553 static const struct key key_table[] = {
1554         { "Enter",      KEY_RETURN },
1555         { "Space",      ' ' },
1556         { "Backspace",  KEY_BACKSPACE },
1557         { "Tab",        KEY_TAB },
1558         { "Escape",     KEY_ESC },
1559         { "Left",       KEY_LEFT },
1560         { "Right",      KEY_RIGHT },
1561         { "Up",         KEY_UP },
1562         { "Down",       KEY_DOWN },
1563         { "Insert",     KEY_IC },
1564         { "Delete",     KEY_DC },
1565         { "Hash",       '#' },
1566         { "Home",       KEY_HOME },
1567         { "End",        KEY_END },
1568         { "PageUp",     KEY_PPAGE },
1569         { "PageDown",   KEY_NPAGE },
1570         { "F1",         KEY_F(1) },
1571         { "F2",         KEY_F(2) },
1572         { "F3",         KEY_F(3) },
1573         { "F4",         KEY_F(4) },
1574         { "F5",         KEY_F(5) },
1575         { "F6",         KEY_F(6) },
1576         { "F7",         KEY_F(7) },
1577         { "F8",         KEY_F(8) },
1578         { "F9",         KEY_F(9) },
1579         { "F10",        KEY_F(10) },
1580         { "F11",        KEY_F(11) },
1581         { "F12",        KEY_F(12) },
1582 };
1584 static int
1585 get_key_value(const char *name)
1587         int i;
1589         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1590                 if (!strcasecmp(key_table[i].name, name))
1591                         return key_table[i].value;
1593         if (strlen(name) == 1 && isprint(*name))
1594                 return (int) *name;
1596         return ERR;
1599 static const char *
1600 get_key_name(int key_value)
1602         static char key_char[] = "'X'";
1603         const char *seq = NULL;
1604         int key;
1606         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1607                 if (key_table[key].value == key_value)
1608                         seq = key_table[key].name;
1610         if (seq == NULL &&
1611             key_value < 127 &&
1612             isprint(key_value)) {
1613                 key_char[1] = (char) key_value;
1614                 seq = key_char;
1615         }
1617         return seq ? seq : "(no key)";
1620 static bool
1621 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1623         const char *sep = *pos > 0 ? ", " : "";
1624         const char *keyname = get_key_name(keybinding->alias);
1626         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1629 static bool
1630 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1631                            enum keymap keymap, bool all)
1633         int i;
1635         for (i = 0; i < keybindings[keymap].size; i++) {
1636                 if (keybindings[keymap].data[i].request == request) {
1637                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1638                                 return FALSE;
1639                         if (!all)
1640                                 break;
1641                 }
1642         }
1644         return TRUE;
1647 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1649 static const char *
1650 get_keys(enum keymap keymap, enum request request, bool all)
1652         static char buf[BUFSIZ];
1653         size_t pos = 0;
1654         int i;
1656         buf[pos] = 0;
1658         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1659                 return "Too many keybindings!";
1660         if (pos > 0 && !all)
1661                 return buf;
1663         if (keymap != KEYMAP_GENERIC) {
1664                 /* Only the generic keymap includes the default keybindings when
1665                  * listing all keys. */
1666                 if (all)
1667                         return buf;
1669                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1670                         return "Too many keybindings!";
1671                 if (pos)
1672                         return buf;
1673         }
1675         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1676                 if (default_keybindings[i].request == request) {
1677                         if (!append_key(buf, &pos, &default_keybindings[i]))
1678                                 return "Too many keybindings!";
1679                         if (!all)
1680                                 return buf;
1681                 }
1682         }
1684         return buf;
1687 struct run_request {
1688         enum keymap keymap;
1689         int key;
1690         const char *argv[SIZEOF_ARG];
1691 };
1693 static struct run_request *run_request;
1694 static size_t run_requests;
1696 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1698 static enum request
1699 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1701         struct run_request *req;
1703         if (argc >= ARRAY_SIZE(req->argv) - 1)
1704                 return REQ_NONE;
1706         if (!realloc_run_requests(&run_request, run_requests, 1))
1707                 return REQ_NONE;
1709         req = &run_request[run_requests];
1710         req->keymap = keymap;
1711         req->key = key;
1712         req->argv[0] = NULL;
1714         if (!format_argv(req->argv, argv, FORMAT_NONE))
1715                 return REQ_NONE;
1717         return REQ_NONE + ++run_requests;
1720 static struct run_request *
1721 get_run_request(enum request request)
1723         if (request <= REQ_NONE)
1724                 return NULL;
1725         return &run_request[request - REQ_NONE - 1];
1728 static void
1729 add_builtin_run_requests(void)
1731         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1732         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1733         const char *commit[] = { "git", "commit", NULL };
1734         const char *gc[] = { "git", "gc", NULL };
1735         struct {
1736                 enum keymap keymap;
1737                 int key;
1738                 int argc;
1739                 const char **argv;
1740         } reqs[] = {
1741                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1742                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1743                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1744                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1745         };
1746         int i;
1748         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1749                 enum request req;
1751                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1752                 if (req != REQ_NONE)
1753                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1754         }
1757 /*
1758  * User config file handling.
1759  */
1761 static int   config_lineno;
1762 static bool  config_errors;
1763 static const char *config_msg;
1765 static const struct enum_map color_map[] = {
1766 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1767         COLOR_MAP(DEFAULT),
1768         COLOR_MAP(BLACK),
1769         COLOR_MAP(BLUE),
1770         COLOR_MAP(CYAN),
1771         COLOR_MAP(GREEN),
1772         COLOR_MAP(MAGENTA),
1773         COLOR_MAP(RED),
1774         COLOR_MAP(WHITE),
1775         COLOR_MAP(YELLOW),
1776 };
1778 static const struct enum_map attr_map[] = {
1779 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1780         ATTR_MAP(NORMAL),
1781         ATTR_MAP(BLINK),
1782         ATTR_MAP(BOLD),
1783         ATTR_MAP(DIM),
1784         ATTR_MAP(REVERSE),
1785         ATTR_MAP(STANDOUT),
1786         ATTR_MAP(UNDERLINE),
1787 };
1789 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1791 static int parse_step(double *opt, const char *arg)
1793         *opt = atoi(arg);
1794         if (!strchr(arg, '%'))
1795                 return OK;
1797         /* "Shift down" so 100% and 1 does not conflict. */
1798         *opt = (*opt - 1) / 100;
1799         if (*opt >= 1.0) {
1800                 *opt = 0.99;
1801                 config_msg = "Step value larger than 100%";
1802                 return ERR;
1803         }
1804         if (*opt < 0.0) {
1805                 *opt = 1;
1806                 config_msg = "Invalid step value";
1807                 return ERR;
1808         }
1809         return OK;
1812 static int
1813 parse_int(int *opt, const char *arg, int min, int max)
1815         int value = atoi(arg);
1817         if (min <= value && value <= max) {
1818                 *opt = value;
1819                 return OK;
1820         }
1822         config_msg = "Integer value out of bound";
1823         return ERR;
1826 static bool
1827 set_color(int *color, const char *name)
1829         if (map_enum(color, color_map, name))
1830                 return TRUE;
1831         if (!prefixcmp(name, "color"))
1832                 return parse_int(color, name + 5, 0, 255) == OK;
1833         return FALSE;
1836 /* Wants: object fgcolor bgcolor [attribute] */
1837 static int
1838 option_color_command(int argc, const char *argv[])
1840         struct line_info *info;
1842         if (argc < 3) {
1843                 config_msg = "Wrong number of arguments given to color command";
1844                 return ERR;
1845         }
1847         info = get_line_info(argv[0]);
1848         if (!info) {
1849                 static const struct enum_map obsolete[] = {
1850                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1851                         ENUM_MAP("main-date",   LINE_DATE),
1852                         ENUM_MAP("main-author", LINE_AUTHOR),
1853                 };
1854                 int index;
1856                 if (!map_enum(&index, obsolete, argv[0])) {
1857                         config_msg = "Unknown color name";
1858                         return ERR;
1859                 }
1860                 info = &line_info[index];
1861         }
1863         if (!set_color(&info->fg, argv[1]) ||
1864             !set_color(&info->bg, argv[2])) {
1865                 config_msg = "Unknown color";
1866                 return ERR;
1867         }
1869         info->attr = 0;
1870         while (argc-- > 3) {
1871                 int attr;
1873                 if (!set_attribute(&attr, argv[argc])) {
1874                         config_msg = "Unknown attribute";
1875                         return ERR;
1876                 }
1877                 info->attr |= attr;
1878         }
1880         return OK;
1883 static int parse_bool(bool *opt, const char *arg)
1885         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1886                 ? TRUE : FALSE;
1887         return OK;
1890 static int parse_enum_do(unsigned int *opt, const char *arg,
1891                          const struct enum_map *map, size_t map_size)
1893         bool is_true;
1895         assert(map_size > 1);
1897         if (map_enum_do(map, map_size, (int *) opt, arg))
1898                 return OK;
1900         if (parse_bool(&is_true, arg) != OK)
1901                 return ERR;
1903         *opt = is_true ? map[1].value : map[0].value;
1904         return OK;
1907 #define parse_enum(opt, arg, map) \
1908         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1910 static int
1911 parse_string(char *opt, const char *arg, size_t optsize)
1913         int arglen = strlen(arg);
1915         switch (arg[0]) {
1916         case '\"':
1917         case '\'':
1918                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1919                         config_msg = "Unmatched quotation";
1920                         return ERR;
1921                 }
1922                 arg += 1; arglen -= 2;
1923         default:
1924                 string_ncopy_do(opt, optsize, arg, arglen);
1925                 return OK;
1926         }
1929 /* Wants: name = value */
1930 static int
1931 option_set_command(int argc, const char *argv[])
1933         if (argc != 3) {
1934                 config_msg = "Wrong number of arguments given to set command";
1935                 return ERR;
1936         }
1938         if (strcmp(argv[1], "=")) {
1939                 config_msg = "No value assigned";
1940                 return ERR;
1941         }
1943         if (!strcmp(argv[0], "show-author"))
1944                 return parse_enum(&opt_author, argv[2], author_map);
1946         if (!strcmp(argv[0], "show-date"))
1947                 return parse_enum(&opt_date, argv[2], date_map);
1949         if (!strcmp(argv[0], "show-rev-graph"))
1950                 return parse_bool(&opt_rev_graph, argv[2]);
1952         if (!strcmp(argv[0], "show-refs"))
1953                 return parse_bool(&opt_show_refs, argv[2]);
1955         if (!strcmp(argv[0], "show-line-numbers"))
1956                 return parse_bool(&opt_line_number, argv[2]);
1958         if (!strcmp(argv[0], "line-graphics"))
1959                 return parse_bool(&opt_line_graphics, argv[2]);
1961         if (!strcmp(argv[0], "line-number-interval"))
1962                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1964         if (!strcmp(argv[0], "author-width"))
1965                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1967         if (!strcmp(argv[0], "horizontal-scroll"))
1968                 return parse_step(&opt_hscroll, argv[2]);
1970         if (!strcmp(argv[0], "split-view-height"))
1971                 return parse_step(&opt_scale_split_view, argv[2]);
1973         if (!strcmp(argv[0], "tab-size"))
1974                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1976         if (!strcmp(argv[0], "commit-encoding"))
1977                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1979         config_msg = "Unknown variable name";
1980         return ERR;
1983 /* Wants: mode request key */
1984 static int
1985 option_bind_command(int argc, const char *argv[])
1987         enum request request;
1988         int keymap = -1;
1989         int key;
1991         if (argc < 3) {
1992                 config_msg = "Wrong number of arguments given to bind command";
1993                 return ERR;
1994         }
1996         if (set_keymap(&keymap, argv[0]) == ERR) {
1997                 config_msg = "Unknown key map";
1998                 return ERR;
1999         }
2001         key = get_key_value(argv[1]);
2002         if (key == ERR) {
2003                 config_msg = "Unknown key";
2004                 return ERR;
2005         }
2007         request = get_request(argv[2]);
2008         if (request == REQ_NONE) {
2009                 static const struct enum_map obsolete[] = {
2010                         ENUM_MAP("cherry-pick",         REQ_NONE),
2011                         ENUM_MAP("screen-resize",       REQ_NONE),
2012                         ENUM_MAP("tree-parent",         REQ_PARENT),
2013                 };
2014                 int alias;
2016                 if (map_enum(&alias, obsolete, argv[2])) {
2017                         if (alias != REQ_NONE)
2018                                 add_keybinding(keymap, alias, key);
2019                         config_msg = "Obsolete request name";
2020                         return ERR;
2021                 }
2022         }
2023         if (request == REQ_NONE && *argv[2]++ == '!')
2024                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2025         if (request == REQ_NONE) {
2026                 config_msg = "Unknown request name";
2027                 return ERR;
2028         }
2030         add_keybinding(keymap, request, key);
2032         return OK;
2035 static int
2036 set_option(const char *opt, char *value)
2038         const char *argv[SIZEOF_ARG];
2039         int argc = 0;
2041         if (!argv_from_string(argv, &argc, value)) {
2042                 config_msg = "Too many option arguments";
2043                 return ERR;
2044         }
2046         if (!strcmp(opt, "color"))
2047                 return option_color_command(argc, argv);
2049         if (!strcmp(opt, "set"))
2050                 return option_set_command(argc, argv);
2052         if (!strcmp(opt, "bind"))
2053                 return option_bind_command(argc, argv);
2055         config_msg = "Unknown option command";
2056         return ERR;
2059 static int
2060 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2062         int status = OK;
2064         config_lineno++;
2065         config_msg = "Internal error";
2067         /* Check for comment markers, since read_properties() will
2068          * only ensure opt and value are split at first " \t". */
2069         optlen = strcspn(opt, "#");
2070         if (optlen == 0)
2071                 return OK;
2073         if (opt[optlen] != 0) {
2074                 config_msg = "No option value";
2075                 status = ERR;
2077         }  else {
2078                 /* Look for comment endings in the value. */
2079                 size_t len = strcspn(value, "#");
2081                 if (len < valuelen) {
2082                         valuelen = len;
2083                         value[valuelen] = 0;
2084                 }
2086                 status = set_option(opt, value);
2087         }
2089         if (status == ERR) {
2090                 warn("Error on line %d, near '%.*s': %s",
2091                      config_lineno, (int) optlen, opt, config_msg);
2092                 config_errors = TRUE;
2093         }
2095         /* Always keep going if errors are encountered. */
2096         return OK;
2099 static void
2100 load_option_file(const char *path)
2102         struct io io = {};
2104         /* It's OK that the file doesn't exist. */
2105         if (!io_open(&io, "%s", path))
2106                 return;
2108         config_lineno = 0;
2109         config_errors = FALSE;
2111         if (io_load(&io, " \t", read_option) == ERR ||
2112             config_errors == TRUE)
2113                 warn("Errors while loading %s.", path);
2116 static int
2117 load_options(void)
2119         const char *home = getenv("HOME");
2120         const char *tigrc_user = getenv("TIGRC_USER");
2121         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2122         char buf[SIZEOF_STR];
2124         add_builtin_run_requests();
2126         if (!tigrc_system)
2127                 tigrc_system = SYSCONFDIR "/tigrc";
2128         load_option_file(tigrc_system);
2130         if (!tigrc_user) {
2131                 if (!home || !string_format(buf, "%s/.tigrc", home))
2132                         return ERR;
2133                 tigrc_user = buf;
2134         }
2135         load_option_file(tigrc_user);
2137         return OK;
2141 /*
2142  * The viewer
2143  */
2145 struct view;
2146 struct view_ops;
2148 /* The display array of active views and the index of the current view. */
2149 static struct view *display[2];
2150 static unsigned int current_view;
2152 #define foreach_displayed_view(view, i) \
2153         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2155 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2157 /* Current head and commit ID */
2158 static char ref_blob[SIZEOF_REF]        = "";
2159 static char ref_commit[SIZEOF_REF]      = "HEAD";
2160 static char ref_head[SIZEOF_REF]        = "HEAD";
2161 static char ref_branch[SIZEOF_REF]      = "";
2163 struct view {
2164         const char *name;       /* View name */
2165         const char *cmd_env;    /* Command line set via environment */
2166         const char *id;         /* Points to either of ref_{head,commit,blob} */
2168         struct view_ops *ops;   /* View operations */
2170         enum keymap keymap;     /* What keymap does this view have */
2171         bool git_dir;           /* Whether the view requires a git directory. */
2172         bool refresh;           /* Whether the view supports refreshing. */
2174         char ref[SIZEOF_REF];   /* Hovered commit reference */
2175         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2177         int height, width;      /* The width and height of the main window */
2178         WINDOW *win;            /* The main window */
2179         WINDOW *title;          /* The title window living below the main window */
2181         /* Navigation */
2182         unsigned long offset;   /* Offset of the window top */
2183         unsigned long yoffset;  /* Offset from the window side. */
2184         unsigned long lineno;   /* Current line number */
2185         unsigned long p_offset; /* Previous offset of the window top */
2186         unsigned long p_yoffset;/* Previous offset from the window side */
2187         unsigned long p_lineno; /* Previous current line number */
2188         bool p_restore;         /* Should the previous position be restored. */
2190         /* Searching */
2191         char grep[SIZEOF_STR];  /* Search string */
2192         regex_t *regex;         /* Pre-compiled regexp */
2194         /* If non-NULL, points to the view that opened this view. If this view
2195          * is closed tig will switch back to the parent view. */
2196         struct view *parent;
2198         /* Buffering */
2199         size_t lines;           /* Total number of lines */
2200         struct line *line;      /* Line index */
2201         unsigned int digits;    /* Number of digits in the lines member. */
2203         /* Drawing */
2204         struct line *curline;   /* Line currently being drawn. */
2205         enum line_type curtype; /* Attribute currently used for drawing. */
2206         unsigned long col;      /* Column when drawing. */
2207         bool has_scrolled;      /* View was scrolled. */
2209         /* Loading */
2210         struct io io;
2211         struct io *pipe;
2212         time_t start_time;
2213         time_t update_secs;
2214 };
2216 struct view_ops {
2217         /* What type of content being displayed. Used in the title bar. */
2218         const char *type;
2219         /* Default command arguments. */
2220         const char **argv;
2221         /* Open and reads in all view content. */
2222         bool (*open)(struct view *view);
2223         /* Read one line; updates view->line. */
2224         bool (*read)(struct view *view, char *data);
2225         /* Draw one line; @lineno must be < view->height. */
2226         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2227         /* Depending on view handle a special requests. */
2228         enum request (*request)(struct view *view, enum request request, struct line *line);
2229         /* Search for regexp in a line. */
2230         bool (*grep)(struct view *view, struct line *line);
2231         /* Select line */
2232         void (*select)(struct view *view, struct line *line);
2233         /* Prepare view for loading */
2234         bool (*prepare)(struct view *view);
2235 };
2237 static struct view_ops blame_ops;
2238 static struct view_ops blob_ops;
2239 static struct view_ops diff_ops;
2240 static struct view_ops help_ops;
2241 static struct view_ops log_ops;
2242 static struct view_ops main_ops;
2243 static struct view_ops pager_ops;
2244 static struct view_ops stage_ops;
2245 static struct view_ops status_ops;
2246 static struct view_ops tree_ops;
2247 static struct view_ops branch_ops;
2249 #define VIEW_STR(name, env, ref, ops, map, git, refresh) \
2250         { name, #env, ref, ops, map, git, refresh }
2252 #define VIEW_(id, name, ops, git, refresh, ref) \
2253         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2255 static struct view views[] = {
2256         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  TRUE,  ref_head),
2257         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  FALSE, ref_commit),
2258         VIEW_(LOG,    "log",    &log_ops,    TRUE,  TRUE,  ref_head),
2259         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  FALSE, ref_commit),
2260         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  FALSE, ref_blob),
2261         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  FALSE, ref_commit),
2262         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  TRUE,  ref_head),
2263         VIEW_(HELP,   "help",   &help_ops,   FALSE, FALSE, ""),
2264         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, FALSE, "stdin"),
2265         VIEW_(STATUS, "status", &status_ops, TRUE,  TRUE,  ""),
2266         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  TRUE,  ""),
2267 };
2269 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2271 #define foreach_view(view, i) \
2272         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2274 #define view_is_displayed(view) \
2275         (view == display[0] || view == display[1])
2278 static inline void
2279 set_view_attr(struct view *view, enum line_type type)
2281         if (!view->curline->selected && view->curtype != type) {
2282                 (void) wattrset(view->win, get_line_attr(type));
2283                 wchgat(view->win, -1, 0, type, NULL);
2284                 view->curtype = type;
2285         }
2288 static int
2289 draw_chars(struct view *view, enum line_type type, const char *string,
2290            int max_len, bool use_tilde)
2292         static char out_buffer[BUFSIZ * 2];
2293         int len = 0;
2294         int col = 0;
2295         int trimmed = FALSE;
2296         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2298         if (max_len <= 0)
2299                 return 0;
2301         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2303         set_view_attr(view, type);
2304         if (len > 0) {
2305                 if (opt_iconv_out != ICONV_NONE) {
2306                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2307                         size_t inlen = len + 1;
2309                         char *outbuf = out_buffer;
2310                         size_t outlen = sizeof(out_buffer);
2312                         size_t ret;
2314                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2315                         if (ret != (size_t) -1) {
2316                                 string = out_buffer;
2317                                 len = sizeof(out_buffer) - outlen;
2318                         }
2319                 }
2321                 waddnstr(view->win, string, len);
2322         }
2323         if (trimmed && use_tilde) {
2324                 set_view_attr(view, LINE_DELIMITER);
2325                 waddch(view->win, '~');
2326                 col++;
2327         }
2329         return col;
2332 static int
2333 draw_space(struct view *view, enum line_type type, int max, int spaces)
2335         static char space[] = "                    ";
2336         int col = 0;
2338         spaces = MIN(max, spaces);
2340         while (spaces > 0) {
2341                 int len = MIN(spaces, sizeof(space) - 1);
2343                 col += draw_chars(view, type, space, len, FALSE);
2344                 spaces -= len;
2345         }
2347         return col;
2350 static bool
2351 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2353         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2354         return view->width + view->yoffset <= view->col;
2357 static bool
2358 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2360         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2361         int max = view->width + view->yoffset - view->col;
2362         int i;
2364         if (max < size)
2365                 size = max;
2367         set_view_attr(view, type);
2368         /* Using waddch() instead of waddnstr() ensures that
2369          * they'll be rendered correctly for the cursor line. */
2370         for (i = skip; i < size; i++)
2371                 waddch(view->win, graphic[i]);
2373         view->col += size;
2374         if (size < max && skip <= size)
2375                 waddch(view->win, ' ');
2376         view->col++;
2378         return view->width + view->yoffset <= view->col;
2381 static bool
2382 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2384         int max = MIN(view->width + view->yoffset - view->col, len);
2385         int col;
2387         if (text)
2388                 col = draw_chars(view, type, text, max - 1, trim);
2389         else
2390                 col = draw_space(view, type, max - 1, max - 1);
2392         view->col += col;
2393         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2394         return view->width + view->yoffset <= view->col;
2397 static bool
2398 draw_date(struct view *view, struct time *time)
2400         const char *date = mkdate(time, opt_date);
2401         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2403         return draw_field(view, LINE_DATE, date, cols, FALSE);
2406 static bool
2407 draw_author(struct view *view, const char *author)
2409         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2410         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2412         if (abbreviate && author)
2413                 author = get_author_initials(author);
2415         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2418 static bool
2419 draw_mode(struct view *view, mode_t mode)
2421         const char *str;
2423         if (S_ISDIR(mode))
2424                 str = "drwxr-xr-x";
2425         else if (S_ISLNK(mode))
2426                 str = "lrwxrwxrwx";
2427         else if (S_ISGITLINK(mode))
2428                 str = "m---------";
2429         else if (S_ISREG(mode) && mode & S_IXUSR)
2430                 str = "-rwxr-xr-x";
2431         else if (S_ISREG(mode))
2432                 str = "-rw-r--r--";
2433         else
2434                 str = "----------";
2436         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2439 static bool
2440 draw_lineno(struct view *view, unsigned int lineno)
2442         char number[10];
2443         int digits3 = view->digits < 3 ? 3 : view->digits;
2444         int max = MIN(view->width + view->yoffset - view->col, digits3);
2445         char *text = NULL;
2446         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2448         lineno += view->offset + 1;
2449         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2450                 static char fmt[] = "%1ld";
2452                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2453                 if (string_format(number, fmt, lineno))
2454                         text = number;
2455         }
2456         if (text)
2457                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2458         else
2459                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2460         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2463 static bool
2464 draw_view_line(struct view *view, unsigned int lineno)
2466         struct line *line;
2467         bool selected = (view->offset + lineno == view->lineno);
2469         assert(view_is_displayed(view));
2471         if (view->offset + lineno >= view->lines)
2472                 return FALSE;
2474         line = &view->line[view->offset + lineno];
2476         wmove(view->win, lineno, 0);
2477         if (line->cleareol)
2478                 wclrtoeol(view->win);
2479         view->col = 0;
2480         view->curline = line;
2481         view->curtype = LINE_NONE;
2482         line->selected = FALSE;
2483         line->dirty = line->cleareol = 0;
2485         if (selected) {
2486                 set_view_attr(view, LINE_CURSOR);
2487                 line->selected = TRUE;
2488                 view->ops->select(view, line);
2489         }
2491         return view->ops->draw(view, line, lineno);
2494 static void
2495 redraw_view_dirty(struct view *view)
2497         bool dirty = FALSE;
2498         int lineno;
2500         for (lineno = 0; lineno < view->height; lineno++) {
2501                 if (view->offset + lineno >= view->lines)
2502                         break;
2503                 if (!view->line[view->offset + lineno].dirty)
2504                         continue;
2505                 dirty = TRUE;
2506                 if (!draw_view_line(view, lineno))
2507                         break;
2508         }
2510         if (!dirty)
2511                 return;
2512         wnoutrefresh(view->win);
2515 static void
2516 redraw_view_from(struct view *view, int lineno)
2518         assert(0 <= lineno && lineno < view->height);
2520         for (; lineno < view->height; lineno++) {
2521                 if (!draw_view_line(view, lineno))
2522                         break;
2523         }
2525         wnoutrefresh(view->win);
2528 static void
2529 redraw_view(struct view *view)
2531         werase(view->win);
2532         redraw_view_from(view, 0);
2536 static void
2537 update_view_title(struct view *view)
2539         char buf[SIZEOF_STR];
2540         char state[SIZEOF_STR];
2541         size_t bufpos = 0, statelen = 0;
2543         assert(view_is_displayed(view));
2545         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2546                 unsigned int view_lines = view->offset + view->height;
2547                 unsigned int lines = view->lines
2548                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2549                                    : 0;
2551                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2552                                    view->ops->type,
2553                                    view->lineno + 1,
2554                                    view->lines,
2555                                    lines);
2557         }
2559         if (view->pipe) {
2560                 time_t secs = time(NULL) - view->start_time;
2562                 /* Three git seconds are a long time ... */
2563                 if (secs > 2)
2564                         string_format_from(state, &statelen, " loading %lds", secs);
2565         }
2567         string_format_from(buf, &bufpos, "[%s]", view->name);
2568         if (*view->ref && bufpos < view->width) {
2569                 size_t refsize = strlen(view->ref);
2570                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2572                 if (minsize < view->width)
2573                         refsize = view->width - minsize + 7;
2574                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2575         }
2577         if (statelen && bufpos < view->width) {
2578                 string_format_from(buf, &bufpos, "%s", state);
2579         }
2581         if (view == display[current_view])
2582                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2583         else
2584                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2586         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2587         wclrtoeol(view->title);
2588         wnoutrefresh(view->title);
2591 static int
2592 apply_step(double step, int value)
2594         if (step >= 1)
2595                 return (int) step;
2596         value *= step + 0.01;
2597         return value ? value : 1;
2600 static void
2601 resize_display(void)
2603         int offset, i;
2604         struct view *base = display[0];
2605         struct view *view = display[1] ? display[1] : display[0];
2607         /* Setup window dimensions */
2609         getmaxyx(stdscr, base->height, base->width);
2611         /* Make room for the status window. */
2612         base->height -= 1;
2614         if (view != base) {
2615                 /* Horizontal split. */
2616                 view->width   = base->width;
2617                 view->height  = apply_step(opt_scale_split_view, base->height);
2618                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2619                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2620                 base->height -= view->height;
2622                 /* Make room for the title bar. */
2623                 view->height -= 1;
2624         }
2626         /* Make room for the title bar. */
2627         base->height -= 1;
2629         offset = 0;
2631         foreach_displayed_view (view, i) {
2632                 if (!view->win) {
2633                         view->win = newwin(view->height, 0, offset, 0);
2634                         if (!view->win)
2635                                 die("Failed to create %s view", view->name);
2637                         scrollok(view->win, FALSE);
2639                         view->title = newwin(1, 0, offset + view->height, 0);
2640                         if (!view->title)
2641                                 die("Failed to create title window");
2643                 } else {
2644                         wresize(view->win, view->height, view->width);
2645                         mvwin(view->win,   offset, 0);
2646                         mvwin(view->title, offset + view->height, 0);
2647                 }
2649                 offset += view->height + 1;
2650         }
2653 static void
2654 redraw_display(bool clear)
2656         struct view *view;
2657         int i;
2659         foreach_displayed_view (view, i) {
2660                 if (clear)
2661                         wclear(view->win);
2662                 redraw_view(view);
2663                 update_view_title(view);
2664         }
2667 static void
2668 toggle_enum_option_do(unsigned int *opt, const char *help,
2669                       const struct enum_map *map, size_t size)
2671         *opt = (*opt + 1) % size;
2672         redraw_display(FALSE);
2673         report("Displaying %s %s", enum_name(map[*opt]), help);
2676 #define toggle_enum_option(opt, help, map) \
2677         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2679 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2680 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2682 static void
2683 toggle_view_option(bool *option, const char *help)
2685         *option = !*option;
2686         redraw_display(FALSE);
2687         report("%sabling %s", *option ? "En" : "Dis", help);
2690 static void
2691 open_option_menu(void)
2693         const struct menu_item menu[] = {
2694                 { '.', "line numbers", &opt_line_number },
2695                 { 'D', "date display", &opt_date },
2696                 { 'A', "author display", &opt_author },
2697                 { 'g', "revision graph display", &opt_rev_graph },
2698                 { 'F', "reference display", &opt_show_refs },
2699                 { 0 }
2700         };
2701         int selected = 0;
2703         if (prompt_menu("Toggle option", menu, &selected)) {
2704                 if (menu[selected].data == &opt_date)
2705                         toggle_date();
2706                 else if (menu[selected].data == &opt_author)
2707                         toggle_author();
2708                 else
2709                         toggle_view_option(menu[selected].data, menu[selected].text);
2710         }
2713 static void
2714 maximize_view(struct view *view)
2716         memset(display, 0, sizeof(display));
2717         current_view = 0;
2718         display[current_view] = view;
2719         resize_display();
2720         redraw_display(FALSE);
2721         report("");
2725 /*
2726  * Navigation
2727  */
2729 static bool
2730 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2732         if (lineno >= view->lines)
2733                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2735         if (offset > lineno || offset + view->height <= lineno) {
2736                 unsigned long half = view->height / 2;
2738                 if (lineno > half)
2739                         offset = lineno - half;
2740                 else
2741                         offset = 0;
2742         }
2744         if (offset != view->offset || lineno != view->lineno) {
2745                 view->offset = offset;
2746                 view->lineno = lineno;
2747                 return TRUE;
2748         }
2750         return FALSE;
2753 /* Scrolling backend */
2754 static void
2755 do_scroll_view(struct view *view, int lines)
2757         bool redraw_current_line = FALSE;
2759         /* The rendering expects the new offset. */
2760         view->offset += lines;
2762         assert(0 <= view->offset && view->offset < view->lines);
2763         assert(lines);
2765         /* Move current line into the view. */
2766         if (view->lineno < view->offset) {
2767                 view->lineno = view->offset;
2768                 redraw_current_line = TRUE;
2769         } else if (view->lineno >= view->offset + view->height) {
2770                 view->lineno = view->offset + view->height - 1;
2771                 redraw_current_line = TRUE;
2772         }
2774         assert(view->offset <= view->lineno && view->lineno < view->lines);
2776         /* Redraw the whole screen if scrolling is pointless. */
2777         if (view->height < ABS(lines)) {
2778                 redraw_view(view);
2780         } else {
2781                 int line = lines > 0 ? view->height - lines : 0;
2782                 int end = line + ABS(lines);
2784                 scrollok(view->win, TRUE);
2785                 wscrl(view->win, lines);
2786                 scrollok(view->win, FALSE);
2788                 while (line < end && draw_view_line(view, line))
2789                         line++;
2791                 if (redraw_current_line)
2792                         draw_view_line(view, view->lineno - view->offset);
2793                 wnoutrefresh(view->win);
2794         }
2796         view->has_scrolled = TRUE;
2797         report("");
2800 /* Scroll frontend */
2801 static void
2802 scroll_view(struct view *view, enum request request)
2804         int lines = 1;
2806         assert(view_is_displayed(view));
2808         switch (request) {
2809         case REQ_SCROLL_LEFT:
2810                 if (view->yoffset == 0) {
2811                         report("Cannot scroll beyond the first column");
2812                         return;
2813                 }
2814                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2815                         view->yoffset = 0;
2816                 else
2817                         view->yoffset -= apply_step(opt_hscroll, view->width);
2818                 redraw_view_from(view, 0);
2819                 report("");
2820                 return;
2821         case REQ_SCROLL_RIGHT:
2822                 view->yoffset += apply_step(opt_hscroll, view->width);
2823                 redraw_view(view);
2824                 report("");
2825                 return;
2826         case REQ_SCROLL_PAGE_DOWN:
2827                 lines = view->height;
2828         case REQ_SCROLL_LINE_DOWN:
2829                 if (view->offset + lines > view->lines)
2830                         lines = view->lines - view->offset;
2832                 if (lines == 0 || view->offset + view->height >= view->lines) {
2833                         report("Cannot scroll beyond the last line");
2834                         return;
2835                 }
2836                 break;
2838         case REQ_SCROLL_PAGE_UP:
2839                 lines = view->height;
2840         case REQ_SCROLL_LINE_UP:
2841                 if (lines > view->offset)
2842                         lines = view->offset;
2844                 if (lines == 0) {
2845                         report("Cannot scroll beyond the first line");
2846                         return;
2847                 }
2849                 lines = -lines;
2850                 break;
2852         default:
2853                 die("request %d not handled in switch", request);
2854         }
2856         do_scroll_view(view, lines);
2859 /* Cursor moving */
2860 static void
2861 move_view(struct view *view, enum request request)
2863         int scroll_steps = 0;
2864         int steps;
2866         switch (request) {
2867         case REQ_MOVE_FIRST_LINE:
2868                 steps = -view->lineno;
2869                 break;
2871         case REQ_MOVE_LAST_LINE:
2872                 steps = view->lines - view->lineno - 1;
2873                 break;
2875         case REQ_MOVE_PAGE_UP:
2876                 steps = view->height > view->lineno
2877                       ? -view->lineno : -view->height;
2878                 break;
2880         case REQ_MOVE_PAGE_DOWN:
2881                 steps = view->lineno + view->height >= view->lines
2882                       ? view->lines - view->lineno - 1 : view->height;
2883                 break;
2885         case REQ_MOVE_UP:
2886                 steps = -1;
2887                 break;
2889         case REQ_MOVE_DOWN:
2890                 steps = 1;
2891                 break;
2893         default:
2894                 die("request %d not handled in switch", request);
2895         }
2897         if (steps <= 0 && view->lineno == 0) {
2898                 report("Cannot move beyond the first line");
2899                 return;
2901         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2902                 report("Cannot move beyond the last line");
2903                 return;
2904         }
2906         /* Move the current line */
2907         view->lineno += steps;
2908         assert(0 <= view->lineno && view->lineno < view->lines);
2910         /* Check whether the view needs to be scrolled */
2911         if (view->lineno < view->offset ||
2912             view->lineno >= view->offset + view->height) {
2913                 scroll_steps = steps;
2914                 if (steps < 0 && -steps > view->offset) {
2915                         scroll_steps = -view->offset;
2917                 } else if (steps > 0) {
2918                         if (view->lineno == view->lines - 1 &&
2919                             view->lines > view->height) {
2920                                 scroll_steps = view->lines - view->offset - 1;
2921                                 if (scroll_steps >= view->height)
2922                                         scroll_steps -= view->height - 1;
2923                         }
2924                 }
2925         }
2927         if (!view_is_displayed(view)) {
2928                 view->offset += scroll_steps;
2929                 assert(0 <= view->offset && view->offset < view->lines);
2930                 view->ops->select(view, &view->line[view->lineno]);
2931                 return;
2932         }
2934         /* Repaint the old "current" line if we be scrolling */
2935         if (ABS(steps) < view->height)
2936                 draw_view_line(view, view->lineno - steps - view->offset);
2938         if (scroll_steps) {
2939                 do_scroll_view(view, scroll_steps);
2940                 return;
2941         }
2943         /* Draw the current line */
2944         draw_view_line(view, view->lineno - view->offset);
2946         wnoutrefresh(view->win);
2947         report("");
2951 /*
2952  * Searching
2953  */
2955 static void search_view(struct view *view, enum request request);
2957 static bool
2958 grep_text(struct view *view, const char *text[])
2960         regmatch_t pmatch;
2961         size_t i;
2963         for (i = 0; text[i]; i++)
2964                 if (*text[i] &&
2965                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2966                         return TRUE;
2967         return FALSE;
2970 static void
2971 select_view_line(struct view *view, unsigned long lineno)
2973         unsigned long old_lineno = view->lineno;
2974         unsigned long old_offset = view->offset;
2976         if (goto_view_line(view, view->offset, lineno)) {
2977                 if (view_is_displayed(view)) {
2978                         if (old_offset != view->offset) {
2979                                 redraw_view(view);
2980                         } else {
2981                                 draw_view_line(view, old_lineno - view->offset);
2982                                 draw_view_line(view, view->lineno - view->offset);
2983                                 wnoutrefresh(view->win);
2984                         }
2985                 } else {
2986                         view->ops->select(view, &view->line[view->lineno]);
2987                 }
2988         }
2991 static void
2992 find_next(struct view *view, enum request request)
2994         unsigned long lineno = view->lineno;
2995         int direction;
2997         if (!*view->grep) {
2998                 if (!*opt_search)
2999                         report("No previous search");
3000                 else
3001                         search_view(view, request);
3002                 return;
3003         }
3005         switch (request) {
3006         case REQ_SEARCH:
3007         case REQ_FIND_NEXT:
3008                 direction = 1;
3009                 break;
3011         case REQ_SEARCH_BACK:
3012         case REQ_FIND_PREV:
3013                 direction = -1;
3014                 break;
3016         default:
3017                 return;
3018         }
3020         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3021                 lineno += direction;
3023         /* Note, lineno is unsigned long so will wrap around in which case it
3024          * will become bigger than view->lines. */
3025         for (; lineno < view->lines; lineno += direction) {
3026                 if (view->ops->grep(view, &view->line[lineno])) {
3027                         select_view_line(view, lineno);
3028                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3029                         return;
3030                 }
3031         }
3033         report("No match found for '%s'", view->grep);
3036 static void
3037 search_view(struct view *view, enum request request)
3039         int regex_err;
3041         if (view->regex) {
3042                 regfree(view->regex);
3043                 *view->grep = 0;
3044         } else {
3045                 view->regex = calloc(1, sizeof(*view->regex));
3046                 if (!view->regex)
3047                         return;
3048         }
3050         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3051         if (regex_err != 0) {
3052                 char buf[SIZEOF_STR] = "unknown error";
3054                 regerror(regex_err, view->regex, buf, sizeof(buf));
3055                 report("Search failed: %s", buf);
3056                 return;
3057         }
3059         string_copy(view->grep, opt_search);
3061         find_next(view, request);
3064 /*
3065  * Incremental updating
3066  */
3068 static void
3069 reset_view(struct view *view)
3071         int i;
3073         for (i = 0; i < view->lines; i++)
3074                 free(view->line[i].data);
3075         free(view->line);
3077         view->p_offset = view->offset;
3078         view->p_yoffset = view->yoffset;
3079         view->p_lineno = view->lineno;
3081         view->line = NULL;
3082         view->offset = 0;
3083         view->yoffset = 0;
3084         view->lines  = 0;
3085         view->lineno = 0;
3086         view->vid[0] = 0;
3087         view->update_secs = 0;
3090 static void
3091 free_argv(const char *argv[])
3093         int argc;
3095         for (argc = 0; argv[argc]; argc++)
3096                 free((void *) argv[argc]);
3099 static const char *
3100 format_arg(const char *name)
3102         static struct {
3103                 const char *name;
3104                 size_t namelen;
3105                 const char *value;
3106                 const char *value_if_empty;
3107         } vars[] = {
3108 #define FORMAT_VAR(name, value, value_if_empty) \
3109         { name, STRING_SIZE(name), value, value_if_empty }
3110                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3111                 FORMAT_VAR("%(file)",           opt_file,       ""),
3112                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3113                 FORMAT_VAR("%(head)",           ref_head,       ""),
3114                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3115                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3116                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3117         };
3118         int i;
3120         for (i = 0; i < ARRAY_SIZE(vars); i++)
3121                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3122                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3124         report("Unknown replacement: `%s`", name);
3125         return NULL;
3128 static bool
3129 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3131         char buf[SIZEOF_STR];
3132         int argc;
3133         bool noreplace = flags == FORMAT_NONE;
3135         free_argv(dst_argv);
3137         for (argc = 0; src_argv[argc]; argc++) {
3138                 const char *arg = src_argv[argc];
3139                 size_t bufpos = 0;
3141                 while (arg) {
3142                         char *next = strstr(arg, "%(");
3143                         int len = next - arg;
3144                         const char *value;
3146                         if (!next || noreplace) {
3147                                 len = strlen(arg);
3148                                 value = "";
3150                         } else {
3151                                 value = format_arg(next);
3153                                 if (!value) {
3154                                         return FALSE;
3155                                 }
3156                         }
3158                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3159                                 return FALSE;
3161                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3162                 }
3164                 dst_argv[argc] = strdup(buf);
3165                 if (!dst_argv[argc])
3166                         break;
3167         }
3169         dst_argv[argc] = NULL;
3171         return src_argv[argc] == NULL;
3174 static bool
3175 restore_view_position(struct view *view)
3177         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3178                 return FALSE;
3180         /* Changing the view position cancels the restoring. */
3181         /* FIXME: Changing back to the first line is not detected. */
3182         if (view->offset != 0 || view->lineno != 0) {
3183                 view->p_restore = FALSE;
3184                 return FALSE;
3185         }
3187         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3188             view_is_displayed(view))
3189                 werase(view->win);
3191         view->yoffset = view->p_yoffset;
3192         view->p_restore = FALSE;
3194         return TRUE;
3197 static void
3198 end_update(struct view *view, bool force)
3200         if (!view->pipe)
3201                 return;
3202         while (!view->ops->read(view, NULL))
3203                 if (!force)
3204                         return;
3205         if (force)
3206                 io_kill(view->pipe);
3207         io_done(view->pipe);
3208         view->pipe = NULL;
3211 static void
3212 setup_update(struct view *view, const char *vid)
3214         reset_view(view);
3215         string_copy_rev(view->vid, vid);
3216         view->pipe = &view->io;
3217         view->start_time = time(NULL);
3220 static bool
3221 prepare_update(struct view *view, const char *argv[], const char *dir)
3223         if (view->pipe)
3224                 end_update(view, TRUE);
3225         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3228 static bool
3229 prepare_update_file(struct view *view, const char *name)
3231         if (view->pipe)
3232                 end_update(view, TRUE);
3233         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3236 static bool
3237 begin_update(struct view *view, bool refresh)
3239         if (view->pipe)
3240                 end_update(view, TRUE);
3242         if (!refresh) {
3243                 if (view->ops->prepare) {
3244                         if (!view->ops->prepare(view))
3245                                 return FALSE;
3246                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3247                         return FALSE;
3248                 }
3250                 /* Put the current ref_* value to the view title ref
3251                  * member. This is needed by the blob view. Most other
3252                  * views sets it automatically after loading because the
3253                  * first line is a commit line. */
3254                 string_copy_rev(view->ref, view->id);
3255         }
3257         if (!io_start(&view->io))
3258                 return FALSE;
3260         setup_update(view, view->id);
3262         return TRUE;
3265 static bool
3266 update_view(struct view *view)
3268         char out_buffer[BUFSIZ * 2];
3269         char *line;
3270         /* Clear the view and redraw everything since the tree sorting
3271          * might have rearranged things. */
3272         bool redraw = view->lines == 0;
3273         bool can_read = TRUE;
3275         if (!view->pipe)
3276                 return TRUE;
3278         if (!io_can_read(view->pipe)) {
3279                 if (view->lines == 0 && view_is_displayed(view)) {
3280                         time_t secs = time(NULL) - view->start_time;
3282                         if (secs > 1 && secs > view->update_secs) {
3283                                 if (view->update_secs == 0)
3284                                         redraw_view(view);
3285                                 update_view_title(view);
3286                                 view->update_secs = secs;
3287                         }
3288                 }
3289                 return TRUE;
3290         }
3292         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3293                 if (opt_iconv_in != ICONV_NONE) {
3294                         ICONV_CONST char *inbuf = line;
3295                         size_t inlen = strlen(line) + 1;
3297                         char *outbuf = out_buffer;
3298                         size_t outlen = sizeof(out_buffer);
3300                         size_t ret;
3302                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3303                         if (ret != (size_t) -1)
3304                                 line = out_buffer;
3305                 }
3307                 if (!view->ops->read(view, line)) {
3308                         report("Allocation failure");
3309                         end_update(view, TRUE);
3310                         return FALSE;
3311                 }
3312         }
3314         {
3315                 unsigned long lines = view->lines;
3316                 int digits;
3318                 for (digits = 0; lines; digits++)
3319                         lines /= 10;
3321                 /* Keep the displayed view in sync with line number scaling. */
3322                 if (digits != view->digits) {
3323                         view->digits = digits;
3324                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3325                                 redraw = TRUE;
3326                 }
3327         }
3329         if (io_error(view->pipe)) {
3330                 report("Failed to read: %s", io_strerror(view->pipe));
3331                 end_update(view, TRUE);
3333         } else if (io_eof(view->pipe)) {
3334                 report("");
3335                 end_update(view, FALSE);
3336         }
3338         if (restore_view_position(view))
3339                 redraw = TRUE;
3341         if (!view_is_displayed(view))
3342                 return TRUE;
3344         if (redraw)
3345                 redraw_view_from(view, 0);
3346         else
3347                 redraw_view_dirty(view);
3349         /* Update the title _after_ the redraw so that if the redraw picks up a
3350          * commit reference in view->ref it'll be available here. */
3351         update_view_title(view);
3352         return TRUE;
3355 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3357 static struct line *
3358 add_line_data(struct view *view, void *data, enum line_type type)
3360         struct line *line;
3362         if (!realloc_lines(&view->line, view->lines, 1))
3363                 return NULL;
3365         line = &view->line[view->lines++];
3366         memset(line, 0, sizeof(*line));
3367         line->type = type;
3368         line->data = data;
3369         line->dirty = 1;
3371         return line;
3374 static struct line *
3375 add_line_text(struct view *view, const char *text, enum line_type type)
3377         char *data = text ? strdup(text) : NULL;
3379         return data ? add_line_data(view, data, type) : NULL;
3382 static struct line *
3383 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3385         char buf[SIZEOF_STR];
3386         va_list args;
3388         va_start(args, fmt);
3389         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3390                 buf[0] = 0;
3391         va_end(args);
3393         return buf[0] ? add_line_text(view, buf, type) : NULL;
3396 /*
3397  * View opening
3398  */
3400 enum open_flags {
3401         OPEN_DEFAULT = 0,       /* Use default view switching. */
3402         OPEN_SPLIT = 1,         /* Split current view. */
3403         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3404         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3405         OPEN_PREPARED = 32,     /* Open already prepared command. */
3406 };
3408 static void
3409 open_view(struct view *prev, enum request request, enum open_flags flags)
3411         bool split = !!(flags & OPEN_SPLIT);
3412         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3413         bool nomaximize = !!(flags & OPEN_REFRESH);
3414         struct view *view = VIEW(request);
3415         int nviews = displayed_views();
3416         struct view *base_view = display[0];
3418         if (view == prev && nviews == 1 && !reload) {
3419                 report("Already in %s view", view->name);
3420                 return;
3421         }
3423         if (view->git_dir && !opt_git_dir[0]) {
3424                 report("The %s view is disabled in pager view", view->name);
3425                 return;
3426         }
3428         if (split) {
3429                 display[1] = view;
3430                 current_view = 1;
3431         } else if (!nomaximize) {
3432                 /* Maximize the current view. */
3433                 memset(display, 0, sizeof(display));
3434                 current_view = 0;
3435                 display[current_view] = view;
3436         }
3438         /* No parent signals that this is the first loaded view. */
3439         if (prev && view != prev) {
3440                 view->parent = prev;
3441         }
3443         /* Resize the view when switching between split- and full-screen,
3444          * or when switching between two different full-screen views. */
3445         if (nviews != displayed_views() ||
3446             (nviews == 1 && base_view != display[0]))
3447                 resize_display();
3449         if (view->ops->open) {
3450                 if (view->pipe)
3451                         end_update(view, TRUE);
3452                 if (!view->ops->open(view)) {
3453                         report("Failed to load %s view", view->name);
3454                         return;
3455                 }
3456                 restore_view_position(view);
3458         } else if ((reload || strcmp(view->vid, view->id)) &&
3459                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3460                 report("Failed to load %s view", view->name);
3461                 return;
3462         }
3464         if (split && prev->lineno - prev->offset >= prev->height) {
3465                 /* Take the title line into account. */
3466                 int lines = prev->lineno - prev->offset - prev->height + 1;
3468                 /* Scroll the view that was split if the current line is
3469                  * outside the new limited view. */
3470                 do_scroll_view(prev, lines);
3471         }
3473         if (prev && view != prev && split && view_is_displayed(prev)) {
3474                 /* "Blur" the previous view. */
3475                 update_view_title(prev);
3476         }
3478         if (view->pipe && view->lines == 0) {
3479                 /* Clear the old view and let the incremental updating refill
3480                  * the screen. */
3481                 werase(view->win);
3482                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3483                 report("");
3484         } else if (view_is_displayed(view)) {
3485                 redraw_view(view);
3486                 report("");
3487         }
3490 static void
3491 open_external_viewer(const char *argv[], const char *dir)
3493         def_prog_mode();           /* save current tty modes */
3494         endwin();                  /* restore original tty modes */
3495         io_run_fg(argv, dir);
3496         fprintf(stderr, "Press Enter to continue");
3497         getc(opt_tty);
3498         reset_prog_mode();
3499         redraw_display(TRUE);
3502 static void
3503 open_mergetool(const char *file)
3505         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3507         open_external_viewer(mergetool_argv, opt_cdup);
3510 static void
3511 open_editor(const char *file)
3513         const char *editor_argv[] = { "vi", file, NULL };
3514         const char *editor;
3516         editor = getenv("GIT_EDITOR");
3517         if (!editor && *opt_editor)
3518                 editor = opt_editor;
3519         if (!editor)
3520                 editor = getenv("VISUAL");
3521         if (!editor)
3522                 editor = getenv("EDITOR");
3523         if (!editor)
3524                 editor = "vi";
3526         editor_argv[0] = editor;
3527         open_external_viewer(editor_argv, opt_cdup);
3530 static void
3531 open_run_request(enum request request)
3533         struct run_request *req = get_run_request(request);
3534         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3536         if (!req) {
3537                 report("Unknown run request");
3538                 return;
3539         }
3541         if (format_argv(argv, req->argv, FORMAT_ALL))
3542                 open_external_viewer(argv, NULL);
3543         free_argv(argv);
3546 /*
3547  * User request switch noodle
3548  */
3550 static int
3551 view_driver(struct view *view, enum request request)
3553         int i;
3555         if (request == REQ_NONE)
3556                 return TRUE;
3558         if (request > REQ_NONE) {
3559                 open_run_request(request);
3560                 /* FIXME: When all views can refresh always do this. */
3561                 if (view->refresh)
3562                         request = REQ_REFRESH;
3563                 else
3564                         return TRUE;
3565         }
3567         if (view && view->lines) {
3568                 request = view->ops->request(view, request, &view->line[view->lineno]);
3569                 if (request == REQ_NONE)
3570                         return TRUE;
3571         }
3573         switch (request) {
3574         case REQ_MOVE_UP:
3575         case REQ_MOVE_DOWN:
3576         case REQ_MOVE_PAGE_UP:
3577         case REQ_MOVE_PAGE_DOWN:
3578         case REQ_MOVE_FIRST_LINE:
3579         case REQ_MOVE_LAST_LINE:
3580                 move_view(view, request);
3581                 break;
3583         case REQ_SCROLL_LEFT:
3584         case REQ_SCROLL_RIGHT:
3585         case REQ_SCROLL_LINE_DOWN:
3586         case REQ_SCROLL_LINE_UP:
3587         case REQ_SCROLL_PAGE_DOWN:
3588         case REQ_SCROLL_PAGE_UP:
3589                 scroll_view(view, request);
3590                 break;
3592         case REQ_VIEW_BLAME:
3593                 if (!opt_file[0]) {
3594                         report("No file chosen, press %s to open tree view",
3595                                get_key(view->keymap, REQ_VIEW_TREE));
3596                         break;
3597                 }
3598                 open_view(view, request, OPEN_DEFAULT);
3599                 break;
3601         case REQ_VIEW_BLOB:
3602                 if (!ref_blob[0]) {
3603                         report("No file chosen, press %s to open tree view",
3604                                get_key(view->keymap, REQ_VIEW_TREE));
3605                         break;
3606                 }
3607                 open_view(view, request, OPEN_DEFAULT);
3608                 break;
3610         case REQ_VIEW_PAGER:
3611                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3612                         report("No pager content, press %s to run command from prompt",
3613                                get_key(view->keymap, REQ_PROMPT));
3614                         break;
3615                 }
3616                 open_view(view, request, OPEN_DEFAULT);
3617                 break;
3619         case REQ_VIEW_STAGE:
3620                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3621                         report("No stage content, press %s to open the status view and choose file",
3622                                get_key(view->keymap, REQ_VIEW_STATUS));
3623                         break;
3624                 }
3625                 open_view(view, request, OPEN_DEFAULT);
3626                 break;
3628         case REQ_VIEW_STATUS:
3629                 if (opt_is_inside_work_tree == FALSE) {
3630                         report("The status view requires a working tree");
3631                         break;
3632                 }
3633                 open_view(view, request, OPEN_DEFAULT);
3634                 break;
3636         case REQ_VIEW_MAIN:
3637         case REQ_VIEW_DIFF:
3638         case REQ_VIEW_LOG:
3639         case REQ_VIEW_TREE:
3640         case REQ_VIEW_HELP:
3641         case REQ_VIEW_BRANCH:
3642                 open_view(view, request, OPEN_DEFAULT);
3643                 break;
3645         case REQ_NEXT:
3646         case REQ_PREVIOUS:
3647                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3649                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3650                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3651                    (view == VIEW(REQ_VIEW_DIFF) &&
3652                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3653                    (view == VIEW(REQ_VIEW_STAGE) &&
3654                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3655                    (view == VIEW(REQ_VIEW_BLOB) &&
3656                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3657                    (view == VIEW(REQ_VIEW_MAIN) &&
3658                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3659                         int line;
3661                         view = view->parent;
3662                         line = view->lineno;
3663                         move_view(view, request);
3664                         if (view_is_displayed(view))
3665                                 update_view_title(view);
3666                         if (line != view->lineno)
3667                                 view->ops->request(view, REQ_ENTER,
3668                                                    &view->line[view->lineno]);
3670                 } else {
3671                         move_view(view, request);
3672                 }
3673                 break;
3675         case REQ_VIEW_NEXT:
3676         {
3677                 int nviews = displayed_views();
3678                 int next_view = (current_view + 1) % nviews;
3680                 if (next_view == current_view) {
3681                         report("Only one view is displayed");
3682                         break;
3683                 }
3685                 current_view = next_view;
3686                 /* Blur out the title of the previous view. */
3687                 update_view_title(view);
3688                 report("");
3689                 break;
3690         }
3691         case REQ_REFRESH:
3692                 report("Refreshing is not yet supported for the %s view", view->name);
3693                 break;
3695         case REQ_MAXIMIZE:
3696                 if (displayed_views() == 2)
3697                         maximize_view(view);
3698                 break;
3700         case REQ_OPTIONS:
3701                 open_option_menu();
3702                 break;
3704         case REQ_TOGGLE_LINENO:
3705                 toggle_view_option(&opt_line_number, "line numbers");
3706                 break;
3708         case REQ_TOGGLE_DATE:
3709                 toggle_date();
3710                 break;
3712         case REQ_TOGGLE_AUTHOR:
3713                 toggle_author();
3714                 break;
3716         case REQ_TOGGLE_REV_GRAPH:
3717                 toggle_view_option(&opt_rev_graph, "revision graph display");
3718                 break;
3720         case REQ_TOGGLE_REFS:
3721                 toggle_view_option(&opt_show_refs, "reference display");
3722                 break;
3724         case REQ_TOGGLE_SORT_FIELD:
3725         case REQ_TOGGLE_SORT_ORDER:
3726                 report("Sorting is not yet supported for the %s view", view->name);
3727                 break;
3729         case REQ_SEARCH:
3730         case REQ_SEARCH_BACK:
3731                 search_view(view, request);
3732                 break;
3734         case REQ_FIND_NEXT:
3735         case REQ_FIND_PREV:
3736                 find_next(view, request);
3737                 break;
3739         case REQ_STOP_LOADING:
3740                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3741                         view = &views[i];
3742                         if (view->pipe)
3743                                 report("Stopped loading the %s view", view->name),
3744                         end_update(view, TRUE);
3745                 }
3746                 break;
3748         case REQ_SHOW_VERSION:
3749                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3750                 return TRUE;
3752         case REQ_SCREEN_REDRAW:
3753                 redraw_display(TRUE);
3754                 break;
3756         case REQ_EDIT:
3757                 report("Nothing to edit");
3758                 break;
3760         case REQ_ENTER:
3761                 report("Nothing to enter");
3762                 break;
3764         case REQ_VIEW_CLOSE:
3765                 /* XXX: Mark closed views by letting view->parent point to the
3766                  * view itself. Parents to closed view should never be
3767                  * followed. */
3768                 if (view->parent &&
3769                     view->parent->parent != view->parent) {
3770                         maximize_view(view->parent);
3771                         view->parent = view;
3772                         break;
3773                 }
3774                 /* Fall-through */
3775         case REQ_QUIT:
3776                 return FALSE;
3778         default:
3779                 report("Unknown key, press %s for help",
3780                        get_key(view->keymap, REQ_VIEW_HELP));
3781                 return TRUE;
3782         }
3784         return TRUE;
3788 /*
3789  * View backend utilities
3790  */
3792 enum sort_field {
3793         ORDERBY_NAME,
3794         ORDERBY_DATE,
3795         ORDERBY_AUTHOR,
3796 };
3798 struct sort_state {
3799         const enum sort_field *fields;
3800         size_t size, current;
3801         bool reverse;
3802 };
3804 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3805 #define get_sort_field(state) ((state).fields[(state).current])
3806 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3808 static void
3809 sort_view(struct view *view, enum request request, struct sort_state *state,
3810           int (*compare)(const void *, const void *))
3812         switch (request) {
3813         case REQ_TOGGLE_SORT_FIELD:
3814                 state->current = (state->current + 1) % state->size;
3815                 break;
3817         case REQ_TOGGLE_SORT_ORDER:
3818                 state->reverse = !state->reverse;
3819                 break;
3820         default:
3821                 die("Not a sort request");
3822         }
3824         qsort(view->line, view->lines, sizeof(*view->line), compare);
3825         redraw_view(view);
3828 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3830 /* Small author cache to reduce memory consumption. It uses binary
3831  * search to lookup or find place to position new entries. No entries
3832  * are ever freed. */
3833 static const char *
3834 get_author(const char *name)
3836         static const char **authors;
3837         static size_t authors_size;
3838         int from = 0, to = authors_size - 1;
3840         while (from <= to) {
3841                 size_t pos = (to + from) / 2;
3842                 int cmp = strcmp(name, authors[pos]);
3844                 if (!cmp)
3845                         return authors[pos];
3847                 if (cmp < 0)
3848                         to = pos - 1;
3849                 else
3850                         from = pos + 1;
3851         }
3853         if (!realloc_authors(&authors, authors_size, 1))
3854                 return NULL;
3855         name = strdup(name);
3856         if (!name)
3857                 return NULL;
3859         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3860         authors[from] = name;
3861         authors_size++;
3863         return name;
3866 static void
3867 parse_timesec(struct time *time, const char *sec)
3869         time->sec = (time_t) atol(sec);
3872 static void
3873 parse_timezone(struct time *time, const char *zone)
3875         long tz;
3877         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3878         tz += ('0' - zone[2]) * 60 * 60;
3879         tz += ('0' - zone[3]) * 60;
3880         tz += ('0' - zone[4]);
3882         if (zone[0] == '-')
3883                 tz = -tz;
3885         time->tz = tz;
3886         time->sec -= tz;
3889 /* Parse author lines where the name may be empty:
3890  *      author  <email@address.tld> 1138474660 +0100
3891  */
3892 static void
3893 parse_author_line(char *ident, const char **author, struct time *time)
3895         char *nameend = strchr(ident, '<');
3896         char *emailend = strchr(ident, '>');
3898         if (nameend && emailend)
3899                 *nameend = *emailend = 0;
3900         ident = chomp_string(ident);
3901         if (!*ident) {
3902                 if (nameend)
3903                         ident = chomp_string(nameend + 1);
3904                 if (!*ident)
3905                         ident = "Unknown";
3906         }
3908         *author = get_author(ident);
3910         /* Parse epoch and timezone */
3911         if (emailend && emailend[1] == ' ') {
3912                 char *secs = emailend + 2;
3913                 char *zone = strchr(secs, ' ');
3915                 parse_timesec(time, secs);
3917                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3918                         parse_timezone(time, zone + 1);
3919         }
3922 static bool
3923 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3925         char rev[SIZEOF_REV];
3926         const char *revlist_argv[] = {
3927                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3928         };
3929         struct menu_item *items;
3930         char text[SIZEOF_STR];
3931         bool ok = TRUE;
3932         int i;
3934         items = calloc(*parents + 1, sizeof(*items));
3935         if (!items)
3936                 return FALSE;
3938         for (i = 0; i < *parents; i++) {
3939                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3940                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3941                     !(items[i].text = strdup(text))) {
3942                         ok = FALSE;
3943                         break;
3944                 }
3945         }
3947         if (ok) {
3948                 *parents = 0;
3949                 ok = prompt_menu("Select parent", items, parents);
3950         }
3951         for (i = 0; items[i].text; i++)
3952                 free((char *) items[i].text);
3953         free(items);
3954         return ok;
3957 static bool
3958 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3960         char buf[SIZEOF_STR * 4];
3961         const char *revlist_argv[] = {
3962                 "git", "log", "--no-color", "-1",
3963                         "--pretty=format:%P", id, "--", path, NULL
3964         };
3965         int parents;
3967         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3968             (parents = strlen(buf) / 40) < 0) {
3969                 report("Failed to get parent information");
3970                 return FALSE;
3972         } else if (parents == 0) {
3973                 if (path)
3974                         report("Path '%s' does not exist in the parent", path);
3975                 else
3976                         report("The selected commit has no parents");
3977                 return FALSE;
3978         }
3980         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3981                 return FALSE;
3983         string_copy_rev(rev, &buf[41 * parents]);
3984         return TRUE;
3987 /*
3988  * Pager backend
3989  */
3991 static bool
3992 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3994         char text[SIZEOF_STR];
3996         if (opt_line_number && draw_lineno(view, lineno))
3997                 return TRUE;
3999         string_expand(text, sizeof(text), line->data, opt_tab_size);
4000         draw_text(view, line->type, text, TRUE);
4001         return TRUE;
4004 static bool
4005 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4007         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4008         char ref[SIZEOF_STR];
4010         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4011                 return TRUE;
4013         /* This is the only fatal call, since it can "corrupt" the buffer. */
4014         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4015                 return FALSE;
4017         return TRUE;
4020 static void
4021 add_pager_refs(struct view *view, struct line *line)
4023         char buf[SIZEOF_STR];
4024         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4025         struct ref_list *list;
4026         size_t bufpos = 0, i;
4027         const char *sep = "Refs: ";
4028         bool is_tag = FALSE;
4030         assert(line->type == LINE_COMMIT);
4032         list = get_ref_list(commit_id);
4033         if (!list) {
4034                 if (view == VIEW(REQ_VIEW_DIFF))
4035                         goto try_add_describe_ref;
4036                 return;
4037         }
4039         for (i = 0; i < list->size; i++) {
4040                 struct ref *ref = list->refs[i];
4041                 const char *fmt = ref->tag    ? "%s[%s]" :
4042                                   ref->remote ? "%s<%s>" : "%s%s";
4044                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4045                         return;
4046                 sep = ", ";
4047                 if (ref->tag)
4048                         is_tag = TRUE;
4049         }
4051         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4052 try_add_describe_ref:
4053                 /* Add <tag>-g<commit_id> "fake" reference. */
4054                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4055                         return;
4056         }
4058         if (bufpos == 0)
4059                 return;
4061         add_line_text(view, buf, LINE_PP_REFS);
4064 static bool
4065 pager_read(struct view *view, char *data)
4067         struct line *line;
4069         if (!data)
4070                 return TRUE;
4072         line = add_line_text(view, data, get_line_type(data));
4073         if (!line)
4074                 return FALSE;
4076         if (line->type == LINE_COMMIT &&
4077             (view == VIEW(REQ_VIEW_DIFF) ||
4078              view == VIEW(REQ_VIEW_LOG)))
4079                 add_pager_refs(view, line);
4081         return TRUE;
4084 static enum request
4085 pager_request(struct view *view, enum request request, struct line *line)
4087         int split = 0;
4089         if (request != REQ_ENTER)
4090                 return request;
4092         if (line->type == LINE_COMMIT &&
4093            (view == VIEW(REQ_VIEW_LOG) ||
4094             view == VIEW(REQ_VIEW_PAGER))) {
4095                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4096                 split = 1;
4097         }
4099         /* Always scroll the view even if it was split. That way
4100          * you can use Enter to scroll through the log view and
4101          * split open each commit diff. */
4102         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4104         /* FIXME: A minor workaround. Scrolling the view will call report("")
4105          * but if we are scrolling a non-current view this won't properly
4106          * update the view title. */
4107         if (split)
4108                 update_view_title(view);
4110         return REQ_NONE;
4113 static bool
4114 pager_grep(struct view *view, struct line *line)
4116         const char *text[] = { line->data, NULL };
4118         return grep_text(view, text);
4121 static void
4122 pager_select(struct view *view, struct line *line)
4124         if (line->type == LINE_COMMIT) {
4125                 char *text = (char *)line->data + STRING_SIZE("commit ");
4127                 if (view != VIEW(REQ_VIEW_PAGER))
4128                         string_copy_rev(view->ref, text);
4129                 string_copy_rev(ref_commit, text);
4130         }
4133 static struct view_ops pager_ops = {
4134         "line",
4135         NULL,
4136         NULL,
4137         pager_read,
4138         pager_draw,
4139         pager_request,
4140         pager_grep,
4141         pager_select,
4142 };
4144 static const char *log_argv[SIZEOF_ARG] = {
4145         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4146 };
4148 static enum request
4149 log_request(struct view *view, enum request request, struct line *line)
4151         switch (request) {
4152         case REQ_REFRESH:
4153                 load_refs();
4154                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4155                 return REQ_NONE;
4156         default:
4157                 return pager_request(view, request, line);
4158         }
4161 static struct view_ops log_ops = {
4162         "line",
4163         log_argv,
4164         NULL,
4165         pager_read,
4166         pager_draw,
4167         log_request,
4168         pager_grep,
4169         pager_select,
4170 };
4172 static const char *diff_argv[SIZEOF_ARG] = {
4173         "git", "show", "--pretty=fuller", "--no-color", "--root",
4174                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4175 };
4177 static struct view_ops diff_ops = {
4178         "line",
4179         diff_argv,
4180         NULL,
4181         pager_read,
4182         pager_draw,
4183         pager_request,
4184         pager_grep,
4185         pager_select,
4186 };
4188 /*
4189  * Help backend
4190  */
4192 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4194 static bool
4195 help_open_keymap_title(struct view *view, enum keymap keymap)
4197         struct line *line;
4199         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4200                                help_keymap_hidden[keymap] ? '+' : '-',
4201                                enum_name(keymap_table[keymap]));
4202         if (line)
4203                 line->other = keymap;
4205         return help_keymap_hidden[keymap];
4208 static void
4209 help_open_keymap(struct view *view, enum keymap keymap)
4211         const char *group = NULL;
4212         char buf[SIZEOF_STR];
4213         size_t bufpos;
4214         bool add_title = TRUE;
4215         int i;
4217         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4218                 const char *key = NULL;
4220                 if (req_info[i].request == REQ_NONE)
4221                         continue;
4223                 if (!req_info[i].request) {
4224                         group = req_info[i].help;
4225                         continue;
4226                 }
4228                 key = get_keys(keymap, req_info[i].request, TRUE);
4229                 if (!key || !*key)
4230                         continue;
4232                 if (add_title && help_open_keymap_title(view, keymap))
4233                         return;
4234                 add_title = FALSE;
4236                 if (group) {
4237                         add_line_text(view, group, LINE_HELP_GROUP);
4238                         group = NULL;
4239                 }
4241                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4242                                 enum_name(req_info[i]), req_info[i].help);
4243         }
4245         group = "External commands:";
4247         for (i = 0; i < run_requests; i++) {
4248                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4249                 const char *key;
4250                 int argc;
4252                 if (!req || req->keymap != keymap)
4253                         continue;
4255                 key = get_key_name(req->key);
4256                 if (!*key)
4257                         key = "(no key defined)";
4259                 if (add_title && help_open_keymap_title(view, keymap))
4260                         return;
4261                 if (group) {
4262                         add_line_text(view, group, LINE_HELP_GROUP);
4263                         group = NULL;
4264                 }
4266                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4267                         if (!string_format_from(buf, &bufpos, "%s%s",
4268                                                 argc ? " " : "", req->argv[argc]))
4269                                 return;
4271                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4272         }
4275 static bool
4276 help_open(struct view *view)
4278         enum keymap keymap;
4280         reset_view(view);
4281         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4282         add_line_text(view, "", LINE_DEFAULT);
4284         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4285                 help_open_keymap(view, keymap);
4287         return TRUE;
4290 static enum request
4291 help_request(struct view *view, enum request request, struct line *line)
4293         switch (request) {
4294         case REQ_ENTER:
4295                 if (line->type == LINE_HELP_KEYMAP) {
4296                         help_keymap_hidden[line->other] =
4297                                 !help_keymap_hidden[line->other];
4298                         view->p_restore = TRUE;
4299                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4300                 }
4302                 return REQ_NONE;
4303         default:
4304                 return pager_request(view, request, line);
4305         }
4308 static struct view_ops help_ops = {
4309         "line",
4310         NULL,
4311         help_open,
4312         NULL,
4313         pager_draw,
4314         help_request,
4315         pager_grep,
4316         pager_select,
4317 };
4320 /*
4321  * Tree backend
4322  */
4324 struct tree_stack_entry {
4325         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4326         unsigned long lineno;           /* Line number to restore */
4327         char *name;                     /* Position of name in opt_path */
4328 };
4330 /* The top of the path stack. */
4331 static struct tree_stack_entry *tree_stack = NULL;
4332 unsigned long tree_lineno = 0;
4334 static void
4335 pop_tree_stack_entry(void)
4337         struct tree_stack_entry *entry = tree_stack;
4339         tree_lineno = entry->lineno;
4340         entry->name[0] = 0;
4341         tree_stack = entry->prev;
4342         free(entry);
4345 static void
4346 push_tree_stack_entry(const char *name, unsigned long lineno)
4348         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4349         size_t pathlen = strlen(opt_path);
4351         if (!entry)
4352                 return;
4354         entry->prev = tree_stack;
4355         entry->name = opt_path + pathlen;
4356         tree_stack = entry;
4358         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4359                 pop_tree_stack_entry();
4360                 return;
4361         }
4363         /* Move the current line to the first tree entry. */
4364         tree_lineno = 1;
4365         entry->lineno = lineno;
4368 /* Parse output from git-ls-tree(1):
4369  *
4370  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4371  */
4373 #define SIZEOF_TREE_ATTR \
4374         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4376 #define SIZEOF_TREE_MODE \
4377         STRING_SIZE("100644 ")
4379 #define TREE_ID_OFFSET \
4380         STRING_SIZE("100644 blob ")
4382 struct tree_entry {
4383         char id[SIZEOF_REV];
4384         mode_t mode;
4385         struct time time;               /* Date from the author ident. */
4386         const char *author;             /* Author of the commit. */
4387         char name[1];
4388 };
4390 static const char *
4391 tree_path(const struct line *line)
4393         return ((struct tree_entry *) line->data)->name;
4396 static int
4397 tree_compare_entry(const struct line *line1, const struct line *line2)
4399         if (line1->type != line2->type)
4400                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4401         return strcmp(tree_path(line1), tree_path(line2));
4404 static const enum sort_field tree_sort_fields[] = {
4405         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4406 };
4407 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4409 static int
4410 tree_compare(const void *l1, const void *l2)
4412         const struct line *line1 = (const struct line *) l1;
4413         const struct line *line2 = (const struct line *) l2;
4414         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4415         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4417         if (line1->type == LINE_TREE_HEAD)
4418                 return -1;
4419         if (line2->type == LINE_TREE_HEAD)
4420                 return 1;
4422         switch (get_sort_field(tree_sort_state)) {
4423         case ORDERBY_DATE:
4424                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4426         case ORDERBY_AUTHOR:
4427                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4429         case ORDERBY_NAME:
4430         default:
4431                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4432         }
4436 static struct line *
4437 tree_entry(struct view *view, enum line_type type, const char *path,
4438            const char *mode, const char *id)
4440         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4441         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4443         if (!entry || !line) {
4444                 free(entry);
4445                 return NULL;
4446         }
4448         strncpy(entry->name, path, strlen(path));
4449         if (mode)
4450                 entry->mode = strtoul(mode, NULL, 8);
4451         if (id)
4452                 string_copy_rev(entry->id, id);
4454         return line;
4457 static bool
4458 tree_read_date(struct view *view, char *text, bool *read_date)
4460         static const char *author_name;
4461         static struct time author_time;
4463         if (!text && *read_date) {
4464                 *read_date = FALSE;
4465                 return TRUE;
4467         } else if (!text) {
4468                 char *path = *opt_path ? opt_path : ".";
4469                 /* Find next entry to process */
4470                 const char *log_file[] = {
4471                         "git", "log", "--no-color", "--pretty=raw",
4472                                 "--cc", "--raw", view->id, "--", path, NULL
4473                 };
4474                 struct io io = {};
4476                 if (!view->lines) {
4477                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4478                         report("Tree is empty");
4479                         return TRUE;
4480                 }
4482                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4483                         report("Failed to load tree data");
4484                         return TRUE;
4485                 }
4487                 io_done(view->pipe);
4488                 view->io = io;
4489                 *read_date = TRUE;
4490                 return FALSE;
4492         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4493                 parse_author_line(text + STRING_SIZE("author "),
4494                                   &author_name, &author_time);
4496         } else if (*text == ':') {
4497                 char *pos;
4498                 size_t annotated = 1;
4499                 size_t i;
4501                 pos = strchr(text, '\t');
4502                 if (!pos)
4503                         return TRUE;
4504                 text = pos + 1;
4505                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4506                         text += strlen(opt_path);
4507                 pos = strchr(text, '/');
4508                 if (pos)
4509                         *pos = 0;
4511                 for (i = 1; i < view->lines; i++) {
4512                         struct line *line = &view->line[i];
4513                         struct tree_entry *entry = line->data;
4515                         annotated += !!entry->author;
4516                         if (entry->author || strcmp(entry->name, text))
4517                                 continue;
4519                         entry->author = author_name;
4520                         entry->time = author_time;
4521                         line->dirty = 1;
4522                         break;
4523                 }
4525                 if (annotated == view->lines)
4526                         io_kill(view->pipe);
4527         }
4528         return TRUE;
4531 static bool
4532 tree_read(struct view *view, char *text)
4534         static bool read_date = FALSE;
4535         struct tree_entry *data;
4536         struct line *entry, *line;
4537         enum line_type type;
4538         size_t textlen = text ? strlen(text) : 0;
4539         char *path = text + SIZEOF_TREE_ATTR;
4541         if (read_date || !text)
4542                 return tree_read_date(view, text, &read_date);
4544         if (textlen <= SIZEOF_TREE_ATTR)
4545                 return FALSE;
4546         if (view->lines == 0 &&
4547             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4548                 return FALSE;
4550         /* Strip the path part ... */
4551         if (*opt_path) {
4552                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4553                 size_t striplen = strlen(opt_path);
4555                 if (pathlen > striplen)
4556                         memmove(path, path + striplen,
4557                                 pathlen - striplen + 1);
4559                 /* Insert "link" to parent directory. */
4560                 if (view->lines == 1 &&
4561                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4562                         return FALSE;
4563         }
4565         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4566         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4567         if (!entry)
4568                 return FALSE;
4569         data = entry->data;
4571         /* Skip "Directory ..." and ".." line. */
4572         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4573                 if (tree_compare_entry(line, entry) <= 0)
4574                         continue;
4576                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4578                 line->data = data;
4579                 line->type = type;
4580                 for (; line <= entry; line++)
4581                         line->dirty = line->cleareol = 1;
4582                 return TRUE;
4583         }
4585         if (tree_lineno > view->lineno) {
4586                 view->lineno = tree_lineno;
4587                 tree_lineno = 0;
4588         }
4590         return TRUE;
4593 static bool
4594 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4596         struct tree_entry *entry = line->data;
4598         if (line->type == LINE_TREE_HEAD) {
4599                 if (draw_text(view, line->type, "Directory path /", TRUE))
4600                         return TRUE;
4601         } else {
4602                 if (draw_mode(view, entry->mode))
4603                         return TRUE;
4605                 if (opt_author && draw_author(view, entry->author))
4606                         return TRUE;
4608                 if (opt_date && draw_date(view, &entry->time))
4609                         return TRUE;
4610         }
4611         if (draw_text(view, line->type, entry->name, TRUE))
4612                 return TRUE;
4613         return TRUE;
4616 static void
4617 open_blob_editor()
4619         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4620         int fd = mkstemp(file);
4622         if (fd == -1)
4623                 report("Failed to create temporary file");
4624         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4625                 report("Failed to save blob data to file");
4626         else
4627                 open_editor(file);
4628         if (fd != -1)
4629                 unlink(file);
4632 static enum request
4633 tree_request(struct view *view, enum request request, struct line *line)
4635         enum open_flags flags;
4637         switch (request) {
4638         case REQ_VIEW_BLAME:
4639                 if (line->type != LINE_TREE_FILE) {
4640                         report("Blame only supported for files");
4641                         return REQ_NONE;
4642                 }
4644                 string_copy(opt_ref, view->vid);
4645                 return request;
4647         case REQ_EDIT:
4648                 if (line->type != LINE_TREE_FILE) {
4649                         report("Edit only supported for files");
4650                 } else if (!is_head_commit(view->vid)) {
4651                         open_blob_editor();
4652                 } else {
4653                         open_editor(opt_file);
4654                 }
4655                 return REQ_NONE;
4657         case REQ_TOGGLE_SORT_FIELD:
4658         case REQ_TOGGLE_SORT_ORDER:
4659                 sort_view(view, request, &tree_sort_state, tree_compare);
4660                 return REQ_NONE;
4662         case REQ_PARENT:
4663                 if (!*opt_path) {
4664                         /* quit view if at top of tree */
4665                         return REQ_VIEW_CLOSE;
4666                 }
4667                 /* fake 'cd  ..' */
4668                 line = &view->line[1];
4669                 break;
4671         case REQ_ENTER:
4672                 break;
4674         default:
4675                 return request;
4676         }
4678         /* Cleanup the stack if the tree view is at a different tree. */
4679         while (!*opt_path && tree_stack)
4680                 pop_tree_stack_entry();
4682         switch (line->type) {
4683         case LINE_TREE_DIR:
4684                 /* Depending on whether it is a subdirectory or parent link
4685                  * mangle the path buffer. */
4686                 if (line == &view->line[1] && *opt_path) {
4687                         pop_tree_stack_entry();
4689                 } else {
4690                         const char *basename = tree_path(line);
4692                         push_tree_stack_entry(basename, view->lineno);
4693                 }
4695                 /* Trees and subtrees share the same ID, so they are not not
4696                  * unique like blobs. */
4697                 flags = OPEN_RELOAD;
4698                 request = REQ_VIEW_TREE;
4699                 break;
4701         case LINE_TREE_FILE:
4702                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4703                 request = REQ_VIEW_BLOB;
4704                 break;
4706         default:
4707                 return REQ_NONE;
4708         }
4710         open_view(view, request, flags);
4711         if (request == REQ_VIEW_TREE)
4712                 view->lineno = tree_lineno;
4714         return REQ_NONE;
4717 static bool
4718 tree_grep(struct view *view, struct line *line)
4720         struct tree_entry *entry = line->data;
4721         const char *text[] = {
4722                 entry->name,
4723                 opt_author ? entry->author : "",
4724                 mkdate(&entry->time, opt_date),
4725                 NULL
4726         };
4728         return grep_text(view, text);
4731 static void
4732 tree_select(struct view *view, struct line *line)
4734         struct tree_entry *entry = line->data;
4736         if (line->type == LINE_TREE_FILE) {
4737                 string_copy_rev(ref_blob, entry->id);
4738                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4740         } else if (line->type != LINE_TREE_DIR) {
4741                 return;
4742         }
4744         string_copy_rev(view->ref, entry->id);
4747 static bool
4748 tree_prepare(struct view *view)
4750         if (view->lines == 0 && opt_prefix[0]) {
4751                 char *pos = opt_prefix;
4753                 while (pos && *pos) {
4754                         char *end = strchr(pos, '/');
4756                         if (end)
4757                                 *end = 0;
4758                         push_tree_stack_entry(pos, 0);
4759                         pos = end;
4760                         if (end) {
4761                                 *end = '/';
4762                                 pos++;
4763                         }
4764                 }
4766         } else if (strcmp(view->vid, view->id)) {
4767                 opt_path[0] = 0;
4768         }
4770         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4773 static const char *tree_argv[SIZEOF_ARG] = {
4774         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4775 };
4777 static struct view_ops tree_ops = {
4778         "file",
4779         tree_argv,
4780         NULL,
4781         tree_read,
4782         tree_draw,
4783         tree_request,
4784         tree_grep,
4785         tree_select,
4786         tree_prepare,
4787 };
4789 static bool
4790 blob_read(struct view *view, char *line)
4792         if (!line)
4793                 return TRUE;
4794         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4797 static enum request
4798 blob_request(struct view *view, enum request request, struct line *line)
4800         switch (request) {
4801         case REQ_EDIT:
4802                 open_blob_editor();
4803                 return REQ_NONE;
4804         default:
4805                 return pager_request(view, request, line);
4806         }
4809 static const char *blob_argv[SIZEOF_ARG] = {
4810         "git", "cat-file", "blob", "%(blob)", NULL
4811 };
4813 static struct view_ops blob_ops = {
4814         "line",
4815         blob_argv,
4816         NULL,
4817         blob_read,
4818         pager_draw,
4819         blob_request,
4820         pager_grep,
4821         pager_select,
4822 };
4824 /*
4825  * Blame backend
4826  *
4827  * Loading the blame view is a two phase job:
4828  *
4829  *  1. File content is read either using opt_file from the
4830  *     filesystem or using git-cat-file.
4831  *  2. Then blame information is incrementally added by
4832  *     reading output from git-blame.
4833  */
4835 static const char *blame_head_argv[] = {
4836         "git", "blame", "--incremental", "--", "%(file)", NULL
4837 };
4839 static const char *blame_ref_argv[] = {
4840         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4841 };
4843 static const char *blame_cat_file_argv[] = {
4844         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4845 };
4847 struct blame_commit {
4848         char id[SIZEOF_REV];            /* SHA1 ID. */
4849         char title[128];                /* First line of the commit message. */
4850         const char *author;             /* Author of the commit. */
4851         struct time time;               /* Date from the author ident. */
4852         char filename[128];             /* Name of file. */
4853         bool has_previous;              /* Was a "previous" line detected. */
4854 };
4856 struct blame {
4857         struct blame_commit *commit;
4858         unsigned long lineno;
4859         char text[1];
4860 };
4862 static bool
4863 blame_open(struct view *view)
4865         char path[SIZEOF_STR];
4867         if (!view->parent && *opt_prefix) {
4868                 string_copy(path, opt_file);
4869                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4870                         return FALSE;
4871         }
4873         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4874                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4875                         return FALSE;
4876         }
4878         setup_update(view, opt_file);
4879         string_format(view->ref, "%s ...", opt_file);
4881         return TRUE;
4884 static struct blame_commit *
4885 get_blame_commit(struct view *view, const char *id)
4887         size_t i;
4889         for (i = 0; i < view->lines; i++) {
4890                 struct blame *blame = view->line[i].data;
4892                 if (!blame->commit)
4893                         continue;
4895                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4896                         return blame->commit;
4897         }
4899         {
4900                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4902                 if (commit)
4903                         string_ncopy(commit->id, id, SIZEOF_REV);
4904                 return commit;
4905         }
4908 static bool
4909 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4911         const char *pos = *posref;
4913         *posref = NULL;
4914         pos = strchr(pos + 1, ' ');
4915         if (!pos || !isdigit(pos[1]))
4916                 return FALSE;
4917         *number = atoi(pos + 1);
4918         if (*number < min || *number > max)
4919                 return FALSE;
4921         *posref = pos;
4922         return TRUE;
4925 static struct blame_commit *
4926 parse_blame_commit(struct view *view, const char *text, int *blamed)
4928         struct blame_commit *commit;
4929         struct blame *blame;
4930         const char *pos = text + SIZEOF_REV - 2;
4931         size_t orig_lineno = 0;
4932         size_t lineno;
4933         size_t group;
4935         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4936                 return NULL;
4938         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4939             !parse_number(&pos, &lineno, 1, view->lines) ||
4940             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4941                 return NULL;
4943         commit = get_blame_commit(view, text);
4944         if (!commit)
4945                 return NULL;
4947         *blamed += group;
4948         while (group--) {
4949                 struct line *line = &view->line[lineno + group - 1];
4951                 blame = line->data;
4952                 blame->commit = commit;
4953                 blame->lineno = orig_lineno + group - 1;
4954                 line->dirty = 1;
4955         }
4957         return commit;
4960 static bool
4961 blame_read_file(struct view *view, const char *line, bool *read_file)
4963         if (!line) {
4964                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4965                 struct io io = {};
4967                 if (view->lines == 0 && !view->parent)
4968                         die("No blame exist for %s", view->vid);
4970                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4971                         report("Failed to load blame data");
4972                         return TRUE;
4973                 }
4975                 io_done(view->pipe);
4976                 view->io = io;
4977                 *read_file = FALSE;
4978                 return FALSE;
4980         } else {
4981                 size_t linelen = strlen(line);
4982                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4984                 if (!blame)
4985                         return FALSE;
4987                 blame->commit = NULL;
4988                 strncpy(blame->text, line, linelen);
4989                 blame->text[linelen] = 0;
4990                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4991         }
4994 static bool
4995 match_blame_header(const char *name, char **line)
4997         size_t namelen = strlen(name);
4998         bool matched = !strncmp(name, *line, namelen);
5000         if (matched)
5001                 *line += namelen;
5003         return matched;
5006 static bool
5007 blame_read(struct view *view, char *line)
5009         static struct blame_commit *commit = NULL;
5010         static int blamed = 0;
5011         static bool read_file = TRUE;
5013         if (read_file)
5014                 return blame_read_file(view, line, &read_file);
5016         if (!line) {
5017                 /* Reset all! */
5018                 commit = NULL;
5019                 blamed = 0;
5020                 read_file = TRUE;
5021                 string_format(view->ref, "%s", view->vid);
5022                 if (view_is_displayed(view)) {
5023                         update_view_title(view);
5024                         redraw_view_from(view, 0);
5025                 }
5026                 return TRUE;
5027         }
5029         if (!commit) {
5030                 commit = parse_blame_commit(view, line, &blamed);
5031                 string_format(view->ref, "%s %2d%%", view->vid,
5032                               view->lines ? blamed * 100 / view->lines : 0);
5034         } else if (match_blame_header("author ", &line)) {
5035                 commit->author = get_author(line);
5037         } else if (match_blame_header("author-time ", &line)) {
5038                 parse_timesec(&commit->time, line);
5040         } else if (match_blame_header("author-tz ", &line)) {
5041                 parse_timezone(&commit->time, line);
5043         } else if (match_blame_header("summary ", &line)) {
5044                 string_ncopy(commit->title, line, strlen(line));
5046         } else if (match_blame_header("previous ", &line)) {
5047                 commit->has_previous = TRUE;
5049         } else if (match_blame_header("filename ", &line)) {
5050                 string_ncopy(commit->filename, line, strlen(line));
5051                 commit = NULL;
5052         }
5054         return TRUE;
5057 static bool
5058 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5060         struct blame *blame = line->data;
5061         struct time *time = NULL;
5062         const char *id = NULL, *author = NULL;
5063         char text[SIZEOF_STR];
5065         if (blame->commit && *blame->commit->filename) {
5066                 id = blame->commit->id;
5067                 author = blame->commit->author;
5068                 time = &blame->commit->time;
5069         }
5071         if (opt_date && draw_date(view, time))
5072                 return TRUE;
5074         if (opt_author && draw_author(view, author))
5075                 return TRUE;
5077         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5078                 return TRUE;
5080         if (draw_lineno(view, lineno))
5081                 return TRUE;
5083         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5084         draw_text(view, LINE_DEFAULT, text, TRUE);
5085         return TRUE;
5088 static bool
5089 check_blame_commit(struct blame *blame, bool check_null_id)
5091         if (!blame->commit)
5092                 report("Commit data not loaded yet");
5093         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5094                 report("No commit exist for the selected line");
5095         else
5096                 return TRUE;
5097         return FALSE;
5100 static void
5101 setup_blame_parent_line(struct view *view, struct blame *blame)
5103         const char *diff_tree_argv[] = {
5104                 "git", "diff-tree", "-U0", blame->commit->id,
5105                         "--", blame->commit->filename, NULL
5106         };
5107         struct io io = {};
5108         int parent_lineno = -1;
5109         int blamed_lineno = -1;
5110         char *line;
5112         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5113                 return;
5115         while ((line = io_get(&io, '\n', TRUE))) {
5116                 if (*line == '@') {
5117                         char *pos = strchr(line, '+');
5119                         parent_lineno = atoi(line + 4);
5120                         if (pos)
5121                                 blamed_lineno = atoi(pos + 1);
5123                 } else if (*line == '+' && parent_lineno != -1) {
5124                         if (blame->lineno == blamed_lineno - 1 &&
5125                             !strcmp(blame->text, line + 1)) {
5126                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5127                                 break;
5128                         }
5129                         blamed_lineno++;
5130                 }
5131         }
5133         io_done(&io);
5136 static enum request
5137 blame_request(struct view *view, enum request request, struct line *line)
5139         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5140         struct blame *blame = line->data;
5142         switch (request) {
5143         case REQ_VIEW_BLAME:
5144                 if (check_blame_commit(blame, TRUE)) {
5145                         string_copy(opt_ref, blame->commit->id);
5146                         string_copy(opt_file, blame->commit->filename);
5147                         if (blame->lineno)
5148                                 view->lineno = blame->lineno;
5149                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5150                 }
5151                 break;
5153         case REQ_PARENT:
5154                 if (check_blame_commit(blame, TRUE) &&
5155                     select_commit_parent(blame->commit->id, opt_ref,
5156                                          blame->commit->filename)) {
5157                         string_copy(opt_file, blame->commit->filename);
5158                         setup_blame_parent_line(view, blame);
5159                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5160                 }
5161                 break;
5163         case REQ_ENTER:
5164                 if (!check_blame_commit(blame, FALSE))
5165                         break;
5167                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5168                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5169                         break;
5171                 if (!strcmp(blame->commit->id, NULL_ID)) {
5172                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5173                         const char *diff_index_argv[] = {
5174                                 "git", "diff-index", "--root", "--patch-with-stat",
5175                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5176                         };
5178                         if (!blame->commit->has_previous) {
5179                                 diff_index_argv[1] = "diff";
5180                                 diff_index_argv[2] = "--no-color";
5181                                 diff_index_argv[6] = "--";
5182                                 diff_index_argv[7] = "/dev/null";
5183                         }
5185                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5186                                 report("Failed to allocate diff command");
5187                                 break;
5188                         }
5189                         flags |= OPEN_PREPARED;
5190                 }
5192                 open_view(view, REQ_VIEW_DIFF, flags);
5193                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5194                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5195                 break;
5197         default:
5198                 return request;
5199         }
5201         return REQ_NONE;
5204 static bool
5205 blame_grep(struct view *view, struct line *line)
5207         struct blame *blame = line->data;
5208         struct blame_commit *commit = blame->commit;
5209         const char *text[] = {
5210                 blame->text,
5211                 commit ? commit->title : "",
5212                 commit ? commit->id : "",
5213                 commit && opt_author ? commit->author : "",
5214                 commit ? mkdate(&commit->time, opt_date) : "",
5215                 NULL
5216         };
5218         return grep_text(view, text);
5221 static void
5222 blame_select(struct view *view, struct line *line)
5224         struct blame *blame = line->data;
5225         struct blame_commit *commit = blame->commit;
5227         if (!commit)
5228                 return;
5230         if (!strcmp(commit->id, NULL_ID))
5231                 string_ncopy(ref_commit, "HEAD", 4);
5232         else
5233                 string_copy_rev(ref_commit, commit->id);
5236 static struct view_ops blame_ops = {
5237         "line",
5238         NULL,
5239         blame_open,
5240         blame_read,
5241         blame_draw,
5242         blame_request,
5243         blame_grep,
5244         blame_select,
5245 };
5247 /*
5248  * Branch backend
5249  */
5251 struct branch {
5252         const char *author;             /* Author of the last commit. */
5253         struct time time;               /* Date of the last activity. */
5254         const struct ref *ref;          /* Name and commit ID information. */
5255 };
5257 static const struct ref branch_all;
5259 static const enum sort_field branch_sort_fields[] = {
5260         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5261 };
5262 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5264 static int
5265 branch_compare(const void *l1, const void *l2)
5267         const struct branch *branch1 = ((const struct line *) l1)->data;
5268         const struct branch *branch2 = ((const struct line *) l2)->data;
5270         switch (get_sort_field(branch_sort_state)) {
5271         case ORDERBY_DATE:
5272                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5274         case ORDERBY_AUTHOR:
5275                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5277         case ORDERBY_NAME:
5278         default:
5279                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5280         }
5283 static bool
5284 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5286         struct branch *branch = line->data;
5287         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5289         if (opt_date && draw_date(view, &branch->time))
5290                 return TRUE;
5292         if (opt_author && draw_author(view, branch->author))
5293                 return TRUE;
5295         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5296         return TRUE;
5299 static enum request
5300 branch_request(struct view *view, enum request request, struct line *line)
5302         struct branch *branch = line->data;
5304         switch (request) {
5305         case REQ_REFRESH:
5306                 load_refs();
5307                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5308                 return REQ_NONE;
5310         case REQ_TOGGLE_SORT_FIELD:
5311         case REQ_TOGGLE_SORT_ORDER:
5312                 sort_view(view, request, &branch_sort_state, branch_compare);
5313                 return REQ_NONE;
5315         case REQ_ENTER:
5316                 if (branch->ref == &branch_all) {
5317                         const char *all_branches_argv[] = {
5318                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5319                                       "--topo-order", "--all", NULL
5320                         };
5321                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5323                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5324                                 report("Failed to load view of all branches");
5325                                 return REQ_NONE;
5326                         }
5327                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5328                 } else {
5329                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5330                 }
5331                 return REQ_NONE;
5333         default:
5334                 return request;
5335         }
5338 static bool
5339 branch_read(struct view *view, char *line)
5341         static char id[SIZEOF_REV];
5342         struct branch *reference;
5343         size_t i;
5345         if (!line)
5346                 return TRUE;
5348         switch (get_line_type(line)) {
5349         case LINE_COMMIT:
5350                 string_copy_rev(id, line + STRING_SIZE("commit "));
5351                 return TRUE;
5353         case LINE_AUTHOR:
5354                 for (i = 0, reference = NULL; i < view->lines; i++) {
5355                         struct branch *branch = view->line[i].data;
5357                         if (strcmp(branch->ref->id, id))
5358                                 continue;
5360                         view->line[i].dirty = TRUE;
5361                         if (reference) {
5362                                 branch->author = reference->author;
5363                                 branch->time = reference->time;
5364                                 continue;
5365                         }
5367                         parse_author_line(line + STRING_SIZE("author "),
5368                                           &branch->author, &branch->time);
5369                         reference = branch;
5370                 }
5371                 return TRUE;
5373         default:
5374                 return TRUE;
5375         }
5379 static bool
5380 branch_open_visitor(void *data, const struct ref *ref)
5382         struct view *view = data;
5383         struct branch *branch;
5385         if (ref->tag || ref->ltag || ref->remote)
5386                 return TRUE;
5388         branch = calloc(1, sizeof(*branch));
5389         if (!branch)
5390                 return FALSE;
5392         branch->ref = ref;
5393         return !!add_line_data(view, branch, LINE_DEFAULT);
5396 static bool
5397 branch_open(struct view *view)
5399         const char *branch_log[] = {
5400                 "git", "log", "--no-color", "--pretty=raw",
5401                         "--simplify-by-decoration", "--all", NULL
5402         };
5404         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5405                 report("Failed to load branch data");
5406                 return TRUE;
5407         }
5409         setup_update(view, view->id);
5410         branch_open_visitor(view, &branch_all);
5411         foreach_ref(branch_open_visitor, view);
5412         view->p_restore = TRUE;
5414         return TRUE;
5417 static bool
5418 branch_grep(struct view *view, struct line *line)
5420         struct branch *branch = line->data;
5421         const char *text[] = {
5422                 branch->ref->name,
5423                 branch->author,
5424                 NULL
5425         };
5427         return grep_text(view, text);
5430 static void
5431 branch_select(struct view *view, struct line *line)
5433         struct branch *branch = line->data;
5435         string_copy_rev(view->ref, branch->ref->id);
5436         string_copy_rev(ref_commit, branch->ref->id);
5437         string_copy_rev(ref_head, branch->ref->id);
5438         string_copy_rev(ref_branch, branch->ref->name);
5441 static struct view_ops branch_ops = {
5442         "branch",
5443         NULL,
5444         branch_open,
5445         branch_read,
5446         branch_draw,
5447         branch_request,
5448         branch_grep,
5449         branch_select,
5450 };
5452 /*
5453  * Status backend
5454  */
5456 struct status {
5457         char status;
5458         struct {
5459                 mode_t mode;
5460                 char rev[SIZEOF_REV];
5461                 char name[SIZEOF_STR];
5462         } old;
5463         struct {
5464                 mode_t mode;
5465                 char rev[SIZEOF_REV];
5466                 char name[SIZEOF_STR];
5467         } new;
5468 };
5470 static char status_onbranch[SIZEOF_STR];
5471 static struct status stage_status;
5472 static enum line_type stage_line_type;
5473 static size_t stage_chunks;
5474 static int *stage_chunk;
5476 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5478 /* This should work even for the "On branch" line. */
5479 static inline bool
5480 status_has_none(struct view *view, struct line *line)
5482         return line < view->line + view->lines && !line[1].data;
5485 /* Get fields from the diff line:
5486  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5487  */
5488 static inline bool
5489 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5491         const char *old_mode = buf +  1;
5492         const char *new_mode = buf +  8;
5493         const char *old_rev  = buf + 15;
5494         const char *new_rev  = buf + 56;
5495         const char *status   = buf + 97;
5497         if (bufsize < 98 ||
5498             old_mode[-1] != ':' ||
5499             new_mode[-1] != ' ' ||
5500             old_rev[-1]  != ' ' ||
5501             new_rev[-1]  != ' ' ||
5502             status[-1]   != ' ')
5503                 return FALSE;
5505         file->status = *status;
5507         string_copy_rev(file->old.rev, old_rev);
5508         string_copy_rev(file->new.rev, new_rev);
5510         file->old.mode = strtoul(old_mode, NULL, 8);
5511         file->new.mode = strtoul(new_mode, NULL, 8);
5513         file->old.name[0] = file->new.name[0] = 0;
5515         return TRUE;
5518 static bool
5519 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5521         struct status *unmerged = NULL;
5522         char *buf;
5523         struct io io = {};
5525         if (!io_run(&io, argv, opt_cdup, IO_RD))
5526                 return FALSE;
5528         add_line_data(view, NULL, type);
5530         while ((buf = io_get(&io, 0, TRUE))) {
5531                 struct status *file = unmerged;
5533                 if (!file) {
5534                         file = calloc(1, sizeof(*file));
5535                         if (!file || !add_line_data(view, file, type))
5536                                 goto error_out;
5537                 }
5539                 /* Parse diff info part. */
5540                 if (status) {
5541                         file->status = status;
5542                         if (status == 'A')
5543                                 string_copy(file->old.rev, NULL_ID);
5545                 } else if (!file->status || file == unmerged) {
5546                         if (!status_get_diff(file, buf, strlen(buf)))
5547                                 goto error_out;
5549                         buf = io_get(&io, 0, TRUE);
5550                         if (!buf)
5551                                 break;
5553                         /* Collapse all modified entries that follow an
5554                          * associated unmerged entry. */
5555                         if (unmerged == file) {
5556                                 unmerged->status = 'U';
5557                                 unmerged = NULL;
5558                         } else if (file->status == 'U') {
5559                                 unmerged = file;
5560                         }
5561                 }
5563                 /* Grab the old name for rename/copy. */
5564                 if (!*file->old.name &&
5565                     (file->status == 'R' || file->status == 'C')) {
5566                         string_ncopy(file->old.name, buf, strlen(buf));
5568                         buf = io_get(&io, 0, TRUE);
5569                         if (!buf)
5570                                 break;
5571                 }
5573                 /* git-ls-files just delivers a NUL separated list of
5574                  * file names similar to the second half of the
5575                  * git-diff-* output. */
5576                 string_ncopy(file->new.name, buf, strlen(buf));
5577                 if (!*file->old.name)
5578                         string_copy(file->old.name, file->new.name);
5579                 file = NULL;
5580         }
5582         if (io_error(&io)) {
5583 error_out:
5584                 io_done(&io);
5585                 return FALSE;
5586         }
5588         if (!view->line[view->lines - 1].data)
5589                 add_line_data(view, NULL, LINE_STAT_NONE);
5591         io_done(&io);
5592         return TRUE;
5595 /* Don't show unmerged entries in the staged section. */
5596 static const char *status_diff_index_argv[] = {
5597         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5598                              "--cached", "-M", "HEAD", NULL
5599 };
5601 static const char *status_diff_files_argv[] = {
5602         "git", "diff-files", "-z", NULL
5603 };
5605 static const char *status_list_other_argv[] = {
5606         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5607 };
5609 static const char *status_list_no_head_argv[] = {
5610         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5611 };
5613 static const char *update_index_argv[] = {
5614         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5615 };
5617 /* Restore the previous line number to stay in the context or select a
5618  * line with something that can be updated. */
5619 static void
5620 status_restore(struct view *view)
5622         if (view->p_lineno >= view->lines)
5623                 view->p_lineno = view->lines - 1;
5624         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5625                 view->p_lineno++;
5626         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5627                 view->p_lineno--;
5629         /* If the above fails, always skip the "On branch" line. */
5630         if (view->p_lineno < view->lines)
5631                 view->lineno = view->p_lineno;
5632         else
5633                 view->lineno = 1;
5635         if (view->lineno < view->offset)
5636                 view->offset = view->lineno;
5637         else if (view->offset + view->height <= view->lineno)
5638                 view->offset = view->lineno - view->height + 1;
5640         view->p_restore = FALSE;
5643 static void
5644 status_update_onbranch(void)
5646         static const char *paths[][2] = {
5647                 { "rebase-apply/rebasing",      "Rebasing" },
5648                 { "rebase-apply/applying",      "Applying mailbox" },
5649                 { "rebase-apply/",              "Rebasing mailbox" },
5650                 { "rebase-merge/interactive",   "Interactive rebase" },
5651                 { "rebase-merge/",              "Rebase merge" },
5652                 { "MERGE_HEAD",                 "Merging" },
5653                 { "BISECT_LOG",                 "Bisecting" },
5654                 { "HEAD",                       "On branch" },
5655         };
5656         char buf[SIZEOF_STR];
5657         struct stat stat;
5658         int i;
5660         if (is_initial_commit()) {
5661                 string_copy(status_onbranch, "Initial commit");
5662                 return;
5663         }
5665         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5666                 char *head = opt_head;
5668                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5669                     lstat(buf, &stat) < 0)
5670                         continue;
5672                 if (!*opt_head) {
5673                         struct io io = {};
5675                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5676                             io_read_buf(&io, buf, sizeof(buf))) {
5677                                 head = buf;
5678                                 if (!prefixcmp(head, "refs/heads/"))
5679                                         head += STRING_SIZE("refs/heads/");
5680                         }
5681                 }
5683                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5684                         string_copy(status_onbranch, opt_head);
5685                 return;
5686         }
5688         string_copy(status_onbranch, "Not currently on any branch");
5691 /* First parse staged info using git-diff-index(1), then parse unstaged
5692  * info using git-diff-files(1), and finally untracked files using
5693  * git-ls-files(1). */
5694 static bool
5695 status_open(struct view *view)
5697         reset_view(view);
5699         add_line_data(view, NULL, LINE_STAT_HEAD);
5700         status_update_onbranch();
5702         io_run_bg(update_index_argv);
5704         if (is_initial_commit()) {
5705                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5706                         return FALSE;
5707         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5708                 return FALSE;
5709         }
5711         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5712             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5713                 return FALSE;
5715         /* Restore the exact position or use the specialized restore
5716          * mode? */
5717         if (!view->p_restore)
5718                 status_restore(view);
5719         return TRUE;
5722 static bool
5723 status_draw(struct view *view, struct line *line, unsigned int lineno)
5725         struct status *status = line->data;
5726         enum line_type type;
5727         const char *text;
5729         if (!status) {
5730                 switch (line->type) {
5731                 case LINE_STAT_STAGED:
5732                         type = LINE_STAT_SECTION;
5733                         text = "Changes to be committed:";
5734                         break;
5736                 case LINE_STAT_UNSTAGED:
5737                         type = LINE_STAT_SECTION;
5738                         text = "Changed but not updated:";
5739                         break;
5741                 case LINE_STAT_UNTRACKED:
5742                         type = LINE_STAT_SECTION;
5743                         text = "Untracked files:";
5744                         break;
5746                 case LINE_STAT_NONE:
5747                         type = LINE_DEFAULT;
5748                         text = "  (no files)";
5749                         break;
5751                 case LINE_STAT_HEAD:
5752                         type = LINE_STAT_HEAD;
5753                         text = status_onbranch;
5754                         break;
5756                 default:
5757                         return FALSE;
5758                 }
5759         } else {
5760                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5762                 buf[0] = status->status;
5763                 if (draw_text(view, line->type, buf, TRUE))
5764                         return TRUE;
5765                 type = LINE_DEFAULT;
5766                 text = status->new.name;
5767         }
5769         draw_text(view, type, text, TRUE);
5770         return TRUE;
5773 static enum request
5774 status_load_error(struct view *view, struct view *stage, const char *path)
5776         if (displayed_views() == 2 || display[current_view] != view)
5777                 maximize_view(view);
5778         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5779         return REQ_NONE;
5782 static enum request
5783 status_enter(struct view *view, struct line *line)
5785         struct status *status = line->data;
5786         const char *oldpath = status ? status->old.name : NULL;
5787         /* Diffs for unmerged entries are empty when passing the new
5788          * path, so leave it empty. */
5789         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5790         const char *info;
5791         enum open_flags split;
5792         struct view *stage = VIEW(REQ_VIEW_STAGE);
5794         if (line->type == LINE_STAT_NONE ||
5795             (!status && line[1].type == LINE_STAT_NONE)) {
5796                 report("No file to diff");
5797                 return REQ_NONE;
5798         }
5800         switch (line->type) {
5801         case LINE_STAT_STAGED:
5802                 if (is_initial_commit()) {
5803                         const char *no_head_diff_argv[] = {
5804                                 "git", "diff", "--no-color", "--patch-with-stat",
5805                                         "--", "/dev/null", newpath, NULL
5806                         };
5808                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5809                                 return status_load_error(view, stage, newpath);
5810                 } else {
5811                         const char *index_show_argv[] = {
5812                                 "git", "diff-index", "--root", "--patch-with-stat",
5813                                         "-C", "-M", "--cached", "HEAD", "--",
5814                                         oldpath, newpath, NULL
5815                         };
5817                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5818                                 return status_load_error(view, stage, newpath);
5819                 }
5821                 if (status)
5822                         info = "Staged changes to %s";
5823                 else
5824                         info = "Staged changes";
5825                 break;
5827         case LINE_STAT_UNSTAGED:
5828         {
5829                 const char *files_show_argv[] = {
5830                         "git", "diff-files", "--root", "--patch-with-stat",
5831                                 "-C", "-M", "--", oldpath, newpath, NULL
5832                 };
5834                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5835                         return status_load_error(view, stage, newpath);
5836                 if (status)
5837                         info = "Unstaged changes to %s";
5838                 else
5839                         info = "Unstaged changes";
5840                 break;
5841         }
5842         case LINE_STAT_UNTRACKED:
5843                 if (!newpath) {
5844                         report("No file to show");
5845                         return REQ_NONE;
5846                 }
5848                 if (!suffixcmp(status->new.name, -1, "/")) {
5849                         report("Cannot display a directory");
5850                         return REQ_NONE;
5851                 }
5853                 if (!prepare_update_file(stage, newpath))
5854                         return status_load_error(view, stage, newpath);
5855                 info = "Untracked file %s";
5856                 break;
5858         case LINE_STAT_HEAD:
5859                 return REQ_NONE;
5861         default:
5862                 die("line type %d not handled in switch", line->type);
5863         }
5865         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5866         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5867         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5868                 if (status) {
5869                         stage_status = *status;
5870                 } else {
5871                         memset(&stage_status, 0, sizeof(stage_status));
5872                 }
5874                 stage_line_type = line->type;
5875                 stage_chunks = 0;
5876                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5877         }
5879         return REQ_NONE;
5882 static bool
5883 status_exists(struct status *status, enum line_type type)
5885         struct view *view = VIEW(REQ_VIEW_STATUS);
5886         unsigned long lineno;
5888         for (lineno = 0; lineno < view->lines; lineno++) {
5889                 struct line *line = &view->line[lineno];
5890                 struct status *pos = line->data;
5892                 if (line->type != type)
5893                         continue;
5894                 if (!pos && (!status || !status->status) && line[1].data) {
5895                         select_view_line(view, lineno);
5896                         return TRUE;
5897                 }
5898                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5899                         select_view_line(view, lineno);
5900                         return TRUE;
5901                 }
5902         }
5904         return FALSE;
5908 static bool
5909 status_update_prepare(struct io *io, enum line_type type)
5911         const char *staged_argv[] = {
5912                 "git", "update-index", "-z", "--index-info", NULL
5913         };
5914         const char *others_argv[] = {
5915                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5916         };
5918         switch (type) {
5919         case LINE_STAT_STAGED:
5920                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5922         case LINE_STAT_UNSTAGED:
5923         case LINE_STAT_UNTRACKED:
5924                 return io_run(io, others_argv, opt_cdup, IO_WR);
5926         default:
5927                 die("line type %d not handled in switch", type);
5928                 return FALSE;
5929         }
5932 static bool
5933 status_update_write(struct io *io, struct status *status, enum line_type type)
5935         char buf[SIZEOF_STR];
5936         size_t bufsize = 0;
5938         switch (type) {
5939         case LINE_STAT_STAGED:
5940                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5941                                         status->old.mode,
5942                                         status->old.rev,
5943                                         status->old.name, 0))
5944                         return FALSE;
5945                 break;
5947         case LINE_STAT_UNSTAGED:
5948         case LINE_STAT_UNTRACKED:
5949                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5950                         return FALSE;
5951                 break;
5953         default:
5954                 die("line type %d not handled in switch", type);
5955         }
5957         return io_write(io, buf, bufsize);
5960 static bool
5961 status_update_file(struct status *status, enum line_type type)
5963         struct io io = {};
5964         bool result;
5966         if (!status_update_prepare(&io, type))
5967                 return FALSE;
5969         result = status_update_write(&io, status, type);
5970         return io_done(&io) && result;
5973 static bool
5974 status_update_files(struct view *view, struct line *line)
5976         char buf[sizeof(view->ref)];
5977         struct io io = {};
5978         bool result = TRUE;
5979         struct line *pos = view->line + view->lines;
5980         int files = 0;
5981         int file, done;
5982         int cursor_y = -1, cursor_x = -1;
5984         if (!status_update_prepare(&io, line->type))
5985                 return FALSE;
5987         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5988                 files++;
5990         string_copy(buf, view->ref);
5991         getsyx(cursor_y, cursor_x);
5992         for (file = 0, done = 5; result && file < files; line++, file++) {
5993                 int almost_done = file * 100 / files;
5995                 if (almost_done > done) {
5996                         done = almost_done;
5997                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5998                                       file, files, done);
5999                         update_view_title(view);
6000                         setsyx(cursor_y, cursor_x);
6001                         doupdate();
6002                 }
6003                 result = status_update_write(&io, line->data, line->type);
6004         }
6005         string_copy(view->ref, buf);
6007         return io_done(&io) && result;
6010 static bool
6011 status_update(struct view *view)
6013         struct line *line = &view->line[view->lineno];
6015         assert(view->lines);
6017         if (!line->data) {
6018                 /* This should work even for the "On branch" line. */
6019                 if (line < view->line + view->lines && !line[1].data) {
6020                         report("Nothing to update");
6021                         return FALSE;
6022                 }
6024                 if (!status_update_files(view, line + 1)) {
6025                         report("Failed to update file status");
6026                         return FALSE;
6027                 }
6029         } else if (!status_update_file(line->data, line->type)) {
6030                 report("Failed to update file status");
6031                 return FALSE;
6032         }
6034         return TRUE;
6037 static bool
6038 status_revert(struct status *status, enum line_type type, bool has_none)
6040         if (!status || type != LINE_STAT_UNSTAGED) {
6041                 if (type == LINE_STAT_STAGED) {
6042                         report("Cannot revert changes to staged files");
6043                 } else if (type == LINE_STAT_UNTRACKED) {
6044                         report("Cannot revert changes to untracked files");
6045                 } else if (has_none) {
6046                         report("Nothing to revert");
6047                 } else {
6048                         report("Cannot revert changes to multiple files");
6049                 }
6051         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6052                 char mode[10] = "100644";
6053                 const char *reset_argv[] = {
6054                         "git", "update-index", "--cacheinfo", mode,
6055                                 status->old.rev, status->old.name, NULL
6056                 };
6057                 const char *checkout_argv[] = {
6058                         "git", "checkout", "--", status->old.name, NULL
6059                 };
6061                 if (status->status == 'U') {
6062                         string_format(mode, "%5o", status->old.mode);
6064                         if (status->old.mode == 0 && status->new.mode == 0) {
6065                                 reset_argv[2] = "--force-remove";
6066                                 reset_argv[3] = status->old.name;
6067                                 reset_argv[4] = NULL;
6068                         }
6070                         if (!io_run_fg(reset_argv, opt_cdup))
6071                                 return FALSE;
6072                         if (status->old.mode == 0 && status->new.mode == 0)
6073                                 return TRUE;
6074                 }
6076                 return io_run_fg(checkout_argv, opt_cdup);
6077         }
6079         return FALSE;
6082 static enum request
6083 status_request(struct view *view, enum request request, struct line *line)
6085         struct status *status = line->data;
6087         switch (request) {
6088         case REQ_STATUS_UPDATE:
6089                 if (!status_update(view))
6090                         return REQ_NONE;
6091                 break;
6093         case REQ_STATUS_REVERT:
6094                 if (!status_revert(status, line->type, status_has_none(view, line)))
6095                         return REQ_NONE;
6096                 break;
6098         case REQ_STATUS_MERGE:
6099                 if (!status || status->status != 'U') {
6100                         report("Merging only possible for files with unmerged status ('U').");
6101                         return REQ_NONE;
6102                 }
6103                 open_mergetool(status->new.name);
6104                 break;
6106         case REQ_EDIT:
6107                 if (!status)
6108                         return request;
6109                 if (status->status == 'D') {
6110                         report("File has been deleted.");
6111                         return REQ_NONE;
6112                 }
6114                 open_editor(status->new.name);
6115                 break;
6117         case REQ_VIEW_BLAME:
6118                 if (status)
6119                         opt_ref[0] = 0;
6120                 return request;
6122         case REQ_ENTER:
6123                 /* After returning the status view has been split to
6124                  * show the stage view. No further reloading is
6125                  * necessary. */
6126                 return status_enter(view, line);
6128         case REQ_REFRESH:
6129                 /* Simply reload the view. */
6130                 break;
6132         default:
6133                 return request;
6134         }
6136         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6138         return REQ_NONE;
6141 static void
6142 status_select(struct view *view, struct line *line)
6144         struct status *status = line->data;
6145         char file[SIZEOF_STR] = "all files";
6146         const char *text;
6147         const char *key;
6149         if (status && !string_format(file, "'%s'", status->new.name))
6150                 return;
6152         if (!status && line[1].type == LINE_STAT_NONE)
6153                 line++;
6155         switch (line->type) {
6156         case LINE_STAT_STAGED:
6157                 text = "Press %s to unstage %s for commit";
6158                 break;
6160         case LINE_STAT_UNSTAGED:
6161                 text = "Press %s to stage %s for commit";
6162                 break;
6164         case LINE_STAT_UNTRACKED:
6165                 text = "Press %s to stage %s for addition";
6166                 break;
6168         case LINE_STAT_HEAD:
6169         case LINE_STAT_NONE:
6170                 text = "Nothing to update";
6171                 break;
6173         default:
6174                 die("line type %d not handled in switch", line->type);
6175         }
6177         if (status && status->status == 'U') {
6178                 text = "Press %s to resolve conflict in %s";
6179                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6181         } else {
6182                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6183         }
6185         string_format(view->ref, text, key, file);
6186         if (status)
6187                 string_copy(opt_file, status->new.name);
6190 static bool
6191 status_grep(struct view *view, struct line *line)
6193         struct status *status = line->data;
6195         if (status) {
6196                 const char buf[2] = { status->status, 0 };
6197                 const char *text[] = { status->new.name, buf, NULL };
6199                 return grep_text(view, text);
6200         }
6202         return FALSE;
6205 static struct view_ops status_ops = {
6206         "file",
6207         NULL,
6208         status_open,
6209         NULL,
6210         status_draw,
6211         status_request,
6212         status_grep,
6213         status_select,
6214 };
6217 static bool
6218 stage_diff_write(struct io *io, struct line *line, struct line *end)
6220         while (line < end) {
6221                 if (!io_write(io, line->data, strlen(line->data)) ||
6222                     !io_write(io, "\n", 1))
6223                         return FALSE;
6224                 line++;
6225                 if (line->type == LINE_DIFF_CHUNK ||
6226                     line->type == LINE_DIFF_HEADER)
6227                         break;
6228         }
6230         return TRUE;
6233 static struct line *
6234 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6236         for (; view->line < line; line--)
6237                 if (line->type == type)
6238                         return line;
6240         return NULL;
6243 static bool
6244 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6246         const char *apply_argv[SIZEOF_ARG] = {
6247                 "git", "apply", "--whitespace=nowarn", NULL
6248         };
6249         struct line *diff_hdr;
6250         struct io io = {};
6251         int argc = 3;
6253         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6254         if (!diff_hdr)
6255                 return FALSE;
6257         if (!revert)
6258                 apply_argv[argc++] = "--cached";
6259         if (revert || stage_line_type == LINE_STAT_STAGED)
6260                 apply_argv[argc++] = "-R";
6261         apply_argv[argc++] = "-";
6262         apply_argv[argc++] = NULL;
6263         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6264                 return FALSE;
6266         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6267             !stage_diff_write(&io, chunk, view->line + view->lines))
6268                 chunk = NULL;
6270         io_done(&io);
6271         io_run_bg(update_index_argv);
6273         return chunk ? TRUE : FALSE;
6276 static bool
6277 stage_update(struct view *view, struct line *line)
6279         struct line *chunk = NULL;
6281         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6282                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6284         if (chunk) {
6285                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6286                         report("Failed to apply chunk");
6287                         return FALSE;
6288                 }
6290         } else if (!stage_status.status) {
6291                 view = VIEW(REQ_VIEW_STATUS);
6293                 for (line = view->line; line < view->line + view->lines; line++)
6294                         if (line->type == stage_line_type)
6295                                 break;
6297                 if (!status_update_files(view, line + 1)) {
6298                         report("Failed to update files");
6299                         return FALSE;
6300                 }
6302         } else if (!status_update_file(&stage_status, stage_line_type)) {
6303                 report("Failed to update file");
6304                 return FALSE;
6305         }
6307         return TRUE;
6310 static bool
6311 stage_revert(struct view *view, struct line *line)
6313         struct line *chunk = NULL;
6315         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6316                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6318         if (chunk) {
6319                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6320                         return FALSE;
6322                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6323                         report("Failed to revert chunk");
6324                         return FALSE;
6325                 }
6326                 return TRUE;
6328         } else {
6329                 return status_revert(stage_status.status ? &stage_status : NULL,
6330                                      stage_line_type, FALSE);
6331         }
6335 static void
6336 stage_next(struct view *view, struct line *line)
6338         int i;
6340         if (!stage_chunks) {
6341                 for (line = view->line; line < view->line + view->lines; line++) {
6342                         if (line->type != LINE_DIFF_CHUNK)
6343                                 continue;
6345                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6346                                 report("Allocation failure");
6347                                 return;
6348                         }
6350                         stage_chunk[stage_chunks++] = line - view->line;
6351                 }
6352         }
6354         for (i = 0; i < stage_chunks; i++) {
6355                 if (stage_chunk[i] > view->lineno) {
6356                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6357                         report("Chunk %d of %d", i + 1, stage_chunks);
6358                         return;
6359                 }
6360         }
6362         report("No next chunk found");
6365 static enum request
6366 stage_request(struct view *view, enum request request, struct line *line)
6368         switch (request) {
6369         case REQ_STATUS_UPDATE:
6370                 if (!stage_update(view, line))
6371                         return REQ_NONE;
6372                 break;
6374         case REQ_STATUS_REVERT:
6375                 if (!stage_revert(view, line))
6376                         return REQ_NONE;
6377                 break;
6379         case REQ_STAGE_NEXT:
6380                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6381                         report("File is untracked; press %s to add",
6382                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6383                         return REQ_NONE;
6384                 }
6385                 stage_next(view, line);
6386                 return REQ_NONE;
6388         case REQ_EDIT:
6389                 if (!stage_status.new.name[0])
6390                         return request;
6391                 if (stage_status.status == 'D') {
6392                         report("File has been deleted.");
6393                         return REQ_NONE;
6394                 }
6396                 open_editor(stage_status.new.name);
6397                 break;
6399         case REQ_REFRESH:
6400                 /* Reload everything ... */
6401                 break;
6403         case REQ_VIEW_BLAME:
6404                 if (stage_status.new.name[0]) {
6405                         string_copy(opt_file, stage_status.new.name);
6406                         opt_ref[0] = 0;
6407                 }
6408                 return request;
6410         case REQ_ENTER:
6411                 return pager_request(view, request, line);
6413         default:
6414                 return request;
6415         }
6417         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6418         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6420         /* Check whether the staged entry still exists, and close the
6421          * stage view if it doesn't. */
6422         if (!status_exists(&stage_status, stage_line_type)) {
6423                 status_restore(VIEW(REQ_VIEW_STATUS));
6424                 return REQ_VIEW_CLOSE;
6425         }
6427         if (stage_line_type == LINE_STAT_UNTRACKED) {
6428                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6429                         report("Cannot display a directory");
6430                         return REQ_NONE;
6431                 }
6433                 if (!prepare_update_file(view, stage_status.new.name)) {
6434                         report("Failed to open file: %s", strerror(errno));
6435                         return REQ_NONE;
6436                 }
6437         }
6438         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6440         return REQ_NONE;
6443 static struct view_ops stage_ops = {
6444         "line",
6445         NULL,
6446         NULL,
6447         pager_read,
6448         pager_draw,
6449         stage_request,
6450         pager_grep,
6451         pager_select,
6452 };
6455 /*
6456  * Revision graph
6457  */
6459 struct commit {
6460         char id[SIZEOF_REV];            /* SHA1 ID. */
6461         char title[128];                /* First line of the commit message. */
6462         const char *author;             /* Author of the commit. */
6463         struct time time;               /* Date from the author ident. */
6464         struct ref_list *refs;          /* Repository references. */
6465         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6466         size_t graph_size;              /* The width of the graph array. */
6467         bool has_parents;               /* Rewritten --parents seen. */
6468 };
6470 /* Size of rev graph with no  "padding" columns */
6471 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6473 struct rev_graph {
6474         struct rev_graph *prev, *next, *parents;
6475         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6476         size_t size;
6477         struct commit *commit;
6478         size_t pos;
6479         unsigned int boundary:1;
6480 };
6482 /* Parents of the commit being visualized. */
6483 static struct rev_graph graph_parents[4];
6485 /* The current stack of revisions on the graph. */
6486 static struct rev_graph graph_stacks[4] = {
6487         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6488         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6489         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6490         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6491 };
6493 static inline bool
6494 graph_parent_is_merge(struct rev_graph *graph)
6496         return graph->parents->size > 1;
6499 static inline void
6500 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6502         struct commit *commit = graph->commit;
6504         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6505                 commit->graph[commit->graph_size++] = symbol;
6508 static void
6509 clear_rev_graph(struct rev_graph *graph)
6511         graph->boundary = 0;
6512         graph->size = graph->pos = 0;
6513         graph->commit = NULL;
6514         memset(graph->parents, 0, sizeof(*graph->parents));
6517 static void
6518 done_rev_graph(struct rev_graph *graph)
6520         if (graph_parent_is_merge(graph) &&
6521             graph->pos < graph->size - 1 &&
6522             graph->next->size == graph->size + graph->parents->size - 1) {
6523                 size_t i = graph->pos + graph->parents->size - 1;
6525                 graph->commit->graph_size = i * 2;
6526                 while (i < graph->next->size - 1) {
6527                         append_to_rev_graph(graph, ' ');
6528                         append_to_rev_graph(graph, '\\');
6529                         i++;
6530                 }
6531         }
6533         clear_rev_graph(graph);
6536 static void
6537 push_rev_graph(struct rev_graph *graph, const char *parent)
6539         int i;
6541         /* "Collapse" duplicate parents lines.
6542          *
6543          * FIXME: This needs to also update update the drawn graph but
6544          * for now it just serves as a method for pruning graph lines. */
6545         for (i = 0; i < graph->size; i++)
6546                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6547                         return;
6549         if (graph->size < SIZEOF_REVITEMS) {
6550                 string_copy_rev(graph->rev[graph->size++], parent);
6551         }
6554 static chtype
6555 get_rev_graph_symbol(struct rev_graph *graph)
6557         chtype symbol;
6559         if (graph->boundary)
6560                 symbol = REVGRAPH_BOUND;
6561         else if (graph->parents->size == 0)
6562                 symbol = REVGRAPH_INIT;
6563         else if (graph_parent_is_merge(graph))
6564                 symbol = REVGRAPH_MERGE;
6565         else if (graph->pos >= graph->size)
6566                 symbol = REVGRAPH_BRANCH;
6567         else
6568                 symbol = REVGRAPH_COMMIT;
6570         return symbol;
6573 static void
6574 draw_rev_graph(struct rev_graph *graph)
6576         struct rev_filler {
6577                 chtype separator, line;
6578         };
6579         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6580         static struct rev_filler fillers[] = {
6581                 { ' ',  '|' },
6582                 { '`',  '.' },
6583                 { '\'', ' ' },
6584                 { '/',  ' ' },
6585         };
6586         chtype symbol = get_rev_graph_symbol(graph);
6587         struct rev_filler *filler;
6588         size_t i;
6590         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6591         filler = &fillers[DEFAULT];
6593         for (i = 0; i < graph->pos; i++) {
6594                 append_to_rev_graph(graph, filler->line);
6595                 if (graph_parent_is_merge(graph->prev) &&
6596                     graph->prev->pos == i)
6597                         filler = &fillers[RSHARP];
6599                 append_to_rev_graph(graph, filler->separator);
6600         }
6602         /* Place the symbol for this revision. */
6603         append_to_rev_graph(graph, symbol);
6605         if (graph->prev->size > graph->size)
6606                 filler = &fillers[RDIAG];
6607         else
6608                 filler = &fillers[DEFAULT];
6610         i++;
6612         for (; i < graph->size; i++) {
6613                 append_to_rev_graph(graph, filler->separator);
6614                 append_to_rev_graph(graph, filler->line);
6615                 if (graph_parent_is_merge(graph->prev) &&
6616                     i < graph->prev->pos + graph->parents->size)
6617                         filler = &fillers[RSHARP];
6618                 if (graph->prev->size > graph->size)
6619                         filler = &fillers[LDIAG];
6620         }
6622         if (graph->prev->size > graph->size) {
6623                 append_to_rev_graph(graph, filler->separator);
6624                 if (filler->line != ' ')
6625                         append_to_rev_graph(graph, filler->line);
6626         }
6629 /* Prepare the next rev graph */
6630 static void
6631 prepare_rev_graph(struct rev_graph *graph)
6633         size_t i;
6635         /* First, traverse all lines of revisions up to the active one. */
6636         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6637                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6638                         break;
6640                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6641         }
6643         /* Interleave the new revision parent(s). */
6644         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6645                 push_rev_graph(graph->next, graph->parents->rev[i]);
6647         /* Lastly, put any remaining revisions. */
6648         for (i = graph->pos + 1; i < graph->size; i++)
6649                 push_rev_graph(graph->next, graph->rev[i]);
6652 static void
6653 update_rev_graph(struct view *view, struct rev_graph *graph)
6655         /* If this is the finalizing update ... */
6656         if (graph->commit)
6657                 prepare_rev_graph(graph);
6659         /* Graph visualization needs a one rev look-ahead,
6660          * so the first update doesn't visualize anything. */
6661         if (!graph->prev->commit)
6662                 return;
6664         if (view->lines > 2)
6665                 view->line[view->lines - 3].dirty = 1;
6666         if (view->lines > 1)
6667                 view->line[view->lines - 2].dirty = 1;
6668         draw_rev_graph(graph->prev);
6669         done_rev_graph(graph->prev->prev);
6673 /*
6674  * Main view backend
6675  */
6677 static const char *main_argv[SIZEOF_ARG] = {
6678         "git", "log", "--no-color", "--pretty=raw", "--parents",
6679                       "--topo-order", "%(head)", NULL
6680 };
6682 static bool
6683 main_draw(struct view *view, struct line *line, unsigned int lineno)
6685         struct commit *commit = line->data;
6687         if (!commit->author)
6688                 return FALSE;
6690         if (opt_date && draw_date(view, &commit->time))
6691                 return TRUE;
6693         if (opt_author && draw_author(view, commit->author))
6694                 return TRUE;
6696         if (opt_rev_graph && commit->graph_size &&
6697             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6698                 return TRUE;
6700         if (opt_show_refs && commit->refs) {
6701                 size_t i;
6703                 for (i = 0; i < commit->refs->size; i++) {
6704                         struct ref *ref = commit->refs->refs[i];
6705                         enum line_type type;
6707                         if (ref->head)
6708                                 type = LINE_MAIN_HEAD;
6709                         else if (ref->ltag)
6710                                 type = LINE_MAIN_LOCAL_TAG;
6711                         else if (ref->tag)
6712                                 type = LINE_MAIN_TAG;
6713                         else if (ref->tracked)
6714                                 type = LINE_MAIN_TRACKED;
6715                         else if (ref->remote)
6716                                 type = LINE_MAIN_REMOTE;
6717                         else
6718                                 type = LINE_MAIN_REF;
6720                         if (draw_text(view, type, "[", TRUE) ||
6721                             draw_text(view, type, ref->name, TRUE) ||
6722                             draw_text(view, type, "]", TRUE))
6723                                 return TRUE;
6725                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6726                                 return TRUE;
6727                 }
6728         }
6730         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6731         return TRUE;
6734 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6735 static bool
6736 main_read(struct view *view, char *line)
6738         static struct rev_graph *graph = graph_stacks;
6739         enum line_type type;
6740         struct commit *commit;
6742         if (!line) {
6743                 int i;
6745                 if (!view->lines && !view->parent)
6746                         die("No revisions match the given arguments.");
6747                 if (view->lines > 0) {
6748                         commit = view->line[view->lines - 1].data;
6749                         view->line[view->lines - 1].dirty = 1;
6750                         if (!commit->author) {
6751                                 view->lines--;
6752                                 free(commit);
6753                                 graph->commit = NULL;
6754                         }
6755                 }
6756                 update_rev_graph(view, graph);
6758                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6759                         clear_rev_graph(&graph_stacks[i]);
6760                 return TRUE;
6761         }
6763         type = get_line_type(line);
6764         if (type == LINE_COMMIT) {
6765                 commit = calloc(1, sizeof(struct commit));
6766                 if (!commit)
6767                         return FALSE;
6769                 line += STRING_SIZE("commit ");
6770                 if (*line == '-') {
6771                         graph->boundary = 1;
6772                         line++;
6773                 }
6775                 string_copy_rev(commit->id, line);
6776                 commit->refs = get_ref_list(commit->id);
6777                 graph->commit = commit;
6778                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6780                 while ((line = strchr(line, ' '))) {
6781                         line++;
6782                         push_rev_graph(graph->parents, line);
6783                         commit->has_parents = TRUE;
6784                 }
6785                 return TRUE;
6786         }
6788         if (!view->lines)
6789                 return TRUE;
6790         commit = view->line[view->lines - 1].data;
6792         switch (type) {
6793         case LINE_PARENT:
6794                 if (commit->has_parents)
6795                         break;
6796                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6797                 break;
6799         case LINE_AUTHOR:
6800                 parse_author_line(line + STRING_SIZE("author "),
6801                                   &commit->author, &commit->time);
6802                 update_rev_graph(view, graph);
6803                 graph = graph->next;
6804                 break;
6806         default:
6807                 /* Fill in the commit title if it has not already been set. */
6808                 if (commit->title[0])
6809                         break;
6811                 /* Require titles to start with a non-space character at the
6812                  * offset used by git log. */
6813                 if (strncmp(line, "    ", 4))
6814                         break;
6815                 line += 4;
6816                 /* Well, if the title starts with a whitespace character,
6817                  * try to be forgiving.  Otherwise we end up with no title. */
6818                 while (isspace(*line))
6819                         line++;
6820                 if (*line == '\0')
6821                         break;
6822                 /* FIXME: More graceful handling of titles; append "..." to
6823                  * shortened titles, etc. */
6825                 string_expand(commit->title, sizeof(commit->title), line, 1);
6826                 view->line[view->lines - 1].dirty = 1;
6827         }
6829         return TRUE;
6832 static enum request
6833 main_request(struct view *view, enum request request, struct line *line)
6835         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6837         switch (request) {
6838         case REQ_ENTER:
6839                 open_view(view, REQ_VIEW_DIFF, flags);
6840                 break;
6841         case REQ_REFRESH:
6842                 load_refs();
6843                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6844                 break;
6845         default:
6846                 return request;
6847         }
6849         return REQ_NONE;
6852 static bool
6853 grep_refs(struct ref_list *list, regex_t *regex)
6855         regmatch_t pmatch;
6856         size_t i;
6858         if (!opt_show_refs || !list)
6859                 return FALSE;
6861         for (i = 0; i < list->size; i++) {
6862                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6863                         return TRUE;
6864         }
6866         return FALSE;
6869 static bool
6870 main_grep(struct view *view, struct line *line)
6872         struct commit *commit = line->data;
6873         const char *text[] = {
6874                 commit->title,
6875                 opt_author ? commit->author : "",
6876                 mkdate(&commit->time, opt_date),
6877                 NULL
6878         };
6880         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6883 static void
6884 main_select(struct view *view, struct line *line)
6886         struct commit *commit = line->data;
6888         string_copy_rev(view->ref, commit->id);
6889         string_copy_rev(ref_commit, view->ref);
6892 static struct view_ops main_ops = {
6893         "commit",
6894         main_argv,
6895         NULL,
6896         main_read,
6897         main_draw,
6898         main_request,
6899         main_grep,
6900         main_select,
6901 };
6904 /*
6905  * Status management
6906  */
6908 /* Whether or not the curses interface has been initialized. */
6909 static bool cursed = FALSE;
6911 /* Terminal hacks and workarounds. */
6912 static bool use_scroll_redrawwin;
6913 static bool use_scroll_status_wclear;
6915 /* The status window is used for polling keystrokes. */
6916 static WINDOW *status_win;
6918 /* Reading from the prompt? */
6919 static bool input_mode = FALSE;
6921 static bool status_empty = FALSE;
6923 /* Update status and title window. */
6924 static void
6925 report(const char *msg, ...)
6927         struct view *view = display[current_view];
6929         if (input_mode)
6930                 return;
6932         if (!view) {
6933                 char buf[SIZEOF_STR];
6934                 va_list args;
6936                 va_start(args, msg);
6937                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6938                         buf[sizeof(buf) - 1] = 0;
6939                         buf[sizeof(buf) - 2] = '.';
6940                         buf[sizeof(buf) - 3] = '.';
6941                         buf[sizeof(buf) - 4] = '.';
6942                 }
6943                 va_end(args);
6944                 die("%s", buf);
6945         }
6947         if (!status_empty || *msg) {
6948                 va_list args;
6950                 va_start(args, msg);
6952                 wmove(status_win, 0, 0);
6953                 if (view->has_scrolled && use_scroll_status_wclear)
6954                         wclear(status_win);
6955                 if (*msg) {
6956                         vwprintw(status_win, msg, args);
6957                         status_empty = FALSE;
6958                 } else {
6959                         status_empty = TRUE;
6960                 }
6961                 wclrtoeol(status_win);
6962                 wnoutrefresh(status_win);
6964                 va_end(args);
6965         }
6967         update_view_title(view);
6970 static void
6971 init_display(void)
6973         const char *term;
6974         int x, y;
6976         /* Initialize the curses library */
6977         if (isatty(STDIN_FILENO)) {
6978                 cursed = !!initscr();
6979                 opt_tty = stdin;
6980         } else {
6981                 /* Leave stdin and stdout alone when acting as a pager. */
6982                 opt_tty = fopen("/dev/tty", "r+");
6983                 if (!opt_tty)
6984                         die("Failed to open /dev/tty");
6985                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6986         }
6988         if (!cursed)
6989                 die("Failed to initialize curses");
6991         nonl();         /* Disable conversion and detect newlines from input. */
6992         cbreak();       /* Take input chars one at a time, no wait for \n */
6993         noecho();       /* Don't echo input */
6994         leaveok(stdscr, FALSE);
6996         if (has_colors())
6997                 init_colors();
6999         getmaxyx(stdscr, y, x);
7000         status_win = newwin(1, 0, y - 1, 0);
7001         if (!status_win)
7002                 die("Failed to create status window");
7004         /* Enable keyboard mapping */
7005         keypad(status_win, TRUE);
7006         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7008         TABSIZE = opt_tab_size;
7010         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7011         if (term && !strcmp(term, "gnome-terminal")) {
7012                 /* In the gnome-terminal-emulator, the message from
7013                  * scrolling up one line when impossible followed by
7014                  * scrolling down one line causes corruption of the
7015                  * status line. This is fixed by calling wclear. */
7016                 use_scroll_status_wclear = TRUE;
7017                 use_scroll_redrawwin = FALSE;
7019         } else if (term && !strcmp(term, "xrvt-xpm")) {
7020                 /* No problems with full optimizations in xrvt-(unicode)
7021                  * and aterm. */
7022                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7024         } else {
7025                 /* When scrolling in (u)xterm the last line in the
7026                  * scrolling direction will update slowly. */
7027                 use_scroll_redrawwin = TRUE;
7028                 use_scroll_status_wclear = FALSE;
7029         }
7032 static int
7033 get_input(int prompt_position)
7035         struct view *view;
7036         int i, key, cursor_y, cursor_x;
7037         bool loading = FALSE;
7039         if (prompt_position)
7040                 input_mode = TRUE;
7042         while (TRUE) {
7043                 foreach_view (view, i) {
7044                         update_view(view);
7045                         if (view_is_displayed(view) && view->has_scrolled &&
7046                             use_scroll_redrawwin)
7047                                 redrawwin(view->win);
7048                         view->has_scrolled = FALSE;
7049                         if (view->pipe)
7050                                 loading = TRUE;
7051                 }
7053                 /* Update the cursor position. */
7054                 if (prompt_position) {
7055                         getbegyx(status_win, cursor_y, cursor_x);
7056                         cursor_x = prompt_position;
7057                 } else {
7058                         view = display[current_view];
7059                         getbegyx(view->win, cursor_y, cursor_x);
7060                         cursor_x = view->width - 1;
7061                         cursor_y += view->lineno - view->offset;
7062                 }
7063                 setsyx(cursor_y, cursor_x);
7065                 /* Refresh, accept single keystroke of input */
7066                 doupdate();
7067                 nodelay(status_win, loading);
7068                 key = wgetch(status_win);
7070                 /* wgetch() with nodelay() enabled returns ERR when
7071                  * there's no input. */
7072                 if (key == ERR) {
7074                 } else if (key == KEY_RESIZE) {
7075                         int height, width;
7077                         getmaxyx(stdscr, height, width);
7079                         wresize(status_win, 1, width);
7080                         mvwin(status_win, height - 1, 0);
7081                         wnoutrefresh(status_win);
7082                         resize_display();
7083                         redraw_display(TRUE);
7085                 } else {
7086                         input_mode = FALSE;
7087                         return key;
7088                 }
7089         }
7092 static char *
7093 prompt_input(const char *prompt, input_handler handler, void *data)
7095         enum input_status status = INPUT_OK;
7096         static char buf[SIZEOF_STR];
7097         size_t pos = 0;
7099         buf[pos] = 0;
7101         while (status == INPUT_OK || status == INPUT_SKIP) {
7102                 int key;
7104                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7105                 wclrtoeol(status_win);
7107                 key = get_input(pos + 1);
7108                 switch (key) {
7109                 case KEY_RETURN:
7110                 case KEY_ENTER:
7111                 case '\n':
7112                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7113                         break;
7115                 case KEY_BACKSPACE:
7116                         if (pos > 0)
7117                                 buf[--pos] = 0;
7118                         else
7119                                 status = INPUT_CANCEL;
7120                         break;
7122                 case KEY_ESC:
7123                         status = INPUT_CANCEL;
7124                         break;
7126                 default:
7127                         if (pos >= sizeof(buf)) {
7128                                 report("Input string too long");
7129                                 return NULL;
7130                         }
7132                         status = handler(data, buf, key);
7133                         if (status == INPUT_OK)
7134                                 buf[pos++] = (char) key;
7135                 }
7136         }
7138         /* Clear the status window */
7139         status_empty = FALSE;
7140         report("");
7142         if (status == INPUT_CANCEL)
7143                 return NULL;
7145         buf[pos++] = 0;
7147         return buf;
7150 static enum input_status
7151 prompt_yesno_handler(void *data, char *buf, int c)
7153         if (c == 'y' || c == 'Y')
7154                 return INPUT_STOP;
7155         if (c == 'n' || c == 'N')
7156                 return INPUT_CANCEL;
7157         return INPUT_SKIP;
7160 static bool
7161 prompt_yesno(const char *prompt)
7163         char prompt2[SIZEOF_STR];
7165         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7166                 return FALSE;
7168         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7171 static enum input_status
7172 read_prompt_handler(void *data, char *buf, int c)
7174         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7177 static char *
7178 read_prompt(const char *prompt)
7180         return prompt_input(prompt, read_prompt_handler, NULL);
7183 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7185         enum input_status status = INPUT_OK;
7186         int size = 0;
7188         while (items[size].text)
7189                 size++;
7191         while (status == INPUT_OK) {
7192                 const struct menu_item *item = &items[*selected];
7193                 int key;
7194                 int i;
7196                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7197                           prompt, *selected + 1, size);
7198                 if (item->hotkey)
7199                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7200                 wprintw(status_win, "%s", item->text);
7201                 wclrtoeol(status_win);
7203                 key = get_input(COLS - 1);
7204                 switch (key) {
7205                 case KEY_RETURN:
7206                 case KEY_ENTER:
7207                 case '\n':
7208                         status = INPUT_STOP;
7209                         break;
7211                 case KEY_LEFT:
7212                 case KEY_UP:
7213                         *selected = *selected - 1;
7214                         if (*selected < 0)
7215                                 *selected = size - 1;
7216                         break;
7218                 case KEY_RIGHT:
7219                 case KEY_DOWN:
7220                         *selected = (*selected + 1) % size;
7221                         break;
7223                 case KEY_ESC:
7224                         status = INPUT_CANCEL;
7225                         break;
7227                 default:
7228                         for (i = 0; items[i].text; i++)
7229                                 if (items[i].hotkey == key) {
7230                                         *selected = i;
7231                                         status = INPUT_STOP;
7232                                         break;
7233                                 }
7234                 }
7235         }
7237         /* Clear the status window */
7238         status_empty = FALSE;
7239         report("");
7241         return status != INPUT_CANCEL;
7244 /*
7245  * Repository properties
7246  */
7248 static struct ref **refs = NULL;
7249 static size_t refs_size = 0;
7250 static struct ref *refs_head = NULL;
7252 static struct ref_list **ref_lists = NULL;
7253 static size_t ref_lists_size = 0;
7255 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7256 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7257 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7259 static int
7260 compare_refs(const void *ref1_, const void *ref2_)
7262         const struct ref *ref1 = *(const struct ref **)ref1_;
7263         const struct ref *ref2 = *(const struct ref **)ref2_;
7265         if (ref1->tag != ref2->tag)
7266                 return ref2->tag - ref1->tag;
7267         if (ref1->ltag != ref2->ltag)
7268                 return ref2->ltag - ref2->ltag;
7269         if (ref1->head != ref2->head)
7270                 return ref2->head - ref1->head;
7271         if (ref1->tracked != ref2->tracked)
7272                 return ref2->tracked - ref1->tracked;
7273         if (ref1->remote != ref2->remote)
7274                 return ref2->remote - ref1->remote;
7275         return strcmp(ref1->name, ref2->name);
7278 static void
7279 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7281         size_t i;
7283         for (i = 0; i < refs_size; i++)
7284                 if (!visitor(data, refs[i]))
7285                         break;
7288 static struct ref *
7289 get_ref_head()
7291         return refs_head;
7294 static struct ref_list *
7295 get_ref_list(const char *id)
7297         struct ref_list *list;
7298         size_t i;
7300         for (i = 0; i < ref_lists_size; i++)
7301                 if (!strcmp(id, ref_lists[i]->id))
7302                         return ref_lists[i];
7304         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7305                 return NULL;
7306         list = calloc(1, sizeof(*list));
7307         if (!list)
7308                 return NULL;
7310         for (i = 0; i < refs_size; i++) {
7311                 if (!strcmp(id, refs[i]->id) &&
7312                     realloc_refs_list(&list->refs, list->size, 1))
7313                         list->refs[list->size++] = refs[i];
7314         }
7316         if (!list->refs) {
7317                 free(list);
7318                 return NULL;
7319         }
7321         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7322         ref_lists[ref_lists_size++] = list;
7323         return list;
7326 static int
7327 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7329         struct ref *ref = NULL;
7330         bool tag = FALSE;
7331         bool ltag = FALSE;
7332         bool remote = FALSE;
7333         bool tracked = FALSE;
7334         bool head = FALSE;
7335         int from = 0, to = refs_size - 1;
7337         if (!prefixcmp(name, "refs/tags/")) {
7338                 if (!suffixcmp(name, namelen, "^{}")) {
7339                         namelen -= 3;
7340                         name[namelen] = 0;
7341                 } else {
7342                         ltag = TRUE;
7343                 }
7345                 tag = TRUE;
7346                 namelen -= STRING_SIZE("refs/tags/");
7347                 name    += STRING_SIZE("refs/tags/");
7349         } else if (!prefixcmp(name, "refs/remotes/")) {
7350                 remote = TRUE;
7351                 namelen -= STRING_SIZE("refs/remotes/");
7352                 name    += STRING_SIZE("refs/remotes/");
7353                 tracked  = !strcmp(opt_remote, name);
7355         } else if (!prefixcmp(name, "refs/heads/")) {
7356                 namelen -= STRING_SIZE("refs/heads/");
7357                 name    += STRING_SIZE("refs/heads/");
7358                 if (!strncmp(opt_head, name, namelen))
7359                         return OK;
7361         } else if (!strcmp(name, "HEAD")) {
7362                 head     = TRUE;
7363                 if (*opt_head) {
7364                         namelen  = strlen(opt_head);
7365                         name     = opt_head;
7366                 }
7367         }
7369         /* If we are reloading or it's an annotated tag, replace the
7370          * previous SHA1 with the resolved commit id; relies on the fact
7371          * git-ls-remote lists the commit id of an annotated tag right
7372          * before the commit id it points to. */
7373         while (from <= to) {
7374                 size_t pos = (to + from) / 2;
7375                 int cmp = strcmp(name, refs[pos]->name);
7377                 if (!cmp) {
7378                         ref = refs[pos];
7379                         break;
7380                 }
7382                 if (cmp < 0)
7383                         to = pos - 1;
7384                 else
7385                         from = pos + 1;
7386         }
7388         if (!ref) {
7389                 if (!realloc_refs(&refs, refs_size, 1))
7390                         return ERR;
7391                 ref = calloc(1, sizeof(*ref) + namelen);
7392                 if (!ref)
7393                         return ERR;
7394                 memmove(refs + from + 1, refs + from,
7395                         (refs_size - from) * sizeof(*refs));
7396                 refs[from] = ref;
7397                 strncpy(ref->name, name, namelen);
7398                 refs_size++;
7399         }
7401         ref->head = head;
7402         ref->tag = tag;
7403         ref->ltag = ltag;
7404         ref->remote = remote;
7405         ref->tracked = tracked;
7406         string_copy_rev(ref->id, id);
7408         if (head)
7409                 refs_head = ref;
7410         return OK;
7413 static int
7414 load_refs(void)
7416         const char *head_argv[] = {
7417                 "git", "symbolic-ref", "HEAD", NULL
7418         };
7419         static const char *ls_remote_argv[SIZEOF_ARG] = {
7420                 "git", "ls-remote", opt_git_dir, NULL
7421         };
7422         static bool init = FALSE;
7423         size_t i;
7425         if (!init) {
7426                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7427                         die("TIG_LS_REMOTE contains too many arguments");
7428                 init = TRUE;
7429         }
7431         if (!*opt_git_dir)
7432                 return OK;
7434         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7435             !prefixcmp(opt_head, "refs/heads/")) {
7436                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7438                 memmove(opt_head, offset, strlen(offset) + 1);
7439         }
7441         refs_head = NULL;
7442         for (i = 0; i < refs_size; i++)
7443                 refs[i]->id[0] = 0;
7445         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7446                 return ERR;
7448         /* Update the ref lists to reflect changes. */
7449         for (i = 0; i < ref_lists_size; i++) {
7450                 struct ref_list *list = ref_lists[i];
7451                 size_t old, new;
7453                 for (old = new = 0; old < list->size; old++)
7454                         if (!strcmp(list->id, list->refs[old]->id))
7455                                 list->refs[new++] = list->refs[old];
7456                 list->size = new;
7457         }
7459         return OK;
7462 static void
7463 set_remote_branch(const char *name, const char *value, size_t valuelen)
7465         if (!strcmp(name, ".remote")) {
7466                 string_ncopy(opt_remote, value, valuelen);
7468         } else if (*opt_remote && !strcmp(name, ".merge")) {
7469                 size_t from = strlen(opt_remote);
7471                 if (!prefixcmp(value, "refs/heads/"))
7472                         value += STRING_SIZE("refs/heads/");
7474                 if (!string_format_from(opt_remote, &from, "/%s", value))
7475                         opt_remote[0] = 0;
7476         }
7479 static void
7480 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7482         const char *argv[SIZEOF_ARG] = { name, "=" };
7483         int argc = 1 + (cmd == option_set_command);
7484         int error = ERR;
7486         if (!argv_from_string(argv, &argc, value))
7487                 config_msg = "Too many option arguments";
7488         else
7489                 error = cmd(argc, argv);
7491         if (error == ERR)
7492                 warn("Option 'tig.%s': %s", name, config_msg);
7495 static bool
7496 set_environment_variable(const char *name, const char *value)
7498         size_t len = strlen(name) + 1 + strlen(value) + 1;
7499         char *env = malloc(len);
7501         if (env &&
7502             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7503             putenv(env) == 0)
7504                 return TRUE;
7505         free(env);
7506         return FALSE;
7509 static void
7510 set_work_tree(const char *value)
7512         char cwd[SIZEOF_STR];
7514         if (!getcwd(cwd, sizeof(cwd)))
7515                 die("Failed to get cwd path: %s", strerror(errno));
7516         if (chdir(opt_git_dir) < 0)
7517                 die("Failed to chdir(%s): %s", strerror(errno));
7518         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7519                 die("Failed to get git path: %s", strerror(errno));
7520         if (chdir(cwd) < 0)
7521                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7522         if (chdir(value) < 0)
7523                 die("Failed to chdir(%s): %s", value, strerror(errno));
7524         if (!getcwd(cwd, sizeof(cwd)))
7525                 die("Failed to get cwd path: %s", strerror(errno));
7526         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7527                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7528         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7529                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7530         opt_is_inside_work_tree = TRUE;
7533 static int
7534 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7536         if (!strcmp(name, "i18n.commitencoding"))
7537                 string_ncopy(opt_encoding, value, valuelen);
7539         else if (!strcmp(name, "core.editor"))
7540                 string_ncopy(opt_editor, value, valuelen);
7542         else if (!strcmp(name, "core.worktree"))
7543                 set_work_tree(value);
7545         else if (!prefixcmp(name, "tig.color."))
7546                 set_repo_config_option(name + 10, value, option_color_command);
7548         else if (!prefixcmp(name, "tig.bind."))
7549                 set_repo_config_option(name + 9, value, option_bind_command);
7551         else if (!prefixcmp(name, "tig."))
7552                 set_repo_config_option(name + 4, value, option_set_command);
7554         else if (*opt_head && !prefixcmp(name, "branch.") &&
7555                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7556                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7558         return OK;
7561 static int
7562 load_git_config(void)
7564         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7566         return io_run_load(config_list_argv, "=", read_repo_config_option);
7569 static int
7570 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7572         if (!opt_git_dir[0]) {
7573                 string_ncopy(opt_git_dir, name, namelen);
7575         } else if (opt_is_inside_work_tree == -1) {
7576                 /* This can be 3 different values depending on the
7577                  * version of git being used. If git-rev-parse does not
7578                  * understand --is-inside-work-tree it will simply echo
7579                  * the option else either "true" or "false" is printed.
7580                  * Default to true for the unknown case. */
7581                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7583         } else if (*name == '.') {
7584                 string_ncopy(opt_cdup, name, namelen);
7586         } else {
7587                 string_ncopy(opt_prefix, name, namelen);
7588         }
7590         return OK;
7593 static int
7594 load_repo_info(void)
7596         const char *rev_parse_argv[] = {
7597                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7598                         "--show-cdup", "--show-prefix", NULL
7599         };
7601         return io_run_load(rev_parse_argv, "=", read_repo_info);
7605 /*
7606  * Main
7607  */
7609 static const char usage[] =
7610 "tig " TIG_VERSION " (" __DATE__ ")\n"
7611 "\n"
7612 "Usage: tig        [options] [revs] [--] [paths]\n"
7613 "   or: tig show   [options] [revs] [--] [paths]\n"
7614 "   or: tig blame  [rev] path\n"
7615 "   or: tig status\n"
7616 "   or: tig <      [git command output]\n"
7617 "\n"
7618 "Options:\n"
7619 "  -v, --version   Show version and exit\n"
7620 "  -h, --help      Show help message and exit";
7622 static void __NORETURN
7623 quit(int sig)
7625         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7626         if (cursed)
7627                 endwin();
7628         exit(0);
7631 static void __NORETURN
7632 die(const char *err, ...)
7634         va_list args;
7636         endwin();
7638         va_start(args, err);
7639         fputs("tig: ", stderr);
7640         vfprintf(stderr, err, args);
7641         fputs("\n", stderr);
7642         va_end(args);
7644         exit(1);
7647 static void
7648 warn(const char *msg, ...)
7650         va_list args;
7652         va_start(args, msg);
7653         fputs("tig warning: ", stderr);
7654         vfprintf(stderr, msg, args);
7655         fputs("\n", stderr);
7656         va_end(args);
7659 static enum request
7660 parse_options(int argc, const char *argv[])
7662         enum request request = REQ_VIEW_MAIN;
7663         const char *subcommand;
7664         bool seen_dashdash = FALSE;
7665         /* XXX: This is vulnerable to the user overriding options
7666          * required for the main view parser. */
7667         const char *custom_argv[SIZEOF_ARG] = {
7668                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7669                         "--topo-order", NULL
7670         };
7671         int i, j = 6;
7673         if (!isatty(STDIN_FILENO)) {
7674                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7675                 return REQ_VIEW_PAGER;
7676         }
7678         if (argc <= 1)
7679                 return REQ_NONE;
7681         subcommand = argv[1];
7682         if (!strcmp(subcommand, "status")) {
7683                 if (argc > 2)
7684                         warn("ignoring arguments after `%s'", subcommand);
7685                 return REQ_VIEW_STATUS;
7687         } else if (!strcmp(subcommand, "blame")) {
7688                 if (argc <= 2 || argc > 4)
7689                         die("invalid number of options to blame\n\n%s", usage);
7691                 i = 2;
7692                 if (argc == 4) {
7693                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7694                         i++;
7695                 }
7697                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7698                 return REQ_VIEW_BLAME;
7700         } else if (!strcmp(subcommand, "show")) {
7701                 request = REQ_VIEW_DIFF;
7703         } else {
7704                 subcommand = NULL;
7705         }
7707         if (subcommand) {
7708                 custom_argv[1] = subcommand;
7709                 j = 2;
7710         }
7712         for (i = 1 + !!subcommand; i < argc; i++) {
7713                 const char *opt = argv[i];
7715                 if (seen_dashdash || !strcmp(opt, "--")) {
7716                         seen_dashdash = TRUE;
7718                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7719                         printf("tig version %s\n", TIG_VERSION);
7720                         quit(0);
7722                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7723                         printf("%s\n", usage);
7724                         quit(0);
7725                 }
7727                 custom_argv[j++] = opt;
7728                 if (j >= ARRAY_SIZE(custom_argv))
7729                         die("command too long");
7730         }
7732         if (!prepare_update(VIEW(request), custom_argv, NULL))
7733                 die("Failed to format arguments");
7735         return request;
7738 int
7739 main(int argc, const char *argv[])
7741         const char *codeset = "UTF-8";
7742         enum request request = parse_options(argc, argv);
7743         struct view *view;
7744         size_t i;
7746         signal(SIGINT, quit);
7747         signal(SIGPIPE, SIG_IGN);
7749         if (setlocale(LC_ALL, "")) {
7750                 codeset = nl_langinfo(CODESET);
7751         }
7753         if (load_repo_info() == ERR)
7754                 die("Failed to load repo info.");
7756         if (load_options() == ERR)
7757                 die("Failed to load user config.");
7759         if (load_git_config() == ERR)
7760                 die("Failed to load repo config.");
7762         /* Require a git repository unless when running in pager mode. */
7763         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7764                 die("Not a git repository");
7766         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7767                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7768                 if (opt_iconv_in == ICONV_NONE)
7769                         die("Failed to initialize character set conversion");
7770         }
7772         if (codeset && strcmp(codeset, "UTF-8")) {
7773                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7774                 if (opt_iconv_out == ICONV_NONE)
7775                         die("Failed to initialize character set conversion");
7776         }
7778         if (load_refs() == ERR)
7779                 die("Failed to load refs.");
7781         foreach_view (view, i)
7782                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7783                         die("Too many arguments in the `%s` environment variable",
7784                             view->cmd_env);
7786         init_display();
7788         if (request != REQ_NONE)
7789                 open_view(NULL, request, OPEN_PREPARED);
7790         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7792         while (view_driver(display[current_view], request)) {
7793                 int key = get_input(0);
7795                 view = display[current_view];
7796                 request = get_keybinding(view->keymap, key);
7798                 /* Some low-level request handling. This keeps access to
7799                  * status_win restricted. */
7800                 switch (request) {
7801                 case REQ_PROMPT:
7802                 {
7803                         char *cmd = read_prompt(":");
7805                         if (cmd && isdigit(*cmd)) {
7806                                 int lineno = view->lineno + 1;
7808                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7809                                         select_view_line(view, lineno - 1);
7810                                         report("");
7811                                 } else {
7812                                         report("Unable to parse '%s' as a line number", cmd);
7813                                 }
7815                         } else if (cmd) {
7816                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7817                                 const char *argv[SIZEOF_ARG] = { "git" };
7818                                 int argc = 1;
7820                                 /* When running random commands, initially show the
7821                                  * command in the title. However, it maybe later be
7822                                  * overwritten if a commit line is selected. */
7823                                 string_ncopy(next->ref, cmd, strlen(cmd));
7825                                 if (!argv_from_string(argv, &argc, cmd)) {
7826                                         report("Too many arguments");
7827                                 } else if (!prepare_update(next, argv, NULL)) {
7828                                         report("Failed to format command");
7829                                 } else {
7830                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7831                                 }
7832                         }
7834                         request = REQ_NONE;
7835                         break;
7836                 }
7837                 case REQ_SEARCH:
7838                 case REQ_SEARCH_BACK:
7839                 {
7840                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7841                         char *search = read_prompt(prompt);
7843                         if (search)
7844                                 string_ncopy(opt_search, search, strlen(search));
7845                         else if (*opt_search)
7846                                 request = request == REQ_SEARCH ?
7847                                         REQ_FIND_NEXT :
7848                                         REQ_FIND_PREV;
7849                         else
7850                                 request = REQ_NONE;
7851                         break;
7852                 }
7853                 default:
7854                         break;
7855                 }
7856         }
7858         quit(0);
7860         return 0;