Code

Free blame view data when reloading view
[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 input_status {
144         INPUT_OK,
145         INPUT_SKIP,
146         INPUT_STOP,
147         INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156         int hotkey;
157         const char *text;
158         void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164  * Allocation helpers ... Entering macro hell to never be seen again.
165  */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
168 static type *                                                                   \
169 name(type **mem, size_t size, size_t increase)                                  \
170 {                                                                               \
171         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
172         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173         type *tmp = *mem;                                                       \
174                                                                                 \
175         if (mem == NULL || num_chunks != num_chunks_new) {                      \
176                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177                 if (tmp)                                                        \
178                         *mem = tmp;                                             \
179         }                                                                       \
180                                                                                 \
181         return tmp;                                                             \
184 /*
185  * String helpers
186  */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
191         if (srclen > dstlen - 1)
192                 srclen = dstlen - 1;
194         strncpy(dst, src, srclen);
195         dst[srclen] = 0;
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204         string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static void
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
215         size_t size, pos;
217         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218                 if (src[pos] == '\t') {
219                         size_t expanded = tabsize - (size % tabsize);
221                         if (expanded + size >= dstlen - 1)
222                                 expanded = dstlen - size - 1;
223                         memcpy(dst + size, "        ", expanded);
224                         size += expanded;
225                 } else {
226                         dst[size++] = src[pos];
227                 }
228         }
230         dst[size] = 0;
233 static char *
234 chomp_string(char *name)
236         int namelen;
238         while (isspace(*name))
239                 name++;
241         namelen = strlen(name) - 1;
242         while (namelen > 0 && isspace(name[namelen]))
243                 name[namelen--] = 0;
245         return name;
248 static bool
249 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
251         va_list args;
252         size_t pos = bufpos ? *bufpos : 0;
254         va_start(args, fmt);
255         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
256         va_end(args);
258         if (bufpos)
259                 *bufpos = pos;
261         return pos >= bufsize ? FALSE : TRUE;
264 #define string_format(buf, fmt, args...) \
265         string_nformat(buf, sizeof(buf), NULL, fmt, args)
267 #define string_format_from(buf, from, fmt, args...) \
268         string_nformat(buf, sizeof(buf), from, fmt, args)
270 static int
271 string_enum_compare(const char *str1, const char *str2, int len)
273         size_t i;
275 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
277         /* Diff-Header == DIFF_HEADER */
278         for (i = 0; i < len; i++) {
279                 if (toupper(str1[i]) == toupper(str2[i]))
280                         continue;
282                 if (string_enum_sep(str1[i]) &&
283                     string_enum_sep(str2[i]))
284                         continue;
286                 return str1[i] - str2[i];
287         }
289         return 0;
292 #define enum_equals(entry, str, len) \
293         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
295 struct enum_map {
296         const char *name;
297         int namelen;
298         int value;
299 };
301 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 static char *
304 enum_map_name(const char *name, size_t namelen)
306         static char buf[SIZEOF_STR];
307         int bufpos;
309         for (bufpos = 0; bufpos <= namelen; bufpos++) {
310                 buf[bufpos] = tolower(name[bufpos]);
311                 if (buf[bufpos] == '_')
312                         buf[bufpos] = '-';
313         }
315         buf[bufpos] = 0;
316         return buf;
319 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
321 static bool
322 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
324         size_t namelen = strlen(name);
325         int i;
327         for (i = 0; i < map_size; i++)
328                 if (enum_equals(map[i], name, namelen)) {
329                         *value = map[i].value;
330                         return TRUE;
331                 }
333         return FALSE;
336 #define map_enum(attr, map, name) \
337         map_enum_do(map, ARRAY_SIZE(map), attr, name)
339 #define prefixcmp(str1, str2) \
340         strncmp(str1, str2, STRING_SIZE(str2))
342 static inline int
343 suffixcmp(const char *str, int slen, const char *suffix)
345         size_t len = slen >= 0 ? slen : strlen(str);
346         size_t suffixlen = strlen(suffix);
348         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
352 /*
353  * Unicode / UTF-8 handling
354  *
355  * NOTE: Much of the following code for dealing with Unicode is derived from
356  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
357  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
358  */
360 static inline int
361 unicode_width(unsigned long c, int tab_size)
363         if (c >= 0x1100 &&
364            (c <= 0x115f                         /* Hangul Jamo */
365             || c == 0x2329
366             || c == 0x232a
367             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
368                                                 /* CJK ... Yi */
369             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
370             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
371             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
372             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
373             || (c >= 0xffe0  && c <= 0xffe6)
374             || (c >= 0x20000 && c <= 0x2fffd)
375             || (c >= 0x30000 && c <= 0x3fffd)))
376                 return 2;
378         if (c == '\t')
379                 return tab_size;
381         return 1;
384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
385  * Illegal bytes are set one. */
386 static const unsigned char utf8_bytes[256] = {
387         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,
388         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,
389         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,
390         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,
391         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,
392         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,
393         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,
394         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,
395 };
397 static inline unsigned char
398 utf8_char_length(const char *string, const char *end)
400         int c = *(unsigned char *) string;
402         return utf8_bytes[c];
405 /* Decode UTF-8 multi-byte representation into a Unicode character. */
406 static inline unsigned long
407 utf8_to_unicode(const char *string, size_t length)
409         unsigned long unicode;
411         switch (length) {
412         case 1:
413                 unicode  =   string[0];
414                 break;
415         case 2:
416                 unicode  =  (string[0] & 0x1f) << 6;
417                 unicode +=  (string[1] & 0x3f);
418                 break;
419         case 3:
420                 unicode  =  (string[0] & 0x0f) << 12;
421                 unicode += ((string[1] & 0x3f) << 6);
422                 unicode +=  (string[2] & 0x3f);
423                 break;
424         case 4:
425                 unicode  =  (string[0] & 0x0f) << 18;
426                 unicode += ((string[1] & 0x3f) << 12);
427                 unicode += ((string[2] & 0x3f) << 6);
428                 unicode +=  (string[3] & 0x3f);
429                 break;
430         case 5:
431                 unicode  =  (string[0] & 0x0f) << 24;
432                 unicode += ((string[1] & 0x3f) << 18);
433                 unicode += ((string[2] & 0x3f) << 12);
434                 unicode += ((string[3] & 0x3f) << 6);
435                 unicode +=  (string[4] & 0x3f);
436                 break;
437         case 6:
438                 unicode  =  (string[0] & 0x01) << 30;
439                 unicode += ((string[1] & 0x3f) << 24);
440                 unicode += ((string[2] & 0x3f) << 18);
441                 unicode += ((string[3] & 0x3f) << 12);
442                 unicode += ((string[4] & 0x3f) << 6);
443                 unicode +=  (string[5] & 0x3f);
444                 break;
445         default:
446                 return 0;
447         }
449         /* Invalid characters could return the special 0xfffd value but NUL
450          * should be just as good. */
451         return unicode > 0xffff ? 0 : unicode;
454 /* Calculates how much of string can be shown within the given maximum width
455  * and sets trimmed parameter to non-zero value if all of string could not be
456  * shown. If the reserve flag is TRUE, it will reserve at least one
457  * trailing character, which can be useful when drawing a delimiter.
458  *
459  * Returns the number of bytes to output from string to satisfy max_width. */
460 static size_t
461 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
463         const char *string = *start;
464         const char *end = strchr(string, '\0');
465         unsigned char last_bytes = 0;
466         size_t last_ucwidth = 0;
468         *width = 0;
469         *trimmed = 0;
471         while (string < end) {
472                 unsigned char bytes = utf8_char_length(string, end);
473                 size_t ucwidth;
474                 unsigned long unicode;
476                 if (string + bytes > end)
477                         break;
479                 /* Change representation to figure out whether
480                  * it is a single- or double-width character. */
482                 unicode = utf8_to_unicode(string, bytes);
483                 /* FIXME: Graceful handling of invalid Unicode character. */
484                 if (!unicode)
485                         break;
487                 ucwidth = unicode_width(unicode, tab_size);
488                 if (skip > 0) {
489                         skip -= ucwidth <= skip ? ucwidth : skip;
490                         *start += bytes;
491                 }
492                 *width  += ucwidth;
493                 if (*width > max_width) {
494                         *trimmed = 1;
495                         *width -= ucwidth;
496                         if (reserve && *width == max_width) {
497                                 string -= last_bytes;
498                                 *width -= last_ucwidth;
499                         }
500                         break;
501                 }
503                 string  += bytes;
504                 last_bytes = ucwidth ? bytes : 0;
505                 last_ucwidth = ucwidth;
506         }
508         return string - *start;
512 #define DATE_INFO \
513         DATE_(NO), \
514         DATE_(DEFAULT), \
515         DATE_(LOCAL), \
516         DATE_(RELATIVE), \
517         DATE_(SHORT)
519 enum date {
520 #define DATE_(name) DATE_##name
521         DATE_INFO
522 #undef  DATE_
523 };
525 static const struct enum_map date_map[] = {
526 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
527         DATE_INFO
528 #undef  DATE_
529 };
531 struct time {
532         time_t sec;
533         int tz;
534 };
536 static inline int timecmp(const struct time *t1, const struct time *t2)
538         return t1->sec - t2->sec;
541 static const char *
542 mkdate(const struct time *time, enum date date)
544         static char buf[DATE_COLS + 1];
545         static const struct enum_map reldate[] = {
546                 { "second", 1,                  60 * 2 },
547                 { "minute", 60,                 60 * 60 * 2 },
548                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
549                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
550                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
551                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
552         };
553         struct tm tm;
555         if (!date || !time || !time->sec)
556                 return "";
558         if (date == DATE_RELATIVE) {
559                 struct timeval now;
560                 time_t date = time->sec + time->tz;
561                 time_t seconds;
562                 int i;
564                 gettimeofday(&now, NULL);
565                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
566                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
567                         if (seconds >= reldate[i].value)
568                                 continue;
570                         seconds /= reldate[i].namelen;
571                         if (!string_format(buf, "%ld %s%s %s",
572                                            seconds, reldate[i].name,
573                                            seconds > 1 ? "s" : "",
574                                            now.tv_sec >= date ? "ago" : "ahead"))
575                                 break;
576                         return buf;
577                 }
578         }
580         if (date == DATE_LOCAL) {
581                 time_t date = time->sec + time->tz;
582                 localtime_r(&date, &tm);
583         }
584         else {
585                 gmtime_r(&time->sec, &tm);
586         }
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);
674 static void
675 argv_free(const char *argv[])
677         int argc;
679         if (!argv)
680                 return;
681         for (argc = 0; argv[argc]; argc++)
682                 free((void *) argv[argc]);
683         argv[0] = NULL;
686 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
688 static bool
689 argv_append(const char ***argv, const char *arg)
691         int argc = 0;
693         while (*argv && (*argv)[argc])
694                 argc++;
696         if (!argv_realloc(argv, argc, 2))
697                 return FALSE;
699         (*argv)[argc++] = strdup(arg);
700         (*argv)[argc] = NULL;
701         return TRUE;
704 static bool
705 argv_append_array(const char ***dst_argv, const char *src_argv[])
707         int i;
709         for (i = 0; src_argv && src_argv[i]; i++)
710                 if (!argv_append(dst_argv, src_argv[i]))
711                         return FALSE;
712         return TRUE;
715 static bool
716 argv_copy(const char ***dst, const char *src[])
718         int argc;
720         for (argc = 0; src[argc]; argc++)
721                 if (!argv_append(dst, src[argc]))
722                         return FALSE;
723         return TRUE;
727 /*
728  * Executing external commands.
729  */
731 enum io_type {
732         IO_FD,                  /* File descriptor based IO. */
733         IO_BG,                  /* Execute command in the background. */
734         IO_FG,                  /* Execute command with same std{in,out,err}. */
735         IO_RD,                  /* Read only fork+exec IO. */
736         IO_WR,                  /* Write only fork+exec IO. */
737         IO_AP,                  /* Append fork+exec output to file. */
738 };
740 struct io {
741         int pipe;               /* Pipe end for reading or writing. */
742         pid_t pid;              /* PID of spawned process. */
743         int error;              /* Error status. */
744         char *buf;              /* Read buffer. */
745         size_t bufalloc;        /* Allocated buffer size. */
746         size_t bufsize;         /* Buffer content size. */
747         char *bufpos;           /* Current buffer position. */
748         unsigned int eof:1;     /* Has end of file been reached. */
749 };
751 static void
752 io_init(struct io *io)
754         memset(io, 0, sizeof(*io));
755         io->pipe = -1;
758 static bool
759 io_open(struct io *io, const char *fmt, ...)
761         char name[SIZEOF_STR] = "";
762         bool fits;
763         va_list args;
765         io_init(io);
767         va_start(args, fmt);
768         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
769         va_end(args);
771         if (!fits) {
772                 io->error = ENAMETOOLONG;
773                 return FALSE;
774         }
775         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
776         if (io->pipe == -1)
777                 io->error = errno;
778         return io->pipe != -1;
781 static bool
782 io_kill(struct io *io)
784         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
787 static bool
788 io_done(struct io *io)
790         pid_t pid = io->pid;
792         if (io->pipe != -1)
793                 close(io->pipe);
794         free(io->buf);
795         io_init(io);
797         while (pid > 0) {
798                 int status;
799                 pid_t waiting = waitpid(pid, &status, 0);
801                 if (waiting < 0) {
802                         if (errno == EINTR)
803                                 continue;
804                         io->error = errno;
805                         return FALSE;
806                 }
808                 return waiting == pid &&
809                        !WIFSIGNALED(status) &&
810                        WIFEXITED(status) &&
811                        !WEXITSTATUS(status);
812         }
814         return TRUE;
817 static bool
818 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
820         int pipefds[2] = { -1, -1 };
821         va_list args;
823         io_init(io);
825         if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
826                 io->error = errno;
827                 return FALSE;
828         } else if (type == IO_AP) {
829                 va_start(args, argv);
830                 pipefds[1] = va_arg(args, int);
831                 va_end(args);
832         }
834         if ((io->pid = fork())) {
835                 if (io->pid == -1)
836                         io->error = errno;
837                 if (pipefds[!(type == IO_WR)] != -1)
838                         close(pipefds[!(type == IO_WR)]);
839                 if (io->pid != -1) {
840                         io->pipe = pipefds[!!(type == IO_WR)];
841                         return TRUE;
842                 }
844         } else {
845                 if (type != IO_FG) {
846                         int devnull = open("/dev/null", O_RDWR);
847                         int readfd  = type == IO_WR ? pipefds[0] : devnull;
848                         int writefd = (type == IO_RD || type == IO_AP)
849                                                         ? pipefds[1] : devnull;
851                         dup2(readfd,  STDIN_FILENO);
852                         dup2(writefd, STDOUT_FILENO);
853                         dup2(devnull, STDERR_FILENO);
855                         close(devnull);
856                         if (pipefds[0] != -1)
857                                 close(pipefds[0]);
858                         if (pipefds[1] != -1)
859                                 close(pipefds[1]);
860                 }
862                 if (dir && *dir && chdir(dir) == -1)
863                         exit(errno);
865                 execvp(argv[0], (char *const*) argv);
866                 exit(errno);
867         }
869         if (pipefds[!!(type == IO_WR)] != -1)
870                 close(pipefds[!!(type == IO_WR)]);
871         return FALSE;
874 static bool
875 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
877         struct io io;
879         return io_run(&io, type, dir, argv, fd) && io_done(&io);
882 static bool
883 io_run_bg(const char **argv)
885         return io_complete(IO_BG, argv, NULL, -1);
888 static bool
889 io_run_fg(const char **argv, const char *dir)
891         return io_complete(IO_FG, argv, dir, -1);
894 static bool
895 io_run_append(const char **argv, int fd)
897         return io_complete(IO_AP, argv, NULL, fd);
900 static bool
901 io_eof(struct io *io)
903         return io->eof;
906 static int
907 io_error(struct io *io)
909         return io->error;
912 static char *
913 io_strerror(struct io *io)
915         return strerror(io->error);
918 static bool
919 io_can_read(struct io *io)
921         struct timeval tv = { 0, 500 };
922         fd_set fds;
924         FD_ZERO(&fds);
925         FD_SET(io->pipe, &fds);
927         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
930 static ssize_t
931 io_read(struct io *io, void *buf, size_t bufsize)
933         do {
934                 ssize_t readsize = read(io->pipe, buf, bufsize);
936                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
937                         continue;
938                 else if (readsize == -1)
939                         io->error = errno;
940                 else if (readsize == 0)
941                         io->eof = 1;
942                 return readsize;
943         } while (1);
946 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
948 static char *
949 io_get(struct io *io, int c, bool can_read)
951         char *eol;
952         ssize_t readsize;
954         while (TRUE) {
955                 if (io->bufsize > 0) {
956                         eol = memchr(io->bufpos, c, io->bufsize);
957                         if (eol) {
958                                 char *line = io->bufpos;
960                                 *eol = 0;
961                                 io->bufpos = eol + 1;
962                                 io->bufsize -= io->bufpos - line;
963                                 return line;
964                         }
965                 }
967                 if (io_eof(io)) {
968                         if (io->bufsize) {
969                                 io->bufpos[io->bufsize] = 0;
970                                 io->bufsize = 0;
971                                 return io->bufpos;
972                         }
973                         return NULL;
974                 }
976                 if (!can_read)
977                         return NULL;
979                 if (io->bufsize > 0 && io->bufpos > io->buf)
980                         memmove(io->buf, io->bufpos, io->bufsize);
982                 if (io->bufalloc == io->bufsize) {
983                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
984                                 return NULL;
985                         io->bufalloc += BUFSIZ;
986                 }
988                 io->bufpos = io->buf;
989                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
990                 if (io_error(io))
991                         return NULL;
992                 io->bufsize += readsize;
993         }
996 static bool
997 io_write(struct io *io, const void *buf, size_t bufsize)
999         size_t written = 0;
1001         while (!io_error(io) && written < bufsize) {
1002                 ssize_t size;
1004                 size = write(io->pipe, buf + written, bufsize - written);
1005                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1006                         continue;
1007                 else if (size == -1)
1008                         io->error = errno;
1009                 else
1010                         written += size;
1011         }
1013         return written == bufsize;
1016 static bool
1017 io_read_buf(struct io *io, char buf[], size_t bufsize)
1019         char *result = io_get(io, '\n', TRUE);
1021         if (result) {
1022                 result = chomp_string(result);
1023                 string_ncopy_do(buf, bufsize, result, strlen(result));
1024         }
1026         return io_done(io) && result;
1029 static bool
1030 io_run_buf(const char **argv, char buf[], size_t bufsize)
1032         struct io io;
1034         return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1037 static int
1038 io_load(struct io *io, const char *separators,
1039         int (*read_property)(char *, size_t, char *, size_t))
1041         char *name;
1042         int state = OK;
1044         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1045                 char *value;
1046                 size_t namelen;
1047                 size_t valuelen;
1049                 name = chomp_string(name);
1050                 namelen = strcspn(name, separators);
1052                 if (name[namelen]) {
1053                         name[namelen] = 0;
1054                         value = chomp_string(name + namelen + 1);
1055                         valuelen = strlen(value);
1057                 } else {
1058                         value = "";
1059                         valuelen = 0;
1060                 }
1062                 state = read_property(name, namelen, value, valuelen);
1063         }
1065         if (state != ERR && io_error(io))
1066                 state = ERR;
1067         io_done(io);
1069         return state;
1072 static int
1073 io_run_load(const char **argv, const char *separators,
1074             int (*read_property)(char *, size_t, char *, size_t))
1076         struct io io;
1078         if (!io_run(&io, IO_RD, NULL, argv))
1079                 return ERR;
1080         return io_load(&io, separators, read_property);
1084 /*
1085  * User requests
1086  */
1088 #define REQ_INFO \
1089         /* XXX: Keep the view request first and in sync with views[]. */ \
1090         REQ_GROUP("View switching") \
1091         REQ_(VIEW_MAIN,         "Show main view"), \
1092         REQ_(VIEW_DIFF,         "Show diff view"), \
1093         REQ_(VIEW_LOG,          "Show log view"), \
1094         REQ_(VIEW_TREE,         "Show tree view"), \
1095         REQ_(VIEW_BLOB,         "Show blob view"), \
1096         REQ_(VIEW_BLAME,        "Show blame view"), \
1097         REQ_(VIEW_BRANCH,       "Show branch view"), \
1098         REQ_(VIEW_HELP,         "Show help page"), \
1099         REQ_(VIEW_PAGER,        "Show pager view"), \
1100         REQ_(VIEW_STATUS,       "Show status view"), \
1101         REQ_(VIEW_STAGE,        "Show stage view"), \
1102         \
1103         REQ_GROUP("View manipulation") \
1104         REQ_(ENTER,             "Enter current line and scroll"), \
1105         REQ_(NEXT,              "Move to next"), \
1106         REQ_(PREVIOUS,          "Move to previous"), \
1107         REQ_(PARENT,            "Move to parent"), \
1108         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1109         REQ_(REFRESH,           "Reload and refresh"), \
1110         REQ_(MAXIMIZE,          "Maximize the current view"), \
1111         REQ_(VIEW_CLOSE,        "Close the current view"), \
1112         REQ_(QUIT,              "Close all views and quit"), \
1113         \
1114         REQ_GROUP("View specific requests") \
1115         REQ_(STATUS_UPDATE,     "Update file status"), \
1116         REQ_(STATUS_REVERT,     "Revert file changes"), \
1117         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1118         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1119         \
1120         REQ_GROUP("Cursor navigation") \
1121         REQ_(MOVE_UP,           "Move cursor one line up"), \
1122         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1123         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1124         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1125         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1126         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1127         \
1128         REQ_GROUP("Scrolling") \
1129         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1130         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1131         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1132         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1133         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1134         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1135         \
1136         REQ_GROUP("Searching") \
1137         REQ_(SEARCH,            "Search the view"), \
1138         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1139         REQ_(FIND_NEXT,         "Find next search match"), \
1140         REQ_(FIND_PREV,         "Find previous search match"), \
1141         \
1142         REQ_GROUP("Option manipulation") \
1143         REQ_(OPTIONS,           "Open option menu"), \
1144         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1145         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1146         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1147         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1148         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1149         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1150         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1151         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1152         \
1153         REQ_GROUP("Misc") \
1154         REQ_(PROMPT,            "Bring up the prompt"), \
1155         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1156         REQ_(SHOW_VERSION,      "Show version information"), \
1157         REQ_(STOP_LOADING,      "Stop all loading views"), \
1158         REQ_(EDIT,              "Open in editor"), \
1159         REQ_(NONE,              "Do nothing")
1162 /* User action requests. */
1163 enum request {
1164 #define REQ_GROUP(help)
1165 #define REQ_(req, help) REQ_##req
1167         /* Offset all requests to avoid conflicts with ncurses getch values. */
1168         REQ_UNKNOWN = KEY_MAX + 1,
1169         REQ_OFFSET,
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_UNKNOWN;
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;
1236 static const char **opt_diff_args       = NULL;
1237 static const char **opt_rev_args        = NULL;
1238 static const char **opt_file_args       = NULL;
1240 #define is_initial_commit()     (!get_ref_head())
1241 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1244 /*
1245  * Line-oriented content detection.
1246  */
1248 #define LINE_INFO \
1249 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1250 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1251 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1252 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1253 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1254 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1255 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1261 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1262 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1263 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1264 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1265 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1266 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1267 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1268 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1269 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1270 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1271 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1272 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1273 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1274 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1275 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1276 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1277 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1278 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1279 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1280 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1281 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1282 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1283 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1284 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1285 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1286 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1287 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1288 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1289 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1290 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1291 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1292 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1293 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1294 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1295 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1296 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1297 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1298 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1299 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1300 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1301 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1302 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1303 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1304 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1305 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1306 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1307 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1309 enum line_type {
1310 #define LINE(type, line, fg, bg, attr) \
1311         LINE_##type
1312         LINE_INFO,
1313         LINE_NONE
1314 #undef  LINE
1315 };
1317 struct line_info {
1318         const char *name;       /* Option name. */
1319         int namelen;            /* Size of option name. */
1320         const char *line;       /* The start of line to match. */
1321         int linelen;            /* Size of string to match. */
1322         int fg, bg, attr;       /* Color and text attributes for the lines. */
1323 };
1325 static struct line_info line_info[] = {
1326 #define LINE(type, line, fg, bg, attr) \
1327         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1328         LINE_INFO
1329 #undef  LINE
1330 };
1332 static enum line_type
1333 get_line_type(const char *line)
1335         int linelen = strlen(line);
1336         enum line_type type;
1338         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1339                 /* Case insensitive search matches Signed-off-by lines better. */
1340                 if (linelen >= line_info[type].linelen &&
1341                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1342                         return type;
1344         return LINE_DEFAULT;
1347 static inline int
1348 get_line_attr(enum line_type type)
1350         assert(type < ARRAY_SIZE(line_info));
1351         return COLOR_PAIR(type) | line_info[type].attr;
1354 static struct line_info *
1355 get_line_info(const char *name)
1357         size_t namelen = strlen(name);
1358         enum line_type type;
1360         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1361                 if (enum_equals(line_info[type], name, namelen))
1362                         return &line_info[type];
1364         return NULL;
1367 static void
1368 init_colors(void)
1370         int default_bg = line_info[LINE_DEFAULT].bg;
1371         int default_fg = line_info[LINE_DEFAULT].fg;
1372         enum line_type type;
1374         start_color();
1376         if (assume_default_colors(default_fg, default_bg) == ERR) {
1377                 default_bg = COLOR_BLACK;
1378                 default_fg = COLOR_WHITE;
1379         }
1381         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1382                 struct line_info *info = &line_info[type];
1383                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1384                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1386                 init_pair(type, fg, bg);
1387         }
1390 struct line {
1391         enum line_type type;
1393         /* State flags */
1394         unsigned int selected:1;
1395         unsigned int dirty:1;
1396         unsigned int cleareol:1;
1397         unsigned int other:16;
1399         void *data;             /* User data */
1400 };
1403 /*
1404  * Keys
1405  */
1407 struct keybinding {
1408         int alias;
1409         enum request request;
1410 };
1412 static struct keybinding default_keybindings[] = {
1413         /* View switching */
1414         { 'm',          REQ_VIEW_MAIN },
1415         { 'd',          REQ_VIEW_DIFF },
1416         { 'l',          REQ_VIEW_LOG },
1417         { 't',          REQ_VIEW_TREE },
1418         { 'f',          REQ_VIEW_BLOB },
1419         { 'B',          REQ_VIEW_BLAME },
1420         { 'H',          REQ_VIEW_BRANCH },
1421         { 'p',          REQ_VIEW_PAGER },
1422         { 'h',          REQ_VIEW_HELP },
1423         { 'S',          REQ_VIEW_STATUS },
1424         { 'c',          REQ_VIEW_STAGE },
1426         /* View manipulation */
1427         { 'q',          REQ_VIEW_CLOSE },
1428         { KEY_TAB,      REQ_VIEW_NEXT },
1429         { KEY_RETURN,   REQ_ENTER },
1430         { KEY_UP,       REQ_PREVIOUS },
1431         { KEY_DOWN,     REQ_NEXT },
1432         { 'R',          REQ_REFRESH },
1433         { KEY_F(5),     REQ_REFRESH },
1434         { 'O',          REQ_MAXIMIZE },
1436         /* Cursor navigation */
1437         { 'k',          REQ_MOVE_UP },
1438         { 'j',          REQ_MOVE_DOWN },
1439         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1440         { KEY_END,      REQ_MOVE_LAST_LINE },
1441         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1442         { ' ',          REQ_MOVE_PAGE_DOWN },
1443         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1444         { 'b',          REQ_MOVE_PAGE_UP },
1445         { '-',          REQ_MOVE_PAGE_UP },
1447         /* Scrolling */
1448         { KEY_LEFT,     REQ_SCROLL_LEFT },
1449         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1450         { KEY_IC,       REQ_SCROLL_LINE_UP },
1451         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1452         { 'w',          REQ_SCROLL_PAGE_UP },
1453         { 's',          REQ_SCROLL_PAGE_DOWN },
1455         /* Searching */
1456         { '/',          REQ_SEARCH },
1457         { '?',          REQ_SEARCH_BACK },
1458         { 'n',          REQ_FIND_NEXT },
1459         { 'N',          REQ_FIND_PREV },
1461         /* Misc */
1462         { 'Q',          REQ_QUIT },
1463         { 'z',          REQ_STOP_LOADING },
1464         { 'v',          REQ_SHOW_VERSION },
1465         { 'r',          REQ_SCREEN_REDRAW },
1466         { 'o',          REQ_OPTIONS },
1467         { '.',          REQ_TOGGLE_LINENO },
1468         { 'D',          REQ_TOGGLE_DATE },
1469         { 'A',          REQ_TOGGLE_AUTHOR },
1470         { 'g',          REQ_TOGGLE_REV_GRAPH },
1471         { 'F',          REQ_TOGGLE_REFS },
1472         { 'I',          REQ_TOGGLE_SORT_ORDER },
1473         { 'i',          REQ_TOGGLE_SORT_FIELD },
1474         { ':',          REQ_PROMPT },
1475         { 'u',          REQ_STATUS_UPDATE },
1476         { '!',          REQ_STATUS_REVERT },
1477         { 'M',          REQ_STATUS_MERGE },
1478         { '@',          REQ_STAGE_NEXT },
1479         { ',',          REQ_PARENT },
1480         { 'e',          REQ_EDIT },
1481 };
1483 #define KEYMAP_INFO \
1484         KEYMAP_(GENERIC), \
1485         KEYMAP_(MAIN), \
1486         KEYMAP_(DIFF), \
1487         KEYMAP_(LOG), \
1488         KEYMAP_(TREE), \
1489         KEYMAP_(BLOB), \
1490         KEYMAP_(BLAME), \
1491         KEYMAP_(BRANCH), \
1492         KEYMAP_(PAGER), \
1493         KEYMAP_(HELP), \
1494         KEYMAP_(STATUS), \
1495         KEYMAP_(STAGE)
1497 enum keymap {
1498 #define KEYMAP_(name) KEYMAP_##name
1499         KEYMAP_INFO
1500 #undef  KEYMAP_
1501 };
1503 static const struct enum_map keymap_table[] = {
1504 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1505         KEYMAP_INFO
1506 #undef  KEYMAP_
1507 };
1509 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1511 struct keybinding_table {
1512         struct keybinding *data;
1513         size_t size;
1514 };
1516 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1518 static void
1519 add_keybinding(enum keymap keymap, enum request request, int key)
1521         struct keybinding_table *table = &keybindings[keymap];
1522         size_t i;
1524         for (i = 0; i < keybindings[keymap].size; i++) {
1525                 if (keybindings[keymap].data[i].alias == key) {
1526                         keybindings[keymap].data[i].request = request;
1527                         return;
1528                 }
1529         }
1531         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1532         if (!table->data)
1533                 die("Failed to allocate keybinding");
1534         table->data[table->size].alias = key;
1535         table->data[table->size++].request = request;
1537         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1538                 int i;
1540                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1541                         if (default_keybindings[i].alias == key)
1542                                 default_keybindings[i].request = REQ_NONE;
1543         }
1546 /* Looks for a key binding first in the given map, then in the generic map, and
1547  * lastly in the default keybindings. */
1548 static enum request
1549 get_keybinding(enum keymap keymap, int key)
1551         size_t i;
1553         for (i = 0; i < keybindings[keymap].size; i++)
1554                 if (keybindings[keymap].data[i].alias == key)
1555                         return keybindings[keymap].data[i].request;
1557         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1558                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1559                         return keybindings[KEYMAP_GENERIC].data[i].request;
1561         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1562                 if (default_keybindings[i].alias == key)
1563                         return default_keybindings[i].request;
1565         return (enum request) key;
1569 struct key {
1570         const char *name;
1571         int value;
1572 };
1574 static const struct key key_table[] = {
1575         { "Enter",      KEY_RETURN },
1576         { "Space",      ' ' },
1577         { "Backspace",  KEY_BACKSPACE },
1578         { "Tab",        KEY_TAB },
1579         { "Escape",     KEY_ESC },
1580         { "Left",       KEY_LEFT },
1581         { "Right",      KEY_RIGHT },
1582         { "Up",         KEY_UP },
1583         { "Down",       KEY_DOWN },
1584         { "Insert",     KEY_IC },
1585         { "Delete",     KEY_DC },
1586         { "Hash",       '#' },
1587         { "Home",       KEY_HOME },
1588         { "End",        KEY_END },
1589         { "PageUp",     KEY_PPAGE },
1590         { "PageDown",   KEY_NPAGE },
1591         { "F1",         KEY_F(1) },
1592         { "F2",         KEY_F(2) },
1593         { "F3",         KEY_F(3) },
1594         { "F4",         KEY_F(4) },
1595         { "F5",         KEY_F(5) },
1596         { "F6",         KEY_F(6) },
1597         { "F7",         KEY_F(7) },
1598         { "F8",         KEY_F(8) },
1599         { "F9",         KEY_F(9) },
1600         { "F10",        KEY_F(10) },
1601         { "F11",        KEY_F(11) },
1602         { "F12",        KEY_F(12) },
1603 };
1605 static int
1606 get_key_value(const char *name)
1608         int i;
1610         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1611                 if (!strcasecmp(key_table[i].name, name))
1612                         return key_table[i].value;
1614         if (strlen(name) == 1 && isprint(*name))
1615                 return (int) *name;
1617         return ERR;
1620 static const char *
1621 get_key_name(int key_value)
1623         static char key_char[] = "'X'";
1624         const char *seq = NULL;
1625         int key;
1627         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1628                 if (key_table[key].value == key_value)
1629                         seq = key_table[key].name;
1631         if (seq == NULL &&
1632             key_value < 127 &&
1633             isprint(key_value)) {
1634                 key_char[1] = (char) key_value;
1635                 seq = key_char;
1636         }
1638         return seq ? seq : "(no key)";
1641 static bool
1642 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1644         const char *sep = *pos > 0 ? ", " : "";
1645         const char *keyname = get_key_name(keybinding->alias);
1647         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1650 static bool
1651 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1652                            enum keymap keymap, bool all)
1654         int i;
1656         for (i = 0; i < keybindings[keymap].size; i++) {
1657                 if (keybindings[keymap].data[i].request == request) {
1658                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1659                                 return FALSE;
1660                         if (!all)
1661                                 break;
1662                 }
1663         }
1665         return TRUE;
1668 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1670 static const char *
1671 get_keys(enum keymap keymap, enum request request, bool all)
1673         static char buf[BUFSIZ];
1674         size_t pos = 0;
1675         int i;
1677         buf[pos] = 0;
1679         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1680                 return "Too many keybindings!";
1681         if (pos > 0 && !all)
1682                 return buf;
1684         if (keymap != KEYMAP_GENERIC) {
1685                 /* Only the generic keymap includes the default keybindings when
1686                  * listing all keys. */
1687                 if (all)
1688                         return buf;
1690                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1691                         return "Too many keybindings!";
1692                 if (pos)
1693                         return buf;
1694         }
1696         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1697                 if (default_keybindings[i].request == request) {
1698                         if (!append_key(buf, &pos, &default_keybindings[i]))
1699                                 return "Too many keybindings!";
1700                         if (!all)
1701                                 return buf;
1702                 }
1703         }
1705         return buf;
1708 struct run_request {
1709         enum keymap keymap;
1710         int key;
1711         const char **argv;
1712 };
1714 static struct run_request *run_request;
1715 static size_t run_requests;
1717 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1719 static enum request
1720 add_run_request(enum keymap keymap, int key, const char **argv)
1722         struct run_request *req;
1724         if (!realloc_run_requests(&run_request, run_requests, 1))
1725                 return REQ_NONE;
1727         req = &run_request[run_requests];
1728         req->keymap = keymap;
1729         req->key = key;
1730         req->argv = NULL;
1732         if (!argv_copy(&req->argv, argv))
1733                 return REQ_NONE;
1735         return REQ_NONE + ++run_requests;
1738 static struct run_request *
1739 get_run_request(enum request request)
1741         if (request <= REQ_NONE)
1742                 return NULL;
1743         return &run_request[request - REQ_NONE - 1];
1746 static void
1747 add_builtin_run_requests(void)
1749         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1750         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1751         const char *commit[] = { "git", "commit", NULL };
1752         const char *gc[] = { "git", "gc", NULL };
1753         struct run_request reqs[] = {
1754                 { KEYMAP_MAIN,    'C', cherry_pick },
1755                 { KEYMAP_STATUS,  'C', commit },
1756                 { KEYMAP_BRANCH,  'C', checkout },
1757                 { KEYMAP_GENERIC, 'G', gc },
1758         };
1759         int i;
1761         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1762                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1764                 if (req != reqs[i].key)
1765                         continue;
1766                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1767                 if (req != REQ_NONE)
1768                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1769         }
1772 /*
1773  * User config file handling.
1774  */
1776 static int   config_lineno;
1777 static bool  config_errors;
1778 static const char *config_msg;
1780 static const struct enum_map color_map[] = {
1781 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1782         COLOR_MAP(DEFAULT),
1783         COLOR_MAP(BLACK),
1784         COLOR_MAP(BLUE),
1785         COLOR_MAP(CYAN),
1786         COLOR_MAP(GREEN),
1787         COLOR_MAP(MAGENTA),
1788         COLOR_MAP(RED),
1789         COLOR_MAP(WHITE),
1790         COLOR_MAP(YELLOW),
1791 };
1793 static const struct enum_map attr_map[] = {
1794 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1795         ATTR_MAP(NORMAL),
1796         ATTR_MAP(BLINK),
1797         ATTR_MAP(BOLD),
1798         ATTR_MAP(DIM),
1799         ATTR_MAP(REVERSE),
1800         ATTR_MAP(STANDOUT),
1801         ATTR_MAP(UNDERLINE),
1802 };
1804 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1806 static int parse_step(double *opt, const char *arg)
1808         *opt = atoi(arg);
1809         if (!strchr(arg, '%'))
1810                 return OK;
1812         /* "Shift down" so 100% and 1 does not conflict. */
1813         *opt = (*opt - 1) / 100;
1814         if (*opt >= 1.0) {
1815                 *opt = 0.99;
1816                 config_msg = "Step value larger than 100%";
1817                 return ERR;
1818         }
1819         if (*opt < 0.0) {
1820                 *opt = 1;
1821                 config_msg = "Invalid step value";
1822                 return ERR;
1823         }
1824         return OK;
1827 static int
1828 parse_int(int *opt, const char *arg, int min, int max)
1830         int value = atoi(arg);
1832         if (min <= value && value <= max) {
1833                 *opt = value;
1834                 return OK;
1835         }
1837         config_msg = "Integer value out of bound";
1838         return ERR;
1841 static bool
1842 set_color(int *color, const char *name)
1844         if (map_enum(color, color_map, name))
1845                 return TRUE;
1846         if (!prefixcmp(name, "color"))
1847                 return parse_int(color, name + 5, 0, 255) == OK;
1848         return FALSE;
1851 /* Wants: object fgcolor bgcolor [attribute] */
1852 static int
1853 option_color_command(int argc, const char *argv[])
1855         struct line_info *info;
1857         if (argc < 3) {
1858                 config_msg = "Wrong number of arguments given to color command";
1859                 return ERR;
1860         }
1862         info = get_line_info(argv[0]);
1863         if (!info) {
1864                 static const struct enum_map obsolete[] = {
1865                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1866                         ENUM_MAP("main-date",   LINE_DATE),
1867                         ENUM_MAP("main-author", LINE_AUTHOR),
1868                 };
1869                 int index;
1871                 if (!map_enum(&index, obsolete, argv[0])) {
1872                         config_msg = "Unknown color name";
1873                         return ERR;
1874                 }
1875                 info = &line_info[index];
1876         }
1878         if (!set_color(&info->fg, argv[1]) ||
1879             !set_color(&info->bg, argv[2])) {
1880                 config_msg = "Unknown color";
1881                 return ERR;
1882         }
1884         info->attr = 0;
1885         while (argc-- > 3) {
1886                 int attr;
1888                 if (!set_attribute(&attr, argv[argc])) {
1889                         config_msg = "Unknown attribute";
1890                         return ERR;
1891                 }
1892                 info->attr |= attr;
1893         }
1895         return OK;
1898 static int parse_bool(bool *opt, const char *arg)
1900         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1901                 ? TRUE : FALSE;
1902         return OK;
1905 static int parse_enum_do(unsigned int *opt, const char *arg,
1906                          const struct enum_map *map, size_t map_size)
1908         bool is_true;
1910         assert(map_size > 1);
1912         if (map_enum_do(map, map_size, (int *) opt, arg))
1913                 return OK;
1915         if (parse_bool(&is_true, arg) != OK)
1916                 return ERR;
1918         *opt = is_true ? map[1].value : map[0].value;
1919         return OK;
1922 #define parse_enum(opt, arg, map) \
1923         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1925 static int
1926 parse_string(char *opt, const char *arg, size_t optsize)
1928         int arglen = strlen(arg);
1930         switch (arg[0]) {
1931         case '\"':
1932         case '\'':
1933                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1934                         config_msg = "Unmatched quotation";
1935                         return ERR;
1936                 }
1937                 arg += 1; arglen -= 2;
1938         default:
1939                 string_ncopy_do(opt, optsize, arg, arglen);
1940                 return OK;
1941         }
1944 /* Wants: name = value */
1945 static int
1946 option_set_command(int argc, const char *argv[])
1948         if (argc != 3) {
1949                 config_msg = "Wrong number of arguments given to set command";
1950                 return ERR;
1951         }
1953         if (strcmp(argv[1], "=")) {
1954                 config_msg = "No value assigned";
1955                 return ERR;
1956         }
1958         if (!strcmp(argv[0], "show-author"))
1959                 return parse_enum(&opt_author, argv[2], author_map);
1961         if (!strcmp(argv[0], "show-date"))
1962                 return parse_enum(&opt_date, argv[2], date_map);
1964         if (!strcmp(argv[0], "show-rev-graph"))
1965                 return parse_bool(&opt_rev_graph, argv[2]);
1967         if (!strcmp(argv[0], "show-refs"))
1968                 return parse_bool(&opt_show_refs, argv[2]);
1970         if (!strcmp(argv[0], "show-line-numbers"))
1971                 return parse_bool(&opt_line_number, argv[2]);
1973         if (!strcmp(argv[0], "line-graphics"))
1974                 return parse_bool(&opt_line_graphics, argv[2]);
1976         if (!strcmp(argv[0], "line-number-interval"))
1977                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1979         if (!strcmp(argv[0], "author-width"))
1980                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1982         if (!strcmp(argv[0], "horizontal-scroll"))
1983                 return parse_step(&opt_hscroll, argv[2]);
1985         if (!strcmp(argv[0], "split-view-height"))
1986                 return parse_step(&opt_scale_split_view, argv[2]);
1988         if (!strcmp(argv[0], "tab-size"))
1989                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1991         if (!strcmp(argv[0], "commit-encoding"))
1992                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1994         config_msg = "Unknown variable name";
1995         return ERR;
1998 /* Wants: mode request key */
1999 static int
2000 option_bind_command(int argc, const char *argv[])
2002         enum request request;
2003         int keymap = -1;
2004         int key;
2006         if (argc < 3) {
2007                 config_msg = "Wrong number of arguments given to bind command";
2008                 return ERR;
2009         }
2011         if (!set_keymap(&keymap, argv[0])) {
2012                 config_msg = "Unknown key map";
2013                 return ERR;
2014         }
2016         key = get_key_value(argv[1]);
2017         if (key == ERR) {
2018                 config_msg = "Unknown key";
2019                 return ERR;
2020         }
2022         request = get_request(argv[2]);
2023         if (request == REQ_UNKNOWN) {
2024                 static const struct enum_map obsolete[] = {
2025                         ENUM_MAP("cherry-pick",         REQ_NONE),
2026                         ENUM_MAP("screen-resize",       REQ_NONE),
2027                         ENUM_MAP("tree-parent",         REQ_PARENT),
2028                 };
2029                 int alias;
2031                 if (map_enum(&alias, obsolete, argv[2])) {
2032                         if (alias != REQ_NONE)
2033                                 add_keybinding(keymap, alias, key);
2034                         config_msg = "Obsolete request name";
2035                         return ERR;
2036                 }
2037         }
2038         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2039                 request = add_run_request(keymap, key, argv + 2);
2040         if (request == REQ_UNKNOWN) {
2041                 config_msg = "Unknown request name";
2042                 return ERR;
2043         }
2045         add_keybinding(keymap, request, key);
2047         return OK;
2050 static int
2051 set_option(const char *opt, char *value)
2053         const char *argv[SIZEOF_ARG];
2054         int argc = 0;
2056         if (!argv_from_string(argv, &argc, value)) {
2057                 config_msg = "Too many option arguments";
2058                 return ERR;
2059         }
2061         if (!strcmp(opt, "color"))
2062                 return option_color_command(argc, argv);
2064         if (!strcmp(opt, "set"))
2065                 return option_set_command(argc, argv);
2067         if (!strcmp(opt, "bind"))
2068                 return option_bind_command(argc, argv);
2070         config_msg = "Unknown option command";
2071         return ERR;
2074 static int
2075 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2077         int status = OK;
2079         config_lineno++;
2080         config_msg = "Internal error";
2082         /* Check for comment markers, since read_properties() will
2083          * only ensure opt and value are split at first " \t". */
2084         optlen = strcspn(opt, "#");
2085         if (optlen == 0)
2086                 return OK;
2088         if (opt[optlen] != 0) {
2089                 config_msg = "No option value";
2090                 status = ERR;
2092         }  else {
2093                 /* Look for comment endings in the value. */
2094                 size_t len = strcspn(value, "#");
2096                 if (len < valuelen) {
2097                         valuelen = len;
2098                         value[valuelen] = 0;
2099                 }
2101                 status = set_option(opt, value);
2102         }
2104         if (status == ERR) {
2105                 warn("Error on line %d, near '%.*s': %s",
2106                      config_lineno, (int) optlen, opt, config_msg);
2107                 config_errors = TRUE;
2108         }
2110         /* Always keep going if errors are encountered. */
2111         return OK;
2114 static void
2115 load_option_file(const char *path)
2117         struct io io;
2119         /* It's OK that the file doesn't exist. */
2120         if (!io_open(&io, "%s", path))
2121                 return;
2123         config_lineno = 0;
2124         config_errors = FALSE;
2126         if (io_load(&io, " \t", read_option) == ERR ||
2127             config_errors == TRUE)
2128                 warn("Errors while loading %s.", path);
2131 static int
2132 load_options(void)
2134         const char *home = getenv("HOME");
2135         const char *tigrc_user = getenv("TIGRC_USER");
2136         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2137         char buf[SIZEOF_STR];
2139         if (!tigrc_system)
2140                 tigrc_system = SYSCONFDIR "/tigrc";
2141         load_option_file(tigrc_system);
2143         if (!tigrc_user) {
2144                 if (!home || !string_format(buf, "%s/.tigrc", home))
2145                         return ERR;
2146                 tigrc_user = buf;
2147         }
2148         load_option_file(tigrc_user);
2150         /* Add _after_ loading config files to avoid adding run requests
2151          * that conflict with keybindings. */
2152         add_builtin_run_requests();
2154         return OK;
2158 /*
2159  * The viewer
2160  */
2162 struct view;
2163 struct view_ops;
2165 /* The display array of active views and the index of the current view. */
2166 static struct view *display[2];
2167 static unsigned int current_view;
2169 #define foreach_displayed_view(view, i) \
2170         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2172 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2174 /* Current head and commit ID */
2175 static char ref_blob[SIZEOF_REF]        = "";
2176 static char ref_commit[SIZEOF_REF]      = "HEAD";
2177 static char ref_head[SIZEOF_REF]        = "HEAD";
2178 static char ref_branch[SIZEOF_REF]      = "";
2180 enum view_type {
2181         VIEW_MAIN,
2182         VIEW_DIFF,
2183         VIEW_LOG,
2184         VIEW_TREE,
2185         VIEW_BLOB,
2186         VIEW_BLAME,
2187         VIEW_BRANCH,
2188         VIEW_HELP,
2189         VIEW_PAGER,
2190         VIEW_STATUS,
2191         VIEW_STAGE,
2192 };
2194 struct view {
2195         enum view_type type;    /* View type */
2196         const char *name;       /* View name */
2197         const char *cmd_env;    /* Command line set via environment */
2198         const char *id;         /* Points to either of ref_{head,commit,blob} */
2200         struct view_ops *ops;   /* View operations */
2202         enum keymap keymap;     /* What keymap does this view have */
2203         bool git_dir;           /* Whether the view requires a git directory. */
2205         char ref[SIZEOF_REF];   /* Hovered commit reference */
2206         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2208         int height, width;      /* The width and height of the main window */
2209         WINDOW *win;            /* The main window */
2210         WINDOW *title;          /* The title window living below the main window */
2212         /* Navigation */
2213         unsigned long offset;   /* Offset of the window top */
2214         unsigned long yoffset;  /* Offset from the window side. */
2215         unsigned long lineno;   /* Current line number */
2216         unsigned long p_offset; /* Previous offset of the window top */
2217         unsigned long p_yoffset;/* Previous offset from the window side */
2218         unsigned long p_lineno; /* Previous current line number */
2219         bool p_restore;         /* Should the previous position be restored. */
2221         /* Searching */
2222         char grep[SIZEOF_STR];  /* Search string */
2223         regex_t *regex;         /* Pre-compiled regexp */
2225         /* If non-NULL, points to the view that opened this view. If this view
2226          * is closed tig will switch back to the parent view. */
2227         struct view *parent;
2228         struct view *prev;
2230         /* Buffering */
2231         size_t lines;           /* Total number of lines */
2232         struct line *line;      /* Line index */
2233         unsigned int digits;    /* Number of digits in the lines member. */
2235         /* Drawing */
2236         struct line *curline;   /* Line currently being drawn. */
2237         enum line_type curtype; /* Attribute currently used for drawing. */
2238         unsigned long col;      /* Column when drawing. */
2239         bool has_scrolled;      /* View was scrolled. */
2241         /* Loading */
2242         const char **argv;      /* Shell command arguments. */
2243         const char *dir;        /* Directory from which to execute. */
2244         struct io io;
2245         struct io *pipe;
2246         time_t start_time;
2247         time_t update_secs;
2248 };
2250 struct view_ops {
2251         /* What type of content being displayed. Used in the title bar. */
2252         const char *type;
2253         /* Default command arguments. */
2254         const char **argv;
2255         /* Open and reads in all view content. */
2256         bool (*open)(struct view *view);
2257         /* Read one line; updates view->line. */
2258         bool (*read)(struct view *view, char *data);
2259         /* Draw one line; @lineno must be < view->height. */
2260         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2261         /* Depending on view handle a special requests. */
2262         enum request (*request)(struct view *view, enum request request, struct line *line);
2263         /* Search for regexp in a line. */
2264         bool (*grep)(struct view *view, struct line *line);
2265         /* Select line */
2266         void (*select)(struct view *view, struct line *line);
2267         /* Prepare view for loading */
2268         bool (*prepare)(struct view *view);
2269 };
2271 static struct view_ops blame_ops;
2272 static struct view_ops blob_ops;
2273 static struct view_ops diff_ops;
2274 static struct view_ops help_ops;
2275 static struct view_ops log_ops;
2276 static struct view_ops main_ops;
2277 static struct view_ops pager_ops;
2278 static struct view_ops stage_ops;
2279 static struct view_ops status_ops;
2280 static struct view_ops tree_ops;
2281 static struct view_ops branch_ops;
2283 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2284         { type, name, #env, ref, ops, map, git }
2286 #define VIEW_(id, name, ops, git, ref) \
2287         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2289 static struct view views[] = {
2290         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2291         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2292         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2293         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2294         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2295         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2296         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2297         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2298         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2299         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2300         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2301 };
2303 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2305 #define foreach_view(view, i) \
2306         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2308 #define view_is_displayed(view) \
2309         (view == display[0] || view == display[1])
2311 static enum request
2312 view_request(struct view *view, enum request request)
2314         if (!view || !view->lines)
2315                 return request;
2316         return view->ops->request(view, request, &view->line[view->lineno]);
2320 /*
2321  * View drawing.
2322  */
2324 static inline void
2325 set_view_attr(struct view *view, enum line_type type)
2327         if (!view->curline->selected && view->curtype != type) {
2328                 (void) wattrset(view->win, get_line_attr(type));
2329                 wchgat(view->win, -1, 0, type, NULL);
2330                 view->curtype = type;
2331         }
2334 static int
2335 draw_chars(struct view *view, enum line_type type, const char *string,
2336            int max_len, bool use_tilde)
2338         static char out_buffer[BUFSIZ * 2];
2339         int len = 0;
2340         int col = 0;
2341         int trimmed = FALSE;
2342         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2344         if (max_len <= 0)
2345                 return 0;
2347         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2349         set_view_attr(view, type);
2350         if (len > 0) {
2351                 if (opt_iconv_out != ICONV_NONE) {
2352                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2353                         size_t inlen = len + 1;
2355                         char *outbuf = out_buffer;
2356                         size_t outlen = sizeof(out_buffer);
2358                         size_t ret;
2360                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2361                         if (ret != (size_t) -1) {
2362                                 string = out_buffer;
2363                                 len = sizeof(out_buffer) - outlen;
2364                         }
2365                 }
2367                 waddnstr(view->win, string, len);
2368         }
2369         if (trimmed && use_tilde) {
2370                 set_view_attr(view, LINE_DELIMITER);
2371                 waddch(view->win, '~');
2372                 col++;
2373         }
2375         return col;
2378 static int
2379 draw_space(struct view *view, enum line_type type, int max, int spaces)
2381         static char space[] = "                    ";
2382         int col = 0;
2384         spaces = MIN(max, spaces);
2386         while (spaces > 0) {
2387                 int len = MIN(spaces, sizeof(space) - 1);
2389                 col += draw_chars(view, type, space, len, FALSE);
2390                 spaces -= len;
2391         }
2393         return col;
2396 static bool
2397 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2399         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2400         return view->width + view->yoffset <= view->col;
2403 static bool
2404 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2406         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2407         int max = view->width + view->yoffset - view->col;
2408         int i;
2410         if (max < size)
2411                 size = max;
2413         set_view_attr(view, type);
2414         /* Using waddch() instead of waddnstr() ensures that
2415          * they'll be rendered correctly for the cursor line. */
2416         for (i = skip; i < size; i++)
2417                 waddch(view->win, graphic[i]);
2419         view->col += size;
2420         if (size < max && skip <= size)
2421                 waddch(view->win, ' ');
2422         view->col++;
2424         return view->width + view->yoffset <= view->col;
2427 static bool
2428 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2430         int max = MIN(view->width + view->yoffset - view->col, len);
2431         int col;
2433         if (text)
2434                 col = draw_chars(view, type, text, max - 1, trim);
2435         else
2436                 col = draw_space(view, type, max - 1, max - 1);
2438         view->col += col;
2439         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2440         return view->width + view->yoffset <= view->col;
2443 static bool
2444 draw_date(struct view *view, struct time *time)
2446         const char *date = mkdate(time, opt_date);
2447         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2449         return draw_field(view, LINE_DATE, date, cols, FALSE);
2452 static bool
2453 draw_author(struct view *view, const char *author)
2455         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2456         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2458         if (abbreviate && author)
2459                 author = get_author_initials(author);
2461         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2464 static bool
2465 draw_mode(struct view *view, mode_t mode)
2467         const char *str;
2469         if (S_ISDIR(mode))
2470                 str = "drwxr-xr-x";
2471         else if (S_ISLNK(mode))
2472                 str = "lrwxrwxrwx";
2473         else if (S_ISGITLINK(mode))
2474                 str = "m---------";
2475         else if (S_ISREG(mode) && mode & S_IXUSR)
2476                 str = "-rwxr-xr-x";
2477         else if (S_ISREG(mode))
2478                 str = "-rw-r--r--";
2479         else
2480                 str = "----------";
2482         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2485 static bool
2486 draw_lineno(struct view *view, unsigned int lineno)
2488         char number[10];
2489         int digits3 = view->digits < 3 ? 3 : view->digits;
2490         int max = MIN(view->width + view->yoffset - view->col, digits3);
2491         char *text = NULL;
2492         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2494         lineno += view->offset + 1;
2495         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2496                 static char fmt[] = "%1ld";
2498                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2499                 if (string_format(number, fmt, lineno))
2500                         text = number;
2501         }
2502         if (text)
2503                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2504         else
2505                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2506         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2509 static bool
2510 draw_view_line(struct view *view, unsigned int lineno)
2512         struct line *line;
2513         bool selected = (view->offset + lineno == view->lineno);
2515         assert(view_is_displayed(view));
2517         if (view->offset + lineno >= view->lines)
2518                 return FALSE;
2520         line = &view->line[view->offset + lineno];
2522         wmove(view->win, lineno, 0);
2523         if (line->cleareol)
2524                 wclrtoeol(view->win);
2525         view->col = 0;
2526         view->curline = line;
2527         view->curtype = LINE_NONE;
2528         line->selected = FALSE;
2529         line->dirty = line->cleareol = 0;
2531         if (selected) {
2532                 set_view_attr(view, LINE_CURSOR);
2533                 line->selected = TRUE;
2534                 view->ops->select(view, line);
2535         }
2537         return view->ops->draw(view, line, lineno);
2540 static void
2541 redraw_view_dirty(struct view *view)
2543         bool dirty = FALSE;
2544         int lineno;
2546         for (lineno = 0; lineno < view->height; lineno++) {
2547                 if (view->offset + lineno >= view->lines)
2548                         break;
2549                 if (!view->line[view->offset + lineno].dirty)
2550                         continue;
2551                 dirty = TRUE;
2552                 if (!draw_view_line(view, lineno))
2553                         break;
2554         }
2556         if (!dirty)
2557                 return;
2558         wnoutrefresh(view->win);
2561 static void
2562 redraw_view_from(struct view *view, int lineno)
2564         assert(0 <= lineno && lineno < view->height);
2566         for (; lineno < view->height; lineno++) {
2567                 if (!draw_view_line(view, lineno))
2568                         break;
2569         }
2571         wnoutrefresh(view->win);
2574 static void
2575 redraw_view(struct view *view)
2577         werase(view->win);
2578         redraw_view_from(view, 0);
2582 static void
2583 update_view_title(struct view *view)
2585         char buf[SIZEOF_STR];
2586         char state[SIZEOF_STR];
2587         size_t bufpos = 0, statelen = 0;
2589         assert(view_is_displayed(view));
2591         if (view->type != VIEW_STATUS && view->lines) {
2592                 unsigned int view_lines = view->offset + view->height;
2593                 unsigned int lines = view->lines
2594                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2595                                    : 0;
2597                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2598                                    view->ops->type,
2599                                    view->lineno + 1,
2600                                    view->lines,
2601                                    lines);
2603         }
2605         if (view->pipe) {
2606                 time_t secs = time(NULL) - view->start_time;
2608                 /* Three git seconds are a long time ... */
2609                 if (secs > 2)
2610                         string_format_from(state, &statelen, " loading %lds", secs);
2611         }
2613         string_format_from(buf, &bufpos, "[%s]", view->name);
2614         if (*view->ref && bufpos < view->width) {
2615                 size_t refsize = strlen(view->ref);
2616                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2618                 if (minsize < view->width)
2619                         refsize = view->width - minsize + 7;
2620                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2621         }
2623         if (statelen && bufpos < view->width) {
2624                 string_format_from(buf, &bufpos, "%s", state);
2625         }
2627         if (view == display[current_view])
2628                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2629         else
2630                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2632         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2633         wclrtoeol(view->title);
2634         wnoutrefresh(view->title);
2637 static int
2638 apply_step(double step, int value)
2640         if (step >= 1)
2641                 return (int) step;
2642         value *= step + 0.01;
2643         return value ? value : 1;
2646 static void
2647 resize_display(void)
2649         int offset, i;
2650         struct view *base = display[0];
2651         struct view *view = display[1] ? display[1] : display[0];
2653         /* Setup window dimensions */
2655         getmaxyx(stdscr, base->height, base->width);
2657         /* Make room for the status window. */
2658         base->height -= 1;
2660         if (view != base) {
2661                 /* Horizontal split. */
2662                 view->width   = base->width;
2663                 view->height  = apply_step(opt_scale_split_view, base->height);
2664                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2665                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2666                 base->height -= view->height;
2668                 /* Make room for the title bar. */
2669                 view->height -= 1;
2670         }
2672         /* Make room for the title bar. */
2673         base->height -= 1;
2675         offset = 0;
2677         foreach_displayed_view (view, i) {
2678                 if (!view->win) {
2679                         view->win = newwin(view->height, 0, offset, 0);
2680                         if (!view->win)
2681                                 die("Failed to create %s view", view->name);
2683                         scrollok(view->win, FALSE);
2685                         view->title = newwin(1, 0, offset + view->height, 0);
2686                         if (!view->title)
2687                                 die("Failed to create title window");
2689                 } else {
2690                         wresize(view->win, view->height, view->width);
2691                         mvwin(view->win,   offset, 0);
2692                         mvwin(view->title, offset + view->height, 0);
2693                 }
2695                 offset += view->height + 1;
2696         }
2699 static void
2700 redraw_display(bool clear)
2702         struct view *view;
2703         int i;
2705         foreach_displayed_view (view, i) {
2706                 if (clear)
2707                         wclear(view->win);
2708                 redraw_view(view);
2709                 update_view_title(view);
2710         }
2714 /*
2715  * Option management
2716  */
2718 static void
2719 toggle_enum_option_do(unsigned int *opt, const char *help,
2720                       const struct enum_map *map, size_t size)
2722         *opt = (*opt + 1) % size;
2723         redraw_display(FALSE);
2724         report("Displaying %s %s", enum_name(map[*opt]), help);
2727 #define toggle_enum_option(opt, help, map) \
2728         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2730 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2731 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2733 static void
2734 toggle_view_option(bool *option, const char *help)
2736         *option = !*option;
2737         redraw_display(FALSE);
2738         report("%sabling %s", *option ? "En" : "Dis", help);
2741 static void
2742 open_option_menu(void)
2744         const struct menu_item menu[] = {
2745                 { '.', "line numbers", &opt_line_number },
2746                 { 'D', "date display", &opt_date },
2747                 { 'A', "author display", &opt_author },
2748                 { 'g', "revision graph display", &opt_rev_graph },
2749                 { 'F', "reference display", &opt_show_refs },
2750                 { 0 }
2751         };
2752         int selected = 0;
2754         if (prompt_menu("Toggle option", menu, &selected)) {
2755                 if (menu[selected].data == &opt_date)
2756                         toggle_date();
2757                 else if (menu[selected].data == &opt_author)
2758                         toggle_author();
2759                 else
2760                         toggle_view_option(menu[selected].data, menu[selected].text);
2761         }
2764 static void
2765 maximize_view(struct view *view)
2767         memset(display, 0, sizeof(display));
2768         current_view = 0;
2769         display[current_view] = view;
2770         resize_display();
2771         redraw_display(FALSE);
2772         report("");
2776 /*
2777  * Navigation
2778  */
2780 static bool
2781 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2783         if (lineno >= view->lines)
2784                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2786         if (offset > lineno || offset + view->height <= lineno) {
2787                 unsigned long half = view->height / 2;
2789                 if (lineno > half)
2790                         offset = lineno - half;
2791                 else
2792                         offset = 0;
2793         }
2795         if (offset != view->offset || lineno != view->lineno) {
2796                 view->offset = offset;
2797                 view->lineno = lineno;
2798                 return TRUE;
2799         }
2801         return FALSE;
2804 /* Scrolling backend */
2805 static void
2806 do_scroll_view(struct view *view, int lines)
2808         bool redraw_current_line = FALSE;
2810         /* The rendering expects the new offset. */
2811         view->offset += lines;
2813         assert(0 <= view->offset && view->offset < view->lines);
2814         assert(lines);
2816         /* Move current line into the view. */
2817         if (view->lineno < view->offset) {
2818                 view->lineno = view->offset;
2819                 redraw_current_line = TRUE;
2820         } else if (view->lineno >= view->offset + view->height) {
2821                 view->lineno = view->offset + view->height - 1;
2822                 redraw_current_line = TRUE;
2823         }
2825         assert(view->offset <= view->lineno && view->lineno < view->lines);
2827         /* Redraw the whole screen if scrolling is pointless. */
2828         if (view->height < ABS(lines)) {
2829                 redraw_view(view);
2831         } else {
2832                 int line = lines > 0 ? view->height - lines : 0;
2833                 int end = line + ABS(lines);
2835                 scrollok(view->win, TRUE);
2836                 wscrl(view->win, lines);
2837                 scrollok(view->win, FALSE);
2839                 while (line < end && draw_view_line(view, line))
2840                         line++;
2842                 if (redraw_current_line)
2843                         draw_view_line(view, view->lineno - view->offset);
2844                 wnoutrefresh(view->win);
2845         }
2847         view->has_scrolled = TRUE;
2848         report("");
2851 /* Scroll frontend */
2852 static void
2853 scroll_view(struct view *view, enum request request)
2855         int lines = 1;
2857         assert(view_is_displayed(view));
2859         switch (request) {
2860         case REQ_SCROLL_LEFT:
2861                 if (view->yoffset == 0) {
2862                         report("Cannot scroll beyond the first column");
2863                         return;
2864                 }
2865                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2866                         view->yoffset = 0;
2867                 else
2868                         view->yoffset -= apply_step(opt_hscroll, view->width);
2869                 redraw_view_from(view, 0);
2870                 report("");
2871                 return;
2872         case REQ_SCROLL_RIGHT:
2873                 view->yoffset += apply_step(opt_hscroll, view->width);
2874                 redraw_view(view);
2875                 report("");
2876                 return;
2877         case REQ_SCROLL_PAGE_DOWN:
2878                 lines = view->height;
2879         case REQ_SCROLL_LINE_DOWN:
2880                 if (view->offset + lines > view->lines)
2881                         lines = view->lines - view->offset;
2883                 if (lines == 0 || view->offset + view->height >= view->lines) {
2884                         report("Cannot scroll beyond the last line");
2885                         return;
2886                 }
2887                 break;
2889         case REQ_SCROLL_PAGE_UP:
2890                 lines = view->height;
2891         case REQ_SCROLL_LINE_UP:
2892                 if (lines > view->offset)
2893                         lines = view->offset;
2895                 if (lines == 0) {
2896                         report("Cannot scroll beyond the first line");
2897                         return;
2898                 }
2900                 lines = -lines;
2901                 break;
2903         default:
2904                 die("request %d not handled in switch", request);
2905         }
2907         do_scroll_view(view, lines);
2910 /* Cursor moving */
2911 static void
2912 move_view(struct view *view, enum request request)
2914         int scroll_steps = 0;
2915         int steps;
2917         switch (request) {
2918         case REQ_MOVE_FIRST_LINE:
2919                 steps = -view->lineno;
2920                 break;
2922         case REQ_MOVE_LAST_LINE:
2923                 steps = view->lines - view->lineno - 1;
2924                 break;
2926         case REQ_MOVE_PAGE_UP:
2927                 steps = view->height > view->lineno
2928                       ? -view->lineno : -view->height;
2929                 break;
2931         case REQ_MOVE_PAGE_DOWN:
2932                 steps = view->lineno + view->height >= view->lines
2933                       ? view->lines - view->lineno - 1 : view->height;
2934                 break;
2936         case REQ_MOVE_UP:
2937                 steps = -1;
2938                 break;
2940         case REQ_MOVE_DOWN:
2941                 steps = 1;
2942                 break;
2944         default:
2945                 die("request %d not handled in switch", request);
2946         }
2948         if (steps <= 0 && view->lineno == 0) {
2949                 report("Cannot move beyond the first line");
2950                 return;
2952         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2953                 report("Cannot move beyond the last line");
2954                 return;
2955         }
2957         /* Move the current line */
2958         view->lineno += steps;
2959         assert(0 <= view->lineno && view->lineno < view->lines);
2961         /* Check whether the view needs to be scrolled */
2962         if (view->lineno < view->offset ||
2963             view->lineno >= view->offset + view->height) {
2964                 scroll_steps = steps;
2965                 if (steps < 0 && -steps > view->offset) {
2966                         scroll_steps = -view->offset;
2968                 } else if (steps > 0) {
2969                         if (view->lineno == view->lines - 1 &&
2970                             view->lines > view->height) {
2971                                 scroll_steps = view->lines - view->offset - 1;
2972                                 if (scroll_steps >= view->height)
2973                                         scroll_steps -= view->height - 1;
2974                         }
2975                 }
2976         }
2978         if (!view_is_displayed(view)) {
2979                 view->offset += scroll_steps;
2980                 assert(0 <= view->offset && view->offset < view->lines);
2981                 view->ops->select(view, &view->line[view->lineno]);
2982                 return;
2983         }
2985         /* Repaint the old "current" line if we be scrolling */
2986         if (ABS(steps) < view->height)
2987                 draw_view_line(view, view->lineno - steps - view->offset);
2989         if (scroll_steps) {
2990                 do_scroll_view(view, scroll_steps);
2991                 return;
2992         }
2994         /* Draw the current line */
2995         draw_view_line(view, view->lineno - view->offset);
2997         wnoutrefresh(view->win);
2998         report("");
3002 /*
3003  * Searching
3004  */
3006 static void search_view(struct view *view, enum request request);
3008 static bool
3009 grep_text(struct view *view, const char *text[])
3011         regmatch_t pmatch;
3012         size_t i;
3014         for (i = 0; text[i]; i++)
3015                 if (*text[i] &&
3016                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3017                         return TRUE;
3018         return FALSE;
3021 static void
3022 select_view_line(struct view *view, unsigned long lineno)
3024         unsigned long old_lineno = view->lineno;
3025         unsigned long old_offset = view->offset;
3027         if (goto_view_line(view, view->offset, lineno)) {
3028                 if (view_is_displayed(view)) {
3029                         if (old_offset != view->offset) {
3030                                 redraw_view(view);
3031                         } else {
3032                                 draw_view_line(view, old_lineno - view->offset);
3033                                 draw_view_line(view, view->lineno - view->offset);
3034                                 wnoutrefresh(view->win);
3035                         }
3036                 } else {
3037                         view->ops->select(view, &view->line[view->lineno]);
3038                 }
3039         }
3042 static void
3043 find_next(struct view *view, enum request request)
3045         unsigned long lineno = view->lineno;
3046         int direction;
3048         if (!*view->grep) {
3049                 if (!*opt_search)
3050                         report("No previous search");
3051                 else
3052                         search_view(view, request);
3053                 return;
3054         }
3056         switch (request) {
3057         case REQ_SEARCH:
3058         case REQ_FIND_NEXT:
3059                 direction = 1;
3060                 break;
3062         case REQ_SEARCH_BACK:
3063         case REQ_FIND_PREV:
3064                 direction = -1;
3065                 break;
3067         default:
3068                 return;
3069         }
3071         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3072                 lineno += direction;
3074         /* Note, lineno is unsigned long so will wrap around in which case it
3075          * will become bigger than view->lines. */
3076         for (; lineno < view->lines; lineno += direction) {
3077                 if (view->ops->grep(view, &view->line[lineno])) {
3078                         select_view_line(view, lineno);
3079                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3080                         return;
3081                 }
3082         }
3084         report("No match found for '%s'", view->grep);
3087 static void
3088 search_view(struct view *view, enum request request)
3090         int regex_err;
3092         if (view->regex) {
3093                 regfree(view->regex);
3094                 *view->grep = 0;
3095         } else {
3096                 view->regex = calloc(1, sizeof(*view->regex));
3097                 if (!view->regex)
3098                         return;
3099         }
3101         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3102         if (regex_err != 0) {
3103                 char buf[SIZEOF_STR] = "unknown error";
3105                 regerror(regex_err, view->regex, buf, sizeof(buf));
3106                 report("Search failed: %s", buf);
3107                 return;
3108         }
3110         string_copy(view->grep, opt_search);
3112         find_next(view, request);
3115 /*
3116  * Incremental updating
3117  */
3119 static void
3120 reset_view(struct view *view)
3122         int i;
3124         for (i = 0; i < view->lines; i++)
3125                 free(view->line[i].data);
3126         free(view->line);
3128         view->p_offset = view->offset;
3129         view->p_yoffset = view->yoffset;
3130         view->p_lineno = view->lineno;
3132         view->line = NULL;
3133         view->offset = 0;
3134         view->yoffset = 0;
3135         view->lines  = 0;
3136         view->lineno = 0;
3137         view->vid[0] = 0;
3138         view->update_secs = 0;
3141 static const char *
3142 format_arg(const char *name)
3144         static struct {
3145                 const char *name;
3146                 size_t namelen;
3147                 const char *value;
3148                 const char *value_if_empty;
3149         } vars[] = {
3150 #define FORMAT_VAR(name, value, value_if_empty) \
3151         { name, STRING_SIZE(name), value, value_if_empty }
3152                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3153                 FORMAT_VAR("%(file)",           opt_file,       ""),
3154                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3155                 FORMAT_VAR("%(head)",           ref_head,       ""),
3156                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3157                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3158                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3159         };
3160         int i;
3162         for (i = 0; i < ARRAY_SIZE(vars); i++)
3163                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3164                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3166         report("Unknown replacement: `%s`", name);
3167         return NULL;
3170 static bool
3171 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3173         char buf[SIZEOF_STR];
3174         int argc;
3176         argv_free(*dst_argv);
3178         for (argc = 0; src_argv[argc]; argc++) {
3179                 const char *arg = src_argv[argc];
3180                 size_t bufpos = 0;
3182                 if (!strcmp(arg, "%(file-args)")) {
3183                         if (!argv_append_array(dst_argv, opt_file_args))
3184                                 break;
3185                         continue;
3187                 } else if (!strcmp(arg, "%(diff-args)")) {
3188                         if (!argv_append_array(dst_argv, opt_diff_args))
3189                                 break;
3190                         continue;
3192                 } else if (!strcmp(arg, "%(rev-args)")) {
3193                         if (!argv_append_array(dst_argv, opt_rev_args))
3194                                 break;
3195                         continue;
3196                 }
3198                 while (arg) {
3199                         char *next = strstr(arg, "%(");
3200                         int len = next - arg;
3201                         const char *value;
3203                         if (!next || !replace) {
3204                                 len = strlen(arg);
3205                                 value = "";
3207                         } else {
3208                                 value = format_arg(next);
3210                                 if (!value) {
3211                                         return FALSE;
3212                                 }
3213                         }
3215                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3216                                 return FALSE;
3218                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3219                 }
3221                 if (!argv_append(dst_argv, buf))
3222                         break;
3223         }
3225         return src_argv[argc] == NULL;
3228 static bool
3229 restore_view_position(struct view *view)
3231         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3232                 return FALSE;
3234         /* Changing the view position cancels the restoring. */
3235         /* FIXME: Changing back to the first line is not detected. */
3236         if (view->offset != 0 || view->lineno != 0) {
3237                 view->p_restore = FALSE;
3238                 return FALSE;
3239         }
3241         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3242             view_is_displayed(view))
3243                 werase(view->win);
3245         view->yoffset = view->p_yoffset;
3246         view->p_restore = FALSE;
3248         return TRUE;
3251 static void
3252 end_update(struct view *view, bool force)
3254         if (!view->pipe)
3255                 return;
3256         while (!view->ops->read(view, NULL))
3257                 if (!force)
3258                         return;
3259         if (force)
3260                 io_kill(view->pipe);
3261         io_done(view->pipe);
3262         view->pipe = NULL;
3265 static void
3266 setup_update(struct view *view, const char *vid)
3268         reset_view(view);
3269         string_copy_rev(view->vid, vid);
3270         view->pipe = &view->io;
3271         view->start_time = time(NULL);
3274 static bool
3275 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3277         view->dir = dir;
3278         return format_argv(&view->argv, argv, replace);
3281 static bool
3282 prepare_update(struct view *view, const char *argv[], const char *dir)
3284         if (view->pipe)
3285                 end_update(view, TRUE);
3286         return prepare_io(view, dir, argv, FALSE);
3289 static bool
3290 start_update(struct view *view, const char **argv, const char *dir)
3292         if (view->pipe)
3293                 io_done(view->pipe);
3294         return prepare_io(view, dir, argv, FALSE) &&
3295                io_run(&view->io, IO_RD, dir, view->argv);
3298 static bool
3299 prepare_update_file(struct view *view, const char *name)
3301         if (view->pipe)
3302                 end_update(view, TRUE);
3303         argv_free(view->argv);
3304         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3307 static bool
3308 begin_update(struct view *view, bool refresh)
3310         if (view->pipe)
3311                 end_update(view, TRUE);
3313         if (!refresh) {
3314                 if (view->ops->prepare) {
3315                         if (!view->ops->prepare(view))
3316                                 return FALSE;
3317                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3318                         return FALSE;
3319                 }
3321                 /* Put the current ref_* value to the view title ref
3322                  * member. This is needed by the blob view. Most other
3323                  * views sets it automatically after loading because the
3324                  * first line is a commit line. */
3325                 string_copy_rev(view->ref, view->id);
3326         }
3328         if (view->argv && view->argv[0] &&
3329             !io_run(&view->io, IO_RD, view->dir, view->argv))
3330                 return FALSE;
3332         setup_update(view, view->id);
3334         return TRUE;
3337 static bool
3338 update_view(struct view *view)
3340         char out_buffer[BUFSIZ * 2];
3341         char *line;
3342         /* Clear the view and redraw everything since the tree sorting
3343          * might have rearranged things. */
3344         bool redraw = view->lines == 0;
3345         bool can_read = TRUE;
3347         if (!view->pipe)
3348                 return TRUE;
3350         if (!io_can_read(view->pipe)) {
3351                 if (view->lines == 0 && view_is_displayed(view)) {
3352                         time_t secs = time(NULL) - view->start_time;
3354                         if (secs > 1 && secs > view->update_secs) {
3355                                 if (view->update_secs == 0)
3356                                         redraw_view(view);
3357                                 update_view_title(view);
3358                                 view->update_secs = secs;
3359                         }
3360                 }
3361                 return TRUE;
3362         }
3364         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3365                 if (opt_iconv_in != ICONV_NONE) {
3366                         ICONV_CONST char *inbuf = line;
3367                         size_t inlen = strlen(line) + 1;
3369                         char *outbuf = out_buffer;
3370                         size_t outlen = sizeof(out_buffer);
3372                         size_t ret;
3374                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3375                         if (ret != (size_t) -1)
3376                                 line = out_buffer;
3377                 }
3379                 if (!view->ops->read(view, line)) {
3380                         report("Allocation failure");
3381                         end_update(view, TRUE);
3382                         return FALSE;
3383                 }
3384         }
3386         {
3387                 unsigned long lines = view->lines;
3388                 int digits;
3390                 for (digits = 0; lines; digits++)
3391                         lines /= 10;
3393                 /* Keep the displayed view in sync with line number scaling. */
3394                 if (digits != view->digits) {
3395                         view->digits = digits;
3396                         if (opt_line_number || view->type == VIEW_BLAME)
3397                                 redraw = TRUE;
3398                 }
3399         }
3401         if (io_error(view->pipe)) {
3402                 report("Failed to read: %s", io_strerror(view->pipe));
3403                 end_update(view, TRUE);
3405         } else if (io_eof(view->pipe)) {
3406                 if (view_is_displayed(view))
3407                         report("");
3408                 end_update(view, FALSE);
3409         }
3411         if (restore_view_position(view))
3412                 redraw = TRUE;
3414         if (!view_is_displayed(view))
3415                 return TRUE;
3417         if (redraw)
3418                 redraw_view_from(view, 0);
3419         else
3420                 redraw_view_dirty(view);
3422         /* Update the title _after_ the redraw so that if the redraw picks up a
3423          * commit reference in view->ref it'll be available here. */
3424         update_view_title(view);
3425         return TRUE;
3428 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3430 static struct line *
3431 add_line_data(struct view *view, void *data, enum line_type type)
3433         struct line *line;
3435         if (!realloc_lines(&view->line, view->lines, 1))
3436                 return NULL;
3438         line = &view->line[view->lines++];
3439         memset(line, 0, sizeof(*line));
3440         line->type = type;
3441         line->data = data;
3442         line->dirty = 1;
3444         return line;
3447 static struct line *
3448 add_line_text(struct view *view, const char *text, enum line_type type)
3450         char *data = text ? strdup(text) : NULL;
3452         return data ? add_line_data(view, data, type) : NULL;
3455 static struct line *
3456 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3458         char buf[SIZEOF_STR];
3459         va_list args;
3461         va_start(args, fmt);
3462         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3463                 buf[0] = 0;
3464         va_end(args);
3466         return buf[0] ? add_line_text(view, buf, type) : NULL;
3469 /*
3470  * View opening
3471  */
3473 enum open_flags {
3474         OPEN_DEFAULT = 0,       /* Use default view switching. */
3475         OPEN_SPLIT = 1,         /* Split current view. */
3476         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3477         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3478         OPEN_PREPARED = 32,     /* Open already prepared command. */
3479 };
3481 static void
3482 open_view(struct view *prev, enum request request, enum open_flags flags)
3484         bool split = !!(flags & OPEN_SPLIT);
3485         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3486         bool nomaximize = !!(flags & OPEN_REFRESH);
3487         struct view *view = VIEW(request);
3488         int nviews = displayed_views();
3489         struct view *base_view = display[0];
3491         if (view == prev && nviews == 1 && !reload) {
3492                 report("Already in %s view", view->name);
3493                 return;
3494         }
3496         if (view->git_dir && !opt_git_dir[0]) {
3497                 report("The %s view is disabled in pager view", view->name);
3498                 return;
3499         }
3501         if (split) {
3502                 display[1] = view;
3503                 current_view = 1;
3504                 view->parent = prev;
3505         } else if (!nomaximize) {
3506                 /* Maximize the current view. */
3507                 memset(display, 0, sizeof(display));
3508                 current_view = 0;
3509                 display[current_view] = view;
3510         }
3512         /* No prev signals that this is the first loaded view. */
3513         if (prev && view != prev) {
3514                 view->prev = prev;
3515         }
3517         /* Resize the view when switching between split- and full-screen,
3518          * or when switching between two different full-screen views. */
3519         if (nviews != displayed_views() ||
3520             (nviews == 1 && base_view != display[0]))
3521                 resize_display();
3523         if (view->ops->open) {
3524                 if (view->pipe)
3525                         end_update(view, TRUE);
3526                 if (!view->ops->open(view)) {
3527                         report("Failed to load %s view", view->name);
3528                         return;
3529                 }
3530                 restore_view_position(view);
3532         } else if ((reload || strcmp(view->vid, view->id)) &&
3533                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3534                 report("Failed to load %s view", view->name);
3535                 return;
3536         }
3538         if (split && prev->lineno - prev->offset >= prev->height) {
3539                 /* Take the title line into account. */
3540                 int lines = prev->lineno - prev->offset - prev->height + 1;
3542                 /* Scroll the view that was split if the current line is
3543                  * outside the new limited view. */
3544                 do_scroll_view(prev, lines);
3545         }
3547         if (prev && view != prev && split && view_is_displayed(prev)) {
3548                 /* "Blur" the previous view. */
3549                 update_view_title(prev);
3550         }
3552         if (view->pipe && view->lines == 0) {
3553                 /* Clear the old view and let the incremental updating refill
3554                  * the screen. */
3555                 werase(view->win);
3556                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3557                 report("");
3558         } else if (view_is_displayed(view)) {
3559                 redraw_view(view);
3560                 report("");
3561         }
3564 static void
3565 open_external_viewer(const char *argv[], const char *dir)
3567         def_prog_mode();           /* save current tty modes */
3568         endwin();                  /* restore original tty modes */
3569         io_run_fg(argv, dir);
3570         fprintf(stderr, "Press Enter to continue");
3571         getc(opt_tty);
3572         reset_prog_mode();
3573         redraw_display(TRUE);
3576 static void
3577 open_mergetool(const char *file)
3579         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3581         open_external_viewer(mergetool_argv, opt_cdup);
3584 static void
3585 open_editor(const char *file)
3587         const char *editor_argv[] = { "vi", file, NULL };
3588         const char *editor;
3590         editor = getenv("GIT_EDITOR");
3591         if (!editor && *opt_editor)
3592                 editor = opt_editor;
3593         if (!editor)
3594                 editor = getenv("VISUAL");
3595         if (!editor)
3596                 editor = getenv("EDITOR");
3597         if (!editor)
3598                 editor = "vi";
3600         editor_argv[0] = editor;
3601         open_external_viewer(editor_argv, opt_cdup);
3604 static void
3605 open_run_request(enum request request)
3607         struct run_request *req = get_run_request(request);
3608         const char **argv = NULL;
3610         if (!req) {
3611                 report("Unknown run request");
3612                 return;
3613         }
3615         if (format_argv(&argv, req->argv, TRUE))
3616                 open_external_viewer(argv, NULL);
3617         if (argv)
3618                 argv_free(argv);
3619         free(argv);
3622 /*
3623  * User request switch noodle
3624  */
3626 static int
3627 view_driver(struct view *view, enum request request)
3629         int i;
3631         if (request == REQ_NONE)
3632                 return TRUE;
3634         if (request > REQ_NONE) {
3635                 open_run_request(request);
3636                 view_request(view, REQ_REFRESH);
3637                 return TRUE;
3638         }
3640         request = view_request(view, request);
3641         if (request == REQ_NONE)
3642                 return TRUE;
3644         switch (request) {
3645         case REQ_MOVE_UP:
3646         case REQ_MOVE_DOWN:
3647         case REQ_MOVE_PAGE_UP:
3648         case REQ_MOVE_PAGE_DOWN:
3649         case REQ_MOVE_FIRST_LINE:
3650         case REQ_MOVE_LAST_LINE:
3651                 move_view(view, request);
3652                 break;
3654         case REQ_SCROLL_LEFT:
3655         case REQ_SCROLL_RIGHT:
3656         case REQ_SCROLL_LINE_DOWN:
3657         case REQ_SCROLL_LINE_UP:
3658         case REQ_SCROLL_PAGE_DOWN:
3659         case REQ_SCROLL_PAGE_UP:
3660                 scroll_view(view, request);
3661                 break;
3663         case REQ_VIEW_BLAME:
3664                 if (!opt_file[0]) {
3665                         report("No file chosen, press %s to open tree view",
3666                                get_key(view->keymap, REQ_VIEW_TREE));
3667                         break;
3668                 }
3669                 open_view(view, request, OPEN_DEFAULT);
3670                 break;
3672         case REQ_VIEW_BLOB:
3673                 if (!ref_blob[0]) {
3674                         report("No file chosen, press %s to open tree view",
3675                                get_key(view->keymap, REQ_VIEW_TREE));
3676                         break;
3677                 }
3678                 open_view(view, request, OPEN_DEFAULT);
3679                 break;
3681         case REQ_VIEW_PAGER:
3682                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3683                         report("No pager content, press %s to run command from prompt",
3684                                get_key(view->keymap, REQ_PROMPT));
3685                         break;
3686                 }
3687                 open_view(view, request, OPEN_DEFAULT);
3688                 break;
3690         case REQ_VIEW_STAGE:
3691                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3692                         report("No stage content, press %s to open the status view and choose file",
3693                                get_key(view->keymap, REQ_VIEW_STATUS));
3694                         break;
3695                 }
3696                 open_view(view, request, OPEN_DEFAULT);
3697                 break;
3699         case REQ_VIEW_STATUS:
3700                 if (opt_is_inside_work_tree == FALSE) {
3701                         report("The status view requires a working tree");
3702                         break;
3703                 }
3704                 open_view(view, request, OPEN_DEFAULT);
3705                 break;
3707         case REQ_VIEW_MAIN:
3708         case REQ_VIEW_DIFF:
3709         case REQ_VIEW_LOG:
3710         case REQ_VIEW_TREE:
3711         case REQ_VIEW_HELP:
3712         case REQ_VIEW_BRANCH:
3713                 open_view(view, request, OPEN_DEFAULT);
3714                 break;
3716         case REQ_NEXT:
3717         case REQ_PREVIOUS:
3718                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3720                 if (view->parent) {
3721                         int line;
3723                         view = view->parent;
3724                         line = view->lineno;
3725                         move_view(view, request);
3726                         if (view_is_displayed(view))
3727                                 update_view_title(view);
3728                         if (line != view->lineno)
3729                                 view_request(view, REQ_ENTER);
3730                 } else {
3731                         move_view(view, request);
3732                 }
3733                 break;
3735         case REQ_VIEW_NEXT:
3736         {
3737                 int nviews = displayed_views();
3738                 int next_view = (current_view + 1) % nviews;
3740                 if (next_view == current_view) {
3741                         report("Only one view is displayed");
3742                         break;
3743                 }
3745                 current_view = next_view;
3746                 /* Blur out the title of the previous view. */
3747                 update_view_title(view);
3748                 report("");
3749                 break;
3750         }
3751         case REQ_REFRESH:
3752                 report("Refreshing is not yet supported for the %s view", view->name);
3753                 break;
3755         case REQ_MAXIMIZE:
3756                 if (displayed_views() == 2)
3757                         maximize_view(view);
3758                 break;
3760         case REQ_OPTIONS:
3761                 open_option_menu();
3762                 break;
3764         case REQ_TOGGLE_LINENO:
3765                 toggle_view_option(&opt_line_number, "line numbers");
3766                 break;
3768         case REQ_TOGGLE_DATE:
3769                 toggle_date();
3770                 break;
3772         case REQ_TOGGLE_AUTHOR:
3773                 toggle_author();
3774                 break;
3776         case REQ_TOGGLE_REV_GRAPH:
3777                 toggle_view_option(&opt_rev_graph, "revision graph display");
3778                 break;
3780         case REQ_TOGGLE_REFS:
3781                 toggle_view_option(&opt_show_refs, "reference display");
3782                 break;
3784         case REQ_TOGGLE_SORT_FIELD:
3785         case REQ_TOGGLE_SORT_ORDER:
3786                 report("Sorting is not yet supported for the %s view", view->name);
3787                 break;
3789         case REQ_SEARCH:
3790         case REQ_SEARCH_BACK:
3791                 search_view(view, request);
3792                 break;
3794         case REQ_FIND_NEXT:
3795         case REQ_FIND_PREV:
3796                 find_next(view, request);
3797                 break;
3799         case REQ_STOP_LOADING:
3800                 foreach_view(view, i) {
3801                         if (view->pipe)
3802                                 report("Stopped loading the %s view", view->name),
3803                         end_update(view, TRUE);
3804                 }
3805                 break;
3807         case REQ_SHOW_VERSION:
3808                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3809                 return TRUE;
3811         case REQ_SCREEN_REDRAW:
3812                 redraw_display(TRUE);
3813                 break;
3815         case REQ_EDIT:
3816                 report("Nothing to edit");
3817                 break;
3819         case REQ_ENTER:
3820                 report("Nothing to enter");
3821                 break;
3823         case REQ_VIEW_CLOSE:
3824                 /* XXX: Mark closed views by letting view->prev point to the
3825                  * view itself. Parents to closed view should never be
3826                  * followed. */
3827                 if (view->prev && view->prev != view) {
3828                         maximize_view(view->prev);
3829                         view->prev = view;
3830                         break;
3831                 }
3832                 /* Fall-through */
3833         case REQ_QUIT:
3834                 return FALSE;
3836         default:
3837                 report("Unknown key, press %s for help",
3838                        get_key(view->keymap, REQ_VIEW_HELP));
3839                 return TRUE;
3840         }
3842         return TRUE;
3846 /*
3847  * View backend utilities
3848  */
3850 enum sort_field {
3851         ORDERBY_NAME,
3852         ORDERBY_DATE,
3853         ORDERBY_AUTHOR,
3854 };
3856 struct sort_state {
3857         const enum sort_field *fields;
3858         size_t size, current;
3859         bool reverse;
3860 };
3862 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3863 #define get_sort_field(state) ((state).fields[(state).current])
3864 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3866 static void
3867 sort_view(struct view *view, enum request request, struct sort_state *state,
3868           int (*compare)(const void *, const void *))
3870         switch (request) {
3871         case REQ_TOGGLE_SORT_FIELD:
3872                 state->current = (state->current + 1) % state->size;
3873                 break;
3875         case REQ_TOGGLE_SORT_ORDER:
3876                 state->reverse = !state->reverse;
3877                 break;
3878         default:
3879                 die("Not a sort request");
3880         }
3882         qsort(view->line, view->lines, sizeof(*view->line), compare);
3883         redraw_view(view);
3886 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3888 /* Small author cache to reduce memory consumption. It uses binary
3889  * search to lookup or find place to position new entries. No entries
3890  * are ever freed. */
3891 static const char *
3892 get_author(const char *name)
3894         static const char **authors;
3895         static size_t authors_size;
3896         int from = 0, to = authors_size - 1;
3898         while (from <= to) {
3899                 size_t pos = (to + from) / 2;
3900                 int cmp = strcmp(name, authors[pos]);
3902                 if (!cmp)
3903                         return authors[pos];
3905                 if (cmp < 0)
3906                         to = pos - 1;
3907                 else
3908                         from = pos + 1;
3909         }
3911         if (!realloc_authors(&authors, authors_size, 1))
3912                 return NULL;
3913         name = strdup(name);
3914         if (!name)
3915                 return NULL;
3917         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3918         authors[from] = name;
3919         authors_size++;
3921         return name;
3924 static void
3925 parse_timesec(struct time *time, const char *sec)
3927         time->sec = (time_t) atol(sec);
3930 static void
3931 parse_timezone(struct time *time, const char *zone)
3933         long tz;
3935         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3936         tz += ('0' - zone[2]) * 60 * 60;
3937         tz += ('0' - zone[3]) * 60 * 10;
3938         tz += ('0' - zone[4]) * 60;
3940         if (zone[0] == '-')
3941                 tz = -tz;
3943         time->tz = tz;
3944         time->sec -= tz;
3947 /* Parse author lines where the name may be empty:
3948  *      author  <email@address.tld> 1138474660 +0100
3949  */
3950 static void
3951 parse_author_line(char *ident, const char **author, struct time *time)
3953         char *nameend = strchr(ident, '<');
3954         char *emailend = strchr(ident, '>');
3956         if (nameend && emailend)
3957                 *nameend = *emailend = 0;
3958         ident = chomp_string(ident);
3959         if (!*ident) {
3960                 if (nameend)
3961                         ident = chomp_string(nameend + 1);
3962                 if (!*ident)
3963                         ident = "Unknown";
3964         }
3966         *author = get_author(ident);
3968         /* Parse epoch and timezone */
3969         if (emailend && emailend[1] == ' ') {
3970                 char *secs = emailend + 2;
3971                 char *zone = strchr(secs, ' ');
3973                 parse_timesec(time, secs);
3975                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3976                         parse_timezone(time, zone + 1);
3977         }
3980 static bool
3981 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3983         char rev[SIZEOF_REV];
3984         const char *revlist_argv[] = {
3985                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3986         };
3987         struct menu_item *items;
3988         char text[SIZEOF_STR];
3989         bool ok = TRUE;
3990         int i;
3992         items = calloc(*parents + 1, sizeof(*items));
3993         if (!items)
3994                 return FALSE;
3996         for (i = 0; i < *parents; i++) {
3997                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3998                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3999                     !(items[i].text = strdup(text))) {
4000                         ok = FALSE;
4001                         break;
4002                 }
4003         }
4005         if (ok) {
4006                 *parents = 0;
4007                 ok = prompt_menu("Select parent", items, parents);
4008         }
4009         for (i = 0; items[i].text; i++)
4010                 free((char *) items[i].text);
4011         free(items);
4012         return ok;
4015 static bool
4016 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4018         char buf[SIZEOF_STR * 4];
4019         const char *revlist_argv[] = {
4020                 "git", "log", "--no-color", "-1",
4021                         "--pretty=format:%P", id, "--", path, NULL
4022         };
4023         int parents;
4025         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4026             (parents = strlen(buf) / 40) < 0) {
4027                 report("Failed to get parent information");
4028                 return FALSE;
4030         } else if (parents == 0) {
4031                 if (path)
4032                         report("Path '%s' does not exist in the parent", path);
4033                 else
4034                         report("The selected commit has no parents");
4035                 return FALSE;
4036         }
4038         if (parents == 1)
4039                 parents = 0;
4040         else if (!open_commit_parent_menu(buf, &parents))
4041                 return FALSE;
4043         string_copy_rev(rev, &buf[41 * parents]);
4044         return TRUE;
4047 /*
4048  * Pager backend
4049  */
4051 static bool
4052 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4054         char text[SIZEOF_STR];
4056         if (opt_line_number && draw_lineno(view, lineno))
4057                 return TRUE;
4059         string_expand(text, sizeof(text), line->data, opt_tab_size);
4060         draw_text(view, line->type, text, TRUE);
4061         return TRUE;
4064 static bool
4065 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4067         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4068         char ref[SIZEOF_STR];
4070         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4071                 return TRUE;
4073         /* This is the only fatal call, since it can "corrupt" the buffer. */
4074         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4075                 return FALSE;
4077         return TRUE;
4080 static void
4081 add_pager_refs(struct view *view, struct line *line)
4083         char buf[SIZEOF_STR];
4084         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4085         struct ref_list *list;
4086         size_t bufpos = 0, i;
4087         const char *sep = "Refs: ";
4088         bool is_tag = FALSE;
4090         assert(line->type == LINE_COMMIT);
4092         list = get_ref_list(commit_id);
4093         if (!list) {
4094                 if (view->type == VIEW_DIFF)
4095                         goto try_add_describe_ref;
4096                 return;
4097         }
4099         for (i = 0; i < list->size; i++) {
4100                 struct ref *ref = list->refs[i];
4101                 const char *fmt = ref->tag    ? "%s[%s]" :
4102                                   ref->remote ? "%s<%s>" : "%s%s";
4104                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4105                         return;
4106                 sep = ", ";
4107                 if (ref->tag)
4108                         is_tag = TRUE;
4109         }
4111         if (!is_tag && view->type == VIEW_DIFF) {
4112 try_add_describe_ref:
4113                 /* Add <tag>-g<commit_id> "fake" reference. */
4114                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4115                         return;
4116         }
4118         if (bufpos == 0)
4119                 return;
4121         add_line_text(view, buf, LINE_PP_REFS);
4124 static bool
4125 pager_read(struct view *view, char *data)
4127         struct line *line;
4129         if (!data)
4130                 return TRUE;
4132         line = add_line_text(view, data, get_line_type(data));
4133         if (!line)
4134                 return FALSE;
4136         if (line->type == LINE_COMMIT &&
4137             (view->type == VIEW_DIFF ||
4138              view->type == VIEW_LOG))
4139                 add_pager_refs(view, line);
4141         return TRUE;
4144 static enum request
4145 pager_request(struct view *view, enum request request, struct line *line)
4147         int split = 0;
4149         if (request != REQ_ENTER)
4150                 return request;
4152         if (line->type == LINE_COMMIT &&
4153            (view->type == VIEW_LOG ||
4154             view->type == VIEW_PAGER)) {
4155                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4156                 split = 1;
4157         }
4159         /* Always scroll the view even if it was split. That way
4160          * you can use Enter to scroll through the log view and
4161          * split open each commit diff. */
4162         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4164         /* FIXME: A minor workaround. Scrolling the view will call report("")
4165          * but if we are scrolling a non-current view this won't properly
4166          * update the view title. */
4167         if (split)
4168                 update_view_title(view);
4170         return REQ_NONE;
4173 static bool
4174 pager_grep(struct view *view, struct line *line)
4176         const char *text[] = { line->data, NULL };
4178         return grep_text(view, text);
4181 static void
4182 pager_select(struct view *view, struct line *line)
4184         if (line->type == LINE_COMMIT) {
4185                 char *text = (char *)line->data + STRING_SIZE("commit ");
4187                 if (view->type != VIEW_PAGER)
4188                         string_copy_rev(view->ref, text);
4189                 string_copy_rev(ref_commit, text);
4190         }
4193 static struct view_ops pager_ops = {
4194         "line",
4195         NULL,
4196         NULL,
4197         pager_read,
4198         pager_draw,
4199         pager_request,
4200         pager_grep,
4201         pager_select,
4202 };
4204 static const char *log_argv[SIZEOF_ARG] = {
4205         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4206 };
4208 static enum request
4209 log_request(struct view *view, enum request request, struct line *line)
4211         switch (request) {
4212         case REQ_REFRESH:
4213                 load_refs();
4214                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4215                 return REQ_NONE;
4216         default:
4217                 return pager_request(view, request, line);
4218         }
4221 static struct view_ops log_ops = {
4222         "line",
4223         log_argv,
4224         NULL,
4225         pager_read,
4226         pager_draw,
4227         log_request,
4228         pager_grep,
4229         pager_select,
4230 };
4232 static const char *diff_argv[SIZEOF_ARG] = {
4233         "git", "show", "--pretty=fuller", "--no-color", "--root",
4234                 "--patch-with-stat", "--find-copies-harder", "-C",
4235                 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4236 };
4238 static struct view_ops diff_ops = {
4239         "line",
4240         diff_argv,
4241         NULL,
4242         pager_read,
4243         pager_draw,
4244         pager_request,
4245         pager_grep,
4246         pager_select,
4247 };
4249 /*
4250  * Help backend
4251  */
4253 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4255 static bool
4256 help_open_keymap_title(struct view *view, enum keymap keymap)
4258         struct line *line;
4260         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4261                                help_keymap_hidden[keymap] ? '+' : '-',
4262                                enum_name(keymap_table[keymap]));
4263         if (line)
4264                 line->other = keymap;
4266         return help_keymap_hidden[keymap];
4269 static void
4270 help_open_keymap(struct view *view, enum keymap keymap)
4272         const char *group = NULL;
4273         char buf[SIZEOF_STR];
4274         size_t bufpos;
4275         bool add_title = TRUE;
4276         int i;
4278         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4279                 const char *key = NULL;
4281                 if (req_info[i].request == REQ_NONE)
4282                         continue;
4284                 if (!req_info[i].request) {
4285                         group = req_info[i].help;
4286                         continue;
4287                 }
4289                 key = get_keys(keymap, req_info[i].request, TRUE);
4290                 if (!key || !*key)
4291                         continue;
4293                 if (add_title && help_open_keymap_title(view, keymap))
4294                         return;
4295                 add_title = FALSE;
4297                 if (group) {
4298                         add_line_text(view, group, LINE_HELP_GROUP);
4299                         group = NULL;
4300                 }
4302                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4303                                 enum_name(req_info[i]), req_info[i].help);
4304         }
4306         group = "External commands:";
4308         for (i = 0; i < run_requests; i++) {
4309                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4310                 const char *key;
4311                 int argc;
4313                 if (!req || req->keymap != keymap)
4314                         continue;
4316                 key = get_key_name(req->key);
4317                 if (!*key)
4318                         key = "(no key defined)";
4320                 if (add_title && help_open_keymap_title(view, keymap))
4321                         return;
4322                 if (group) {
4323                         add_line_text(view, group, LINE_HELP_GROUP);
4324                         group = NULL;
4325                 }
4327                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4328                         if (!string_format_from(buf, &bufpos, "%s%s",
4329                                                 argc ? " " : "", req->argv[argc]))
4330                                 return;
4332                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4333         }
4336 static bool
4337 help_open(struct view *view)
4339         enum keymap keymap;
4341         reset_view(view);
4342         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4343         add_line_text(view, "", LINE_DEFAULT);
4345         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4346                 help_open_keymap(view, keymap);
4348         return TRUE;
4351 static enum request
4352 help_request(struct view *view, enum request request, struct line *line)
4354         switch (request) {
4355         case REQ_ENTER:
4356                 if (line->type == LINE_HELP_KEYMAP) {
4357                         help_keymap_hidden[line->other] =
4358                                 !help_keymap_hidden[line->other];
4359                         view->p_restore = TRUE;
4360                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4361                 }
4363                 return REQ_NONE;
4364         default:
4365                 return pager_request(view, request, line);
4366         }
4369 static struct view_ops help_ops = {
4370         "line",
4371         NULL,
4372         help_open,
4373         NULL,
4374         pager_draw,
4375         help_request,
4376         pager_grep,
4377         pager_select,
4378 };
4381 /*
4382  * Tree backend
4383  */
4385 struct tree_stack_entry {
4386         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4387         unsigned long lineno;           /* Line number to restore */
4388         char *name;                     /* Position of name in opt_path */
4389 };
4391 /* The top of the path stack. */
4392 static struct tree_stack_entry *tree_stack = NULL;
4393 unsigned long tree_lineno = 0;
4395 static void
4396 pop_tree_stack_entry(void)
4398         struct tree_stack_entry *entry = tree_stack;
4400         tree_lineno = entry->lineno;
4401         entry->name[0] = 0;
4402         tree_stack = entry->prev;
4403         free(entry);
4406 static void
4407 push_tree_stack_entry(const char *name, unsigned long lineno)
4409         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4410         size_t pathlen = strlen(opt_path);
4412         if (!entry)
4413                 return;
4415         entry->prev = tree_stack;
4416         entry->name = opt_path + pathlen;
4417         tree_stack = entry;
4419         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4420                 pop_tree_stack_entry();
4421                 return;
4422         }
4424         /* Move the current line to the first tree entry. */
4425         tree_lineno = 1;
4426         entry->lineno = lineno;
4429 /* Parse output from git-ls-tree(1):
4430  *
4431  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4432  */
4434 #define SIZEOF_TREE_ATTR \
4435         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4437 #define SIZEOF_TREE_MODE \
4438         STRING_SIZE("100644 ")
4440 #define TREE_ID_OFFSET \
4441         STRING_SIZE("100644 blob ")
4443 struct tree_entry {
4444         char id[SIZEOF_REV];
4445         mode_t mode;
4446         struct time time;               /* Date from the author ident. */
4447         const char *author;             /* Author of the commit. */
4448         char name[1];
4449 };
4451 static const char *
4452 tree_path(const struct line *line)
4454         return ((struct tree_entry *) line->data)->name;
4457 static int
4458 tree_compare_entry(const struct line *line1, const struct line *line2)
4460         if (line1->type != line2->type)
4461                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4462         return strcmp(tree_path(line1), tree_path(line2));
4465 static const enum sort_field tree_sort_fields[] = {
4466         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4467 };
4468 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4470 static int
4471 tree_compare(const void *l1, const void *l2)
4473         const struct line *line1 = (const struct line *) l1;
4474         const struct line *line2 = (const struct line *) l2;
4475         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4476         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4478         if (line1->type == LINE_TREE_HEAD)
4479                 return -1;
4480         if (line2->type == LINE_TREE_HEAD)
4481                 return 1;
4483         switch (get_sort_field(tree_sort_state)) {
4484         case ORDERBY_DATE:
4485                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4487         case ORDERBY_AUTHOR:
4488                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4490         case ORDERBY_NAME:
4491         default:
4492                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4493         }
4497 static struct line *
4498 tree_entry(struct view *view, enum line_type type, const char *path,
4499            const char *mode, const char *id)
4501         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4502         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4504         if (!entry || !line) {
4505                 free(entry);
4506                 return NULL;
4507         }
4509         strncpy(entry->name, path, strlen(path));
4510         if (mode)
4511                 entry->mode = strtoul(mode, NULL, 8);
4512         if (id)
4513                 string_copy_rev(entry->id, id);
4515         return line;
4518 static bool
4519 tree_read_date(struct view *view, char *text, bool *read_date)
4521         static const char *author_name;
4522         static struct time author_time;
4524         if (!text && *read_date) {
4525                 *read_date = FALSE;
4526                 return TRUE;
4528         } else if (!text) {
4529                 char *path = *opt_path ? opt_path : ".";
4530                 /* Find next entry to process */
4531                 const char *log_file[] = {
4532                         "git", "log", "--no-color", "--pretty=raw",
4533                                 "--cc", "--raw", view->id, "--", path, NULL
4534                 };
4536                 if (!view->lines) {
4537                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4538                         report("Tree is empty");
4539                         return TRUE;
4540                 }
4542                 if (!start_update(view, log_file, opt_cdup)) {
4543                         report("Failed to load tree data");
4544                         return TRUE;
4545                 }
4547                 *read_date = TRUE;
4548                 return FALSE;
4550         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4551                 parse_author_line(text + STRING_SIZE("author "),
4552                                   &author_name, &author_time);
4554         } else if (*text == ':') {
4555                 char *pos;
4556                 size_t annotated = 1;
4557                 size_t i;
4559                 pos = strchr(text, '\t');
4560                 if (!pos)
4561                         return TRUE;
4562                 text = pos + 1;
4563                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4564                         text += strlen(opt_path);
4565                 pos = strchr(text, '/');
4566                 if (pos)
4567                         *pos = 0;
4569                 for (i = 1; i < view->lines; i++) {
4570                         struct line *line = &view->line[i];
4571                         struct tree_entry *entry = line->data;
4573                         annotated += !!entry->author;
4574                         if (entry->author || strcmp(entry->name, text))
4575                                 continue;
4577                         entry->author = author_name;
4578                         entry->time = author_time;
4579                         line->dirty = 1;
4580                         break;
4581                 }
4583                 if (annotated == view->lines)
4584                         io_kill(view->pipe);
4585         }
4586         return TRUE;
4589 static bool
4590 tree_read(struct view *view, char *text)
4592         static bool read_date = FALSE;
4593         struct tree_entry *data;
4594         struct line *entry, *line;
4595         enum line_type type;
4596         size_t textlen = text ? strlen(text) : 0;
4597         char *path = text + SIZEOF_TREE_ATTR;
4599         if (read_date || !text)
4600                 return tree_read_date(view, text, &read_date);
4602         if (textlen <= SIZEOF_TREE_ATTR)
4603                 return FALSE;
4604         if (view->lines == 0 &&
4605             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4606                 return FALSE;
4608         /* Strip the path part ... */
4609         if (*opt_path) {
4610                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4611                 size_t striplen = strlen(opt_path);
4613                 if (pathlen > striplen)
4614                         memmove(path, path + striplen,
4615                                 pathlen - striplen + 1);
4617                 /* Insert "link" to parent directory. */
4618                 if (view->lines == 1 &&
4619                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4620                         return FALSE;
4621         }
4623         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4624         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4625         if (!entry)
4626                 return FALSE;
4627         data = entry->data;
4629         /* Skip "Directory ..." and ".." line. */
4630         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4631                 if (tree_compare_entry(line, entry) <= 0)
4632                         continue;
4634                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4636                 line->data = data;
4637                 line->type = type;
4638                 for (; line <= entry; line++)
4639                         line->dirty = line->cleareol = 1;
4640                 return TRUE;
4641         }
4643         if (tree_lineno > view->lineno) {
4644                 view->lineno = tree_lineno;
4645                 tree_lineno = 0;
4646         }
4648         return TRUE;
4651 static bool
4652 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4654         struct tree_entry *entry = line->data;
4656         if (line->type == LINE_TREE_HEAD) {
4657                 if (draw_text(view, line->type, "Directory path /", TRUE))
4658                         return TRUE;
4659         } else {
4660                 if (draw_mode(view, entry->mode))
4661                         return TRUE;
4663                 if (opt_author && draw_author(view, entry->author))
4664                         return TRUE;
4666                 if (opt_date && draw_date(view, &entry->time))
4667                         return TRUE;
4668         }
4669         if (draw_text(view, line->type, entry->name, TRUE))
4670                 return TRUE;
4671         return TRUE;
4674 static void
4675 open_blob_editor(const char *id)
4677         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4678         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4679         int fd = mkstemp(file);
4681         if (fd == -1)
4682                 report("Failed to create temporary file");
4683         else if (!io_run_append(blob_argv, fd))
4684                 report("Failed to save blob data to file");
4685         else
4686                 open_editor(file);
4687         if (fd != -1)
4688                 unlink(file);
4691 static enum request
4692 tree_request(struct view *view, enum request request, struct line *line)
4694         enum open_flags flags;
4695         struct tree_entry *entry = line->data;
4697         switch (request) {
4698         case REQ_VIEW_BLAME:
4699                 if (line->type != LINE_TREE_FILE) {
4700                         report("Blame only supported for files");
4701                         return REQ_NONE;
4702                 }
4704                 string_copy(opt_ref, view->vid);
4705                 return request;
4707         case REQ_EDIT:
4708                 if (line->type != LINE_TREE_FILE) {
4709                         report("Edit only supported for files");
4710                 } else if (!is_head_commit(view->vid)) {
4711                         open_blob_editor(entry->id);
4712                 } else {
4713                         open_editor(opt_file);
4714                 }
4715                 return REQ_NONE;
4717         case REQ_TOGGLE_SORT_FIELD:
4718         case REQ_TOGGLE_SORT_ORDER:
4719                 sort_view(view, request, &tree_sort_state, tree_compare);
4720                 return REQ_NONE;
4722         case REQ_PARENT:
4723                 if (!*opt_path) {
4724                         /* quit view if at top of tree */
4725                         return REQ_VIEW_CLOSE;
4726                 }
4727                 /* fake 'cd  ..' */
4728                 line = &view->line[1];
4729                 break;
4731         case REQ_ENTER:
4732                 break;
4734         default:
4735                 return request;
4736         }
4738         /* Cleanup the stack if the tree view is at a different tree. */
4739         while (!*opt_path && tree_stack)
4740                 pop_tree_stack_entry();
4742         switch (line->type) {
4743         case LINE_TREE_DIR:
4744                 /* Depending on whether it is a subdirectory or parent link
4745                  * mangle the path buffer. */
4746                 if (line == &view->line[1] && *opt_path) {
4747                         pop_tree_stack_entry();
4749                 } else {
4750                         const char *basename = tree_path(line);
4752                         push_tree_stack_entry(basename, view->lineno);
4753                 }
4755                 /* Trees and subtrees share the same ID, so they are not not
4756                  * unique like blobs. */
4757                 flags = OPEN_RELOAD;
4758                 request = REQ_VIEW_TREE;
4759                 break;
4761         case LINE_TREE_FILE:
4762                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4763                 request = REQ_VIEW_BLOB;
4764                 break;
4766         default:
4767                 return REQ_NONE;
4768         }
4770         open_view(view, request, flags);
4771         if (request == REQ_VIEW_TREE)
4772                 view->lineno = tree_lineno;
4774         return REQ_NONE;
4777 static bool
4778 tree_grep(struct view *view, struct line *line)
4780         struct tree_entry *entry = line->data;
4781         const char *text[] = {
4782                 entry->name,
4783                 opt_author ? entry->author : "",
4784                 mkdate(&entry->time, opt_date),
4785                 NULL
4786         };
4788         return grep_text(view, text);
4791 static void
4792 tree_select(struct view *view, struct line *line)
4794         struct tree_entry *entry = line->data;
4796         if (line->type == LINE_TREE_FILE) {
4797                 string_copy_rev(ref_blob, entry->id);
4798                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4800         } else if (line->type != LINE_TREE_DIR) {
4801                 return;
4802         }
4804         string_copy_rev(view->ref, entry->id);
4807 static bool
4808 tree_prepare(struct view *view)
4810         if (view->lines == 0 && opt_prefix[0]) {
4811                 char *pos = opt_prefix;
4813                 while (pos && *pos) {
4814                         char *end = strchr(pos, '/');
4816                         if (end)
4817                                 *end = 0;
4818                         push_tree_stack_entry(pos, 0);
4819                         pos = end;
4820                         if (end) {
4821                                 *end = '/';
4822                                 pos++;
4823                         }
4824                 }
4826         } else if (strcmp(view->vid, view->id)) {
4827                 opt_path[0] = 0;
4828         }
4830         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4833 static const char *tree_argv[SIZEOF_ARG] = {
4834         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4835 };
4837 static struct view_ops tree_ops = {
4838         "file",
4839         tree_argv,
4840         NULL,
4841         tree_read,
4842         tree_draw,
4843         tree_request,
4844         tree_grep,
4845         tree_select,
4846         tree_prepare,
4847 };
4849 static bool
4850 blob_read(struct view *view, char *line)
4852         if (!line)
4853                 return TRUE;
4854         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4857 static enum request
4858 blob_request(struct view *view, enum request request, struct line *line)
4860         switch (request) {
4861         case REQ_EDIT:
4862                 open_blob_editor(view->vid);
4863                 return REQ_NONE;
4864         default:
4865                 return pager_request(view, request, line);
4866         }
4869 static const char *blob_argv[SIZEOF_ARG] = {
4870         "git", "cat-file", "blob", "%(blob)", NULL
4871 };
4873 static struct view_ops blob_ops = {
4874         "line",
4875         blob_argv,
4876         NULL,
4877         blob_read,
4878         pager_draw,
4879         blob_request,
4880         pager_grep,
4881         pager_select,
4882 };
4884 /*
4885  * Blame backend
4886  *
4887  * Loading the blame view is a two phase job:
4888  *
4889  *  1. File content is read either using opt_file from the
4890  *     filesystem or using git-cat-file.
4891  *  2. Then blame information is incrementally added by
4892  *     reading output from git-blame.
4893  */
4895 struct blame_commit {
4896         char id[SIZEOF_REV];            /* SHA1 ID. */
4897         char title[128];                /* First line of the commit message. */
4898         const char *author;             /* Author of the commit. */
4899         struct time time;               /* Date from the author ident. */
4900         char filename[128];             /* Name of file. */
4901         bool has_previous;              /* Was a "previous" line detected. */
4902 };
4904 struct blame {
4905         struct blame_commit *commit;
4906         unsigned long lineno;
4907         char text[1];
4908 };
4910 static bool
4911 blame_open(struct view *view)
4913         char path[SIZEOF_STR];
4914         size_t i;
4916         if (!view->prev && *opt_prefix) {
4917                 string_copy(path, opt_file);
4918                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4919                         return FALSE;
4920         }
4922         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4923                 const char *blame_cat_file_argv[] = {
4924                         "git", "cat-file", "blob", path, NULL
4925                 };
4927                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4928                     !start_update(view, blame_cat_file_argv, opt_cdup))
4929                         return FALSE;
4930         }
4932         /* First pass: remove multiple references to the same commit. */
4933         for (i = 0; i < view->lines; i++) {
4934                 struct blame *blame = view->line[i].data;
4936                 if (blame->commit && blame->commit->id[0])
4937                         blame->commit->id[0] = 0;
4938                 else
4939                         blame->commit = NULL;
4940         }
4942         /* Second pass: free existing references. */
4943         for (i = 0; i < view->lines; i++) {
4944                 struct blame *blame = view->line[i].data;
4946                 if (blame->commit)
4947                         free(blame->commit);
4948         }
4950         setup_update(view, opt_file);
4951         string_format(view->ref, "%s ...", opt_file);
4953         return TRUE;
4956 static struct blame_commit *
4957 get_blame_commit(struct view *view, const char *id)
4959         size_t i;
4961         for (i = 0; i < view->lines; i++) {
4962                 struct blame *blame = view->line[i].data;
4964                 if (!blame->commit)
4965                         continue;
4967                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4968                         return blame->commit;
4969         }
4971         {
4972                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4974                 if (commit)
4975                         string_ncopy(commit->id, id, SIZEOF_REV);
4976                 return commit;
4977         }
4980 static bool
4981 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4983         const char *pos = *posref;
4985         *posref = NULL;
4986         pos = strchr(pos + 1, ' ');
4987         if (!pos || !isdigit(pos[1]))
4988                 return FALSE;
4989         *number = atoi(pos + 1);
4990         if (*number < min || *number > max)
4991                 return FALSE;
4993         *posref = pos;
4994         return TRUE;
4997 static struct blame_commit *
4998 parse_blame_commit(struct view *view, const char *text, int *blamed)
5000         struct blame_commit *commit;
5001         struct blame *blame;
5002         const char *pos = text + SIZEOF_REV - 2;
5003         size_t orig_lineno = 0;
5004         size_t lineno;
5005         size_t group;
5007         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5008                 return NULL;
5010         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5011             !parse_number(&pos, &lineno, 1, view->lines) ||
5012             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5013                 return NULL;
5015         commit = get_blame_commit(view, text);
5016         if (!commit)
5017                 return NULL;
5019         *blamed += group;
5020         while (group--) {
5021                 struct line *line = &view->line[lineno + group - 1];
5023                 blame = line->data;
5024                 blame->commit = commit;
5025                 blame->lineno = orig_lineno + group - 1;
5026                 line->dirty = 1;
5027         }
5029         return commit;
5032 static bool
5033 blame_read_file(struct view *view, const char *line, bool *read_file)
5035         if (!line) {
5036                 const char *blame_argv[] = {
5037                         "git", "blame", "--incremental",
5038                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5039                 };
5041                 if (view->lines == 0 && !view->prev)
5042                         die("No blame exist for %s", view->vid);
5044                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5045                         report("Failed to load blame data");
5046                         return TRUE;
5047                 }
5049                 *read_file = FALSE;
5050                 return FALSE;
5052         } else {
5053                 size_t linelen = strlen(line);
5054                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5056                 if (!blame)
5057                         return FALSE;
5059                 blame->commit = NULL;
5060                 strncpy(blame->text, line, linelen);
5061                 blame->text[linelen] = 0;
5062                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5063         }
5066 static bool
5067 match_blame_header(const char *name, char **line)
5069         size_t namelen = strlen(name);
5070         bool matched = !strncmp(name, *line, namelen);
5072         if (matched)
5073                 *line += namelen;
5075         return matched;
5078 static bool
5079 blame_read(struct view *view, char *line)
5081         static struct blame_commit *commit = NULL;
5082         static int blamed = 0;
5083         static bool read_file = TRUE;
5085         if (read_file)
5086                 return blame_read_file(view, line, &read_file);
5088         if (!line) {
5089                 /* Reset all! */
5090                 commit = NULL;
5091                 blamed = 0;
5092                 read_file = TRUE;
5093                 string_format(view->ref, "%s", view->vid);
5094                 if (view_is_displayed(view)) {
5095                         update_view_title(view);
5096                         redraw_view_from(view, 0);
5097                 }
5098                 return TRUE;
5099         }
5101         if (!commit) {
5102                 commit = parse_blame_commit(view, line, &blamed);
5103                 string_format(view->ref, "%s %2d%%", view->vid,
5104                               view->lines ? blamed * 100 / view->lines : 0);
5106         } else if (match_blame_header("author ", &line)) {
5107                 commit->author = get_author(line);
5109         } else if (match_blame_header("author-time ", &line)) {
5110                 parse_timesec(&commit->time, line);
5112         } else if (match_blame_header("author-tz ", &line)) {
5113                 parse_timezone(&commit->time, line);
5115         } else if (match_blame_header("summary ", &line)) {
5116                 string_ncopy(commit->title, line, strlen(line));
5118         } else if (match_blame_header("previous ", &line)) {
5119                 commit->has_previous = TRUE;
5121         } else if (match_blame_header("filename ", &line)) {
5122                 string_ncopy(commit->filename, line, strlen(line));
5123                 commit = NULL;
5124         }
5126         return TRUE;
5129 static bool
5130 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5132         struct blame *blame = line->data;
5133         struct time *time = NULL;
5134         const char *id = NULL, *author = NULL;
5135         char text[SIZEOF_STR];
5137         if (blame->commit && *blame->commit->filename) {
5138                 id = blame->commit->id;
5139                 author = blame->commit->author;
5140                 time = &blame->commit->time;
5141         }
5143         if (opt_date && draw_date(view, time))
5144                 return TRUE;
5146         if (opt_author && draw_author(view, author))
5147                 return TRUE;
5149         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5150                 return TRUE;
5152         if (draw_lineno(view, lineno))
5153                 return TRUE;
5155         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5156         draw_text(view, LINE_DEFAULT, text, TRUE);
5157         return TRUE;
5160 static bool
5161 check_blame_commit(struct blame *blame, bool check_null_id)
5163         if (!blame->commit)
5164                 report("Commit data not loaded yet");
5165         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5166                 report("No commit exist for the selected line");
5167         else
5168                 return TRUE;
5169         return FALSE;
5172 static void
5173 setup_blame_parent_line(struct view *view, struct blame *blame)
5175         const char *diff_tree_argv[] = {
5176                 "git", "diff-tree", "-U0", blame->commit->id,
5177                         "--", blame->commit->filename, NULL
5178         };
5179         struct io io;
5180         int parent_lineno = -1;
5181         int blamed_lineno = -1;
5182         char *line;
5184         if (!io_run(&io, IO_RD, NULL, diff_tree_argv))
5185                 return;
5187         while ((line = io_get(&io, '\n', TRUE))) {
5188                 if (*line == '@') {
5189                         char *pos = strchr(line, '+');
5191                         parent_lineno = atoi(line + 4);
5192                         if (pos)
5193                                 blamed_lineno = atoi(pos + 1);
5195                 } else if (*line == '+' && parent_lineno != -1) {
5196                         if (blame->lineno == blamed_lineno - 1 &&
5197                             !strcmp(blame->text, line + 1)) {
5198                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5199                                 break;
5200                         }
5201                         blamed_lineno++;
5202                 }
5203         }
5205         io_done(&io);
5208 static enum request
5209 blame_request(struct view *view, enum request request, struct line *line)
5211         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5212         struct blame *blame = line->data;
5214         switch (request) {
5215         case REQ_VIEW_BLAME:
5216                 if (check_blame_commit(blame, TRUE)) {
5217                         string_copy(opt_ref, blame->commit->id);
5218                         string_copy(opt_file, blame->commit->filename);
5219                         if (blame->lineno)
5220                                 view->lineno = blame->lineno;
5221                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5222                 }
5223                 break;
5225         case REQ_PARENT:
5226                 if (check_blame_commit(blame, TRUE) &&
5227                     select_commit_parent(blame->commit->id, opt_ref,
5228                                          blame->commit->filename)) {
5229                         string_copy(opt_file, blame->commit->filename);
5230                         setup_blame_parent_line(view, blame);
5231                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5232                 }
5233                 break;
5235         case REQ_ENTER:
5236                 if (!check_blame_commit(blame, FALSE))
5237                         break;
5239                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5240                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5241                         break;
5243                 if (!strcmp(blame->commit->id, NULL_ID)) {
5244                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5245                         const char *diff_index_argv[] = {
5246                                 "git", "diff-index", "--root", "--patch-with-stat",
5247                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5248                         };
5250                         if (!blame->commit->has_previous) {
5251                                 diff_index_argv[1] = "diff";
5252                                 diff_index_argv[2] = "--no-color";
5253                                 diff_index_argv[6] = "--";
5254                                 diff_index_argv[7] = "/dev/null";
5255                         }
5257                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5258                                 report("Failed to allocate diff command");
5259                                 break;
5260                         }
5261                         flags |= OPEN_PREPARED;
5262                 }
5264                 open_view(view, REQ_VIEW_DIFF, flags);
5265                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5266                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5267                 break;
5269         default:
5270                 return request;
5271         }
5273         return REQ_NONE;
5276 static bool
5277 blame_grep(struct view *view, struct line *line)
5279         struct blame *blame = line->data;
5280         struct blame_commit *commit = blame->commit;
5281         const char *text[] = {
5282                 blame->text,
5283                 commit ? commit->title : "",
5284                 commit ? commit->id : "",
5285                 commit && opt_author ? commit->author : "",
5286                 commit ? mkdate(&commit->time, opt_date) : "",
5287                 NULL
5288         };
5290         return grep_text(view, text);
5293 static void
5294 blame_select(struct view *view, struct line *line)
5296         struct blame *blame = line->data;
5297         struct blame_commit *commit = blame->commit;
5299         if (!commit)
5300                 return;
5302         if (!strcmp(commit->id, NULL_ID))
5303                 string_ncopy(ref_commit, "HEAD", 4);
5304         else
5305                 string_copy_rev(ref_commit, commit->id);
5308 static struct view_ops blame_ops = {
5309         "line",
5310         NULL,
5311         blame_open,
5312         blame_read,
5313         blame_draw,
5314         blame_request,
5315         blame_grep,
5316         blame_select,
5317 };
5319 /*
5320  * Branch backend
5321  */
5323 struct branch {
5324         const char *author;             /* Author of the last commit. */
5325         struct time time;               /* Date of the last activity. */
5326         const struct ref *ref;          /* Name and commit ID information. */
5327 };
5329 static const struct ref branch_all;
5331 static const enum sort_field branch_sort_fields[] = {
5332         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5333 };
5334 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5336 static int
5337 branch_compare(const void *l1, const void *l2)
5339         const struct branch *branch1 = ((const struct line *) l1)->data;
5340         const struct branch *branch2 = ((const struct line *) l2)->data;
5342         switch (get_sort_field(branch_sort_state)) {
5343         case ORDERBY_DATE:
5344                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5346         case ORDERBY_AUTHOR:
5347                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5349         case ORDERBY_NAME:
5350         default:
5351                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5352         }
5355 static bool
5356 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5358         struct branch *branch = line->data;
5359         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5361         if (opt_date && draw_date(view, &branch->time))
5362                 return TRUE;
5364         if (opt_author && draw_author(view, branch->author))
5365                 return TRUE;
5367         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5368         return TRUE;
5371 static enum request
5372 branch_request(struct view *view, enum request request, struct line *line)
5374         struct branch *branch = line->data;
5376         switch (request) {
5377         case REQ_REFRESH:
5378                 load_refs();
5379                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5380                 return REQ_NONE;
5382         case REQ_TOGGLE_SORT_FIELD:
5383         case REQ_TOGGLE_SORT_ORDER:
5384                 sort_view(view, request, &branch_sort_state, branch_compare);
5385                 return REQ_NONE;
5387         case REQ_ENTER:
5388         {
5389                 const struct ref *ref = branch->ref;
5390                 const char *all_branches_argv[] = {
5391                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5392                               "--topo-order",
5393                               ref == &branch_all ? "--all" : ref->name, NULL
5394                 };
5395                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5397                 if (!prepare_update(main_view, all_branches_argv, NULL))
5398                         report("Failed to load view of all branches");
5399                 else
5400                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5401                 return REQ_NONE;
5402         }
5403         default:
5404                 return request;
5405         }
5408 static bool
5409 branch_read(struct view *view, char *line)
5411         static char id[SIZEOF_REV];
5412         struct branch *reference;
5413         size_t i;
5415         if (!line)
5416                 return TRUE;
5418         switch (get_line_type(line)) {
5419         case LINE_COMMIT:
5420                 string_copy_rev(id, line + STRING_SIZE("commit "));
5421                 return TRUE;
5423         case LINE_AUTHOR:
5424                 for (i = 0, reference = NULL; i < view->lines; i++) {
5425                         struct branch *branch = view->line[i].data;
5427                         if (strcmp(branch->ref->id, id))
5428                                 continue;
5430                         view->line[i].dirty = TRUE;
5431                         if (reference) {
5432                                 branch->author = reference->author;
5433                                 branch->time = reference->time;
5434                                 continue;
5435                         }
5437                         parse_author_line(line + STRING_SIZE("author "),
5438                                           &branch->author, &branch->time);
5439                         reference = branch;
5440                 }
5441                 return TRUE;
5443         default:
5444                 return TRUE;
5445         }
5449 static bool
5450 branch_open_visitor(void *data, const struct ref *ref)
5452         struct view *view = data;
5453         struct branch *branch;
5455         if (ref->tag || ref->ltag || ref->remote)
5456                 return TRUE;
5458         branch = calloc(1, sizeof(*branch));
5459         if (!branch)
5460                 return FALSE;
5462         branch->ref = ref;
5463         return !!add_line_data(view, branch, LINE_DEFAULT);
5466 static bool
5467 branch_open(struct view *view)
5469         const char *branch_log[] = {
5470                 "git", "log", "--no-color", "--pretty=raw",
5471                         "--simplify-by-decoration", "--all", NULL
5472         };
5474         if (!start_update(view, branch_log, NULL)) {
5475                 report("Failed to load branch data");
5476                 return TRUE;
5477         }
5479         setup_update(view, view->id);
5480         branch_open_visitor(view, &branch_all);
5481         foreach_ref(branch_open_visitor, view);
5482         view->p_restore = TRUE;
5484         return TRUE;
5487 static bool
5488 branch_grep(struct view *view, struct line *line)
5490         struct branch *branch = line->data;
5491         const char *text[] = {
5492                 branch->ref->name,
5493                 branch->author,
5494                 NULL
5495         };
5497         return grep_text(view, text);
5500 static void
5501 branch_select(struct view *view, struct line *line)
5503         struct branch *branch = line->data;
5505         string_copy_rev(view->ref, branch->ref->id);
5506         string_copy_rev(ref_commit, branch->ref->id);
5507         string_copy_rev(ref_head, branch->ref->id);
5508         string_copy_rev(ref_branch, branch->ref->name);
5511 static struct view_ops branch_ops = {
5512         "branch",
5513         NULL,
5514         branch_open,
5515         branch_read,
5516         branch_draw,
5517         branch_request,
5518         branch_grep,
5519         branch_select,
5520 };
5522 /*
5523  * Status backend
5524  */
5526 struct status {
5527         char status;
5528         struct {
5529                 mode_t mode;
5530                 char rev[SIZEOF_REV];
5531                 char name[SIZEOF_STR];
5532         } old;
5533         struct {
5534                 mode_t mode;
5535                 char rev[SIZEOF_REV];
5536                 char name[SIZEOF_STR];
5537         } new;
5538 };
5540 static char status_onbranch[SIZEOF_STR];
5541 static struct status stage_status;
5542 static enum line_type stage_line_type;
5543 static size_t stage_chunks;
5544 static int *stage_chunk;
5546 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5548 /* This should work even for the "On branch" line. */
5549 static inline bool
5550 status_has_none(struct view *view, struct line *line)
5552         return line < view->line + view->lines && !line[1].data;
5555 /* Get fields from the diff line:
5556  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5557  */
5558 static inline bool
5559 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5561         const char *old_mode = buf +  1;
5562         const char *new_mode = buf +  8;
5563         const char *old_rev  = buf + 15;
5564         const char *new_rev  = buf + 56;
5565         const char *status   = buf + 97;
5567         if (bufsize < 98 ||
5568             old_mode[-1] != ':' ||
5569             new_mode[-1] != ' ' ||
5570             old_rev[-1]  != ' ' ||
5571             new_rev[-1]  != ' ' ||
5572             status[-1]   != ' ')
5573                 return FALSE;
5575         file->status = *status;
5577         string_copy_rev(file->old.rev, old_rev);
5578         string_copy_rev(file->new.rev, new_rev);
5580         file->old.mode = strtoul(old_mode, NULL, 8);
5581         file->new.mode = strtoul(new_mode, NULL, 8);
5583         file->old.name[0] = file->new.name[0] = 0;
5585         return TRUE;
5588 static bool
5589 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5591         struct status *unmerged = NULL;
5592         char *buf;
5593         struct io io;
5595         if (!io_run(&io, IO_RD, opt_cdup, argv))
5596                 return FALSE;
5598         add_line_data(view, NULL, type);
5600         while ((buf = io_get(&io, 0, TRUE))) {
5601                 struct status *file = unmerged;
5603                 if (!file) {
5604                         file = calloc(1, sizeof(*file));
5605                         if (!file || !add_line_data(view, file, type))
5606                                 goto error_out;
5607                 }
5609                 /* Parse diff info part. */
5610                 if (status) {
5611                         file->status = status;
5612                         if (status == 'A')
5613                                 string_copy(file->old.rev, NULL_ID);
5615                 } else if (!file->status || file == unmerged) {
5616                         if (!status_get_diff(file, buf, strlen(buf)))
5617                                 goto error_out;
5619                         buf = io_get(&io, 0, TRUE);
5620                         if (!buf)
5621                                 break;
5623                         /* Collapse all modified entries that follow an
5624                          * associated unmerged entry. */
5625                         if (unmerged == file) {
5626                                 unmerged->status = 'U';
5627                                 unmerged = NULL;
5628                         } else if (file->status == 'U') {
5629                                 unmerged = file;
5630                         }
5631                 }
5633                 /* Grab the old name for rename/copy. */
5634                 if (!*file->old.name &&
5635                     (file->status == 'R' || file->status == 'C')) {
5636                         string_ncopy(file->old.name, buf, strlen(buf));
5638                         buf = io_get(&io, 0, TRUE);
5639                         if (!buf)
5640                                 break;
5641                 }
5643                 /* git-ls-files just delivers a NUL separated list of
5644                  * file names similar to the second half of the
5645                  * git-diff-* output. */
5646                 string_ncopy(file->new.name, buf, strlen(buf));
5647                 if (!*file->old.name)
5648                         string_copy(file->old.name, file->new.name);
5649                 file = NULL;
5650         }
5652         if (io_error(&io)) {
5653 error_out:
5654                 io_done(&io);
5655                 return FALSE;
5656         }
5658         if (!view->line[view->lines - 1].data)
5659                 add_line_data(view, NULL, LINE_STAT_NONE);
5661         io_done(&io);
5662         return TRUE;
5665 /* Don't show unmerged entries in the staged section. */
5666 static const char *status_diff_index_argv[] = {
5667         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5668                              "--cached", "-M", "HEAD", NULL
5669 };
5671 static const char *status_diff_files_argv[] = {
5672         "git", "diff-files", "-z", NULL
5673 };
5675 static const char *status_list_other_argv[] = {
5676         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5677 };
5679 static const char *status_list_no_head_argv[] = {
5680         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5681 };
5683 static const char *update_index_argv[] = {
5684         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5685 };
5687 /* Restore the previous line number to stay in the context or select a
5688  * line with something that can be updated. */
5689 static void
5690 status_restore(struct view *view)
5692         if (view->p_lineno >= view->lines)
5693                 view->p_lineno = view->lines - 1;
5694         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5695                 view->p_lineno++;
5696         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5697                 view->p_lineno--;
5699         /* If the above fails, always skip the "On branch" line. */
5700         if (view->p_lineno < view->lines)
5701                 view->lineno = view->p_lineno;
5702         else
5703                 view->lineno = 1;
5705         if (view->lineno < view->offset)
5706                 view->offset = view->lineno;
5707         else if (view->offset + view->height <= view->lineno)
5708                 view->offset = view->lineno - view->height + 1;
5710         view->p_restore = FALSE;
5713 static void
5714 status_update_onbranch(void)
5716         static const char *paths[][2] = {
5717                 { "rebase-apply/rebasing",      "Rebasing" },
5718                 { "rebase-apply/applying",      "Applying mailbox" },
5719                 { "rebase-apply/",              "Rebasing mailbox" },
5720                 { "rebase-merge/interactive",   "Interactive rebase" },
5721                 { "rebase-merge/",              "Rebase merge" },
5722                 { "MERGE_HEAD",                 "Merging" },
5723                 { "BISECT_LOG",                 "Bisecting" },
5724                 { "HEAD",                       "On branch" },
5725         };
5726         char buf[SIZEOF_STR];
5727         struct stat stat;
5728         int i;
5730         if (is_initial_commit()) {
5731                 string_copy(status_onbranch, "Initial commit");
5732                 return;
5733         }
5735         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5736                 char *head = opt_head;
5738                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5739                     lstat(buf, &stat) < 0)
5740                         continue;
5742                 if (!*opt_head) {
5743                         struct io io;
5745                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5746                             io_read_buf(&io, buf, sizeof(buf))) {
5747                                 head = buf;
5748                                 if (!prefixcmp(head, "refs/heads/"))
5749                                         head += STRING_SIZE("refs/heads/");
5750                         }
5751                 }
5753                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5754                         string_copy(status_onbranch, opt_head);
5755                 return;
5756         }
5758         string_copy(status_onbranch, "Not currently on any branch");
5761 /* First parse staged info using git-diff-index(1), then parse unstaged
5762  * info using git-diff-files(1), and finally untracked files using
5763  * git-ls-files(1). */
5764 static bool
5765 status_open(struct view *view)
5767         reset_view(view);
5769         add_line_data(view, NULL, LINE_STAT_HEAD);
5770         status_update_onbranch();
5772         io_run_bg(update_index_argv);
5774         if (is_initial_commit()) {
5775                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5776                         return FALSE;
5777         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5778                 return FALSE;
5779         }
5781         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5782             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5783                 return FALSE;
5785         /* Restore the exact position or use the specialized restore
5786          * mode? */
5787         if (!view->p_restore)
5788                 status_restore(view);
5789         return TRUE;
5792 static bool
5793 status_draw(struct view *view, struct line *line, unsigned int lineno)
5795         struct status *status = line->data;
5796         enum line_type type;
5797         const char *text;
5799         if (!status) {
5800                 switch (line->type) {
5801                 case LINE_STAT_STAGED:
5802                         type = LINE_STAT_SECTION;
5803                         text = "Changes to be committed:";
5804                         break;
5806                 case LINE_STAT_UNSTAGED:
5807                         type = LINE_STAT_SECTION;
5808                         text = "Changed but not updated:";
5809                         break;
5811                 case LINE_STAT_UNTRACKED:
5812                         type = LINE_STAT_SECTION;
5813                         text = "Untracked files:";
5814                         break;
5816                 case LINE_STAT_NONE:
5817                         type = LINE_DEFAULT;
5818                         text = "  (no files)";
5819                         break;
5821                 case LINE_STAT_HEAD:
5822                         type = LINE_STAT_HEAD;
5823                         text = status_onbranch;
5824                         break;
5826                 default:
5827                         return FALSE;
5828                 }
5829         } else {
5830                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5832                 buf[0] = status->status;
5833                 if (draw_text(view, line->type, buf, TRUE))
5834                         return TRUE;
5835                 type = LINE_DEFAULT;
5836                 text = status->new.name;
5837         }
5839         draw_text(view, type, text, TRUE);
5840         return TRUE;
5843 static enum request
5844 status_load_error(struct view *view, struct view *stage, const char *path)
5846         if (displayed_views() == 2 || display[current_view] != view)
5847                 maximize_view(view);
5848         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5849         return REQ_NONE;
5852 static enum request
5853 status_enter(struct view *view, struct line *line)
5855         struct status *status = line->data;
5856         const char *oldpath = status ? status->old.name : NULL;
5857         /* Diffs for unmerged entries are empty when passing the new
5858          * path, so leave it empty. */
5859         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5860         const char *info;
5861         enum open_flags split;
5862         struct view *stage = VIEW(REQ_VIEW_STAGE);
5864         if (line->type == LINE_STAT_NONE ||
5865             (!status && line[1].type == LINE_STAT_NONE)) {
5866                 report("No file to diff");
5867                 return REQ_NONE;
5868         }
5870         switch (line->type) {
5871         case LINE_STAT_STAGED:
5872                 if (is_initial_commit()) {
5873                         const char *no_head_diff_argv[] = {
5874                                 "git", "diff", "--no-color", "--patch-with-stat",
5875                                         "--", "/dev/null", newpath, NULL
5876                         };
5878                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5879                                 return status_load_error(view, stage, newpath);
5880                 } else {
5881                         const char *index_show_argv[] = {
5882                                 "git", "diff-index", "--root", "--patch-with-stat",
5883                                         "-C", "-M", "--cached", "HEAD", "--",
5884                                         oldpath, newpath, NULL
5885                         };
5887                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5888                                 return status_load_error(view, stage, newpath);
5889                 }
5891                 if (status)
5892                         info = "Staged changes to %s";
5893                 else
5894                         info = "Staged changes";
5895                 break;
5897         case LINE_STAT_UNSTAGED:
5898         {
5899                 const char *files_show_argv[] = {
5900                         "git", "diff-files", "--root", "--patch-with-stat",
5901                                 "-C", "-M", "--", oldpath, newpath, NULL
5902                 };
5904                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5905                         return status_load_error(view, stage, newpath);
5906                 if (status)
5907                         info = "Unstaged changes to %s";
5908                 else
5909                         info = "Unstaged changes";
5910                 break;
5911         }
5912         case LINE_STAT_UNTRACKED:
5913                 if (!newpath) {
5914                         report("No file to show");
5915                         return REQ_NONE;
5916                 }
5918                 if (!suffixcmp(status->new.name, -1, "/")) {
5919                         report("Cannot display a directory");
5920                         return REQ_NONE;
5921                 }
5923                 if (!prepare_update_file(stage, newpath))
5924                         return status_load_error(view, stage, newpath);
5925                 info = "Untracked file %s";
5926                 break;
5928         case LINE_STAT_HEAD:
5929                 return REQ_NONE;
5931         default:
5932                 die("line type %d not handled in switch", line->type);
5933         }
5935         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5936         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5937         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5938                 if (status) {
5939                         stage_status = *status;
5940                 } else {
5941                         memset(&stage_status, 0, sizeof(stage_status));
5942                 }
5944                 stage_line_type = line->type;
5945                 stage_chunks = 0;
5946                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5947         }
5949         return REQ_NONE;
5952 static bool
5953 status_exists(struct status *status, enum line_type type)
5955         struct view *view = VIEW(REQ_VIEW_STATUS);
5956         unsigned long lineno;
5958         for (lineno = 0; lineno < view->lines; lineno++) {
5959                 struct line *line = &view->line[lineno];
5960                 struct status *pos = line->data;
5962                 if (line->type != type)
5963                         continue;
5964                 if (!pos && (!status || !status->status) && line[1].data) {
5965                         select_view_line(view, lineno);
5966                         return TRUE;
5967                 }
5968                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5969                         select_view_line(view, lineno);
5970                         return TRUE;
5971                 }
5972         }
5974         return FALSE;
5978 static bool
5979 status_update_prepare(struct io *io, enum line_type type)
5981         const char *staged_argv[] = {
5982                 "git", "update-index", "-z", "--index-info", NULL
5983         };
5984         const char *others_argv[] = {
5985                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5986         };
5988         switch (type) {
5989         case LINE_STAT_STAGED:
5990                 return io_run(io, IO_WR, opt_cdup, staged_argv);
5992         case LINE_STAT_UNSTAGED:
5993         case LINE_STAT_UNTRACKED:
5994                 return io_run(io, IO_WR, opt_cdup, others_argv);
5996         default:
5997                 die("line type %d not handled in switch", type);
5998                 return FALSE;
5999         }
6002 static bool
6003 status_update_write(struct io *io, struct status *status, enum line_type type)
6005         char buf[SIZEOF_STR];
6006         size_t bufsize = 0;
6008         switch (type) {
6009         case LINE_STAT_STAGED:
6010                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6011                                         status->old.mode,
6012                                         status->old.rev,
6013                                         status->old.name, 0))
6014                         return FALSE;
6015                 break;
6017         case LINE_STAT_UNSTAGED:
6018         case LINE_STAT_UNTRACKED:
6019                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6020                         return FALSE;
6021                 break;
6023         default:
6024                 die("line type %d not handled in switch", type);
6025         }
6027         return io_write(io, buf, bufsize);
6030 static bool
6031 status_update_file(struct status *status, enum line_type type)
6033         struct io io;
6034         bool result;
6036         if (!status_update_prepare(&io, type))
6037                 return FALSE;
6039         result = status_update_write(&io, status, type);
6040         return io_done(&io) && result;
6043 static bool
6044 status_update_files(struct view *view, struct line *line)
6046         char buf[sizeof(view->ref)];
6047         struct io io;
6048         bool result = TRUE;
6049         struct line *pos = view->line + view->lines;
6050         int files = 0;
6051         int file, done;
6052         int cursor_y = -1, cursor_x = -1;
6054         if (!status_update_prepare(&io, line->type))
6055                 return FALSE;
6057         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6058                 files++;
6060         string_copy(buf, view->ref);
6061         getsyx(cursor_y, cursor_x);
6062         for (file = 0, done = 5; result && file < files; line++, file++) {
6063                 int almost_done = file * 100 / files;
6065                 if (almost_done > done) {
6066                         done = almost_done;
6067                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6068                                       file, files, done);
6069                         update_view_title(view);
6070                         setsyx(cursor_y, cursor_x);
6071                         doupdate();
6072                 }
6073                 result = status_update_write(&io, line->data, line->type);
6074         }
6075         string_copy(view->ref, buf);
6077         return io_done(&io) && result;
6080 static bool
6081 status_update(struct view *view)
6083         struct line *line = &view->line[view->lineno];
6085         assert(view->lines);
6087         if (!line->data) {
6088                 /* This should work even for the "On branch" line. */
6089                 if (line < view->line + view->lines && !line[1].data) {
6090                         report("Nothing to update");
6091                         return FALSE;
6092                 }
6094                 if (!status_update_files(view, line + 1)) {
6095                         report("Failed to update file status");
6096                         return FALSE;
6097                 }
6099         } else if (!status_update_file(line->data, line->type)) {
6100                 report("Failed to update file status");
6101                 return FALSE;
6102         }
6104         return TRUE;
6107 static bool
6108 status_revert(struct status *status, enum line_type type, bool has_none)
6110         if (!status || type != LINE_STAT_UNSTAGED) {
6111                 if (type == LINE_STAT_STAGED) {
6112                         report("Cannot revert changes to staged files");
6113                 } else if (type == LINE_STAT_UNTRACKED) {
6114                         report("Cannot revert changes to untracked files");
6115                 } else if (has_none) {
6116                         report("Nothing to revert");
6117                 } else {
6118                         report("Cannot revert changes to multiple files");
6119                 }
6121         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6122                 char mode[10] = "100644";
6123                 const char *reset_argv[] = {
6124                         "git", "update-index", "--cacheinfo", mode,
6125                                 status->old.rev, status->old.name, NULL
6126                 };
6127                 const char *checkout_argv[] = {
6128                         "git", "checkout", "--", status->old.name, NULL
6129                 };
6131                 if (status->status == 'U') {
6132                         string_format(mode, "%5o", status->old.mode);
6134                         if (status->old.mode == 0 && status->new.mode == 0) {
6135                                 reset_argv[2] = "--force-remove";
6136                                 reset_argv[3] = status->old.name;
6137                                 reset_argv[4] = NULL;
6138                         }
6140                         if (!io_run_fg(reset_argv, opt_cdup))
6141                                 return FALSE;
6142                         if (status->old.mode == 0 && status->new.mode == 0)
6143                                 return TRUE;
6144                 }
6146                 return io_run_fg(checkout_argv, opt_cdup);
6147         }
6149         return FALSE;
6152 static enum request
6153 status_request(struct view *view, enum request request, struct line *line)
6155         struct status *status = line->data;
6157         switch (request) {
6158         case REQ_STATUS_UPDATE:
6159                 if (!status_update(view))
6160                         return REQ_NONE;
6161                 break;
6163         case REQ_STATUS_REVERT:
6164                 if (!status_revert(status, line->type, status_has_none(view, line)))
6165                         return REQ_NONE;
6166                 break;
6168         case REQ_STATUS_MERGE:
6169                 if (!status || status->status != 'U') {
6170                         report("Merging only possible for files with unmerged status ('U').");
6171                         return REQ_NONE;
6172                 }
6173                 open_mergetool(status->new.name);
6174                 break;
6176         case REQ_EDIT:
6177                 if (!status)
6178                         return request;
6179                 if (status->status == 'D') {
6180                         report("File has been deleted.");
6181                         return REQ_NONE;
6182                 }
6184                 open_editor(status->new.name);
6185                 break;
6187         case REQ_VIEW_BLAME:
6188                 if (status)
6189                         opt_ref[0] = 0;
6190                 return request;
6192         case REQ_ENTER:
6193                 /* After returning the status view has been split to
6194                  * show the stage view. No further reloading is
6195                  * necessary. */
6196                 return status_enter(view, line);
6198         case REQ_REFRESH:
6199                 /* Simply reload the view. */
6200                 break;
6202         default:
6203                 return request;
6204         }
6206         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6208         return REQ_NONE;
6211 static void
6212 status_select(struct view *view, struct line *line)
6214         struct status *status = line->data;
6215         char file[SIZEOF_STR] = "all files";
6216         const char *text;
6217         const char *key;
6219         if (status && !string_format(file, "'%s'", status->new.name))
6220                 return;
6222         if (!status && line[1].type == LINE_STAT_NONE)
6223                 line++;
6225         switch (line->type) {
6226         case LINE_STAT_STAGED:
6227                 text = "Press %s to unstage %s for commit";
6228                 break;
6230         case LINE_STAT_UNSTAGED:
6231                 text = "Press %s to stage %s for commit";
6232                 break;
6234         case LINE_STAT_UNTRACKED:
6235                 text = "Press %s to stage %s for addition";
6236                 break;
6238         case LINE_STAT_HEAD:
6239         case LINE_STAT_NONE:
6240                 text = "Nothing to update";
6241                 break;
6243         default:
6244                 die("line type %d not handled in switch", line->type);
6245         }
6247         if (status && status->status == 'U') {
6248                 text = "Press %s to resolve conflict in %s";
6249                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6251         } else {
6252                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6253         }
6255         string_format(view->ref, text, key, file);
6256         if (status)
6257                 string_copy(opt_file, status->new.name);
6260 static bool
6261 status_grep(struct view *view, struct line *line)
6263         struct status *status = line->data;
6265         if (status) {
6266                 const char buf[2] = { status->status, 0 };
6267                 const char *text[] = { status->new.name, buf, NULL };
6269                 return grep_text(view, text);
6270         }
6272         return FALSE;
6275 static struct view_ops status_ops = {
6276         "file",
6277         NULL,
6278         status_open,
6279         NULL,
6280         status_draw,
6281         status_request,
6282         status_grep,
6283         status_select,
6284 };
6287 static bool
6288 stage_diff_write(struct io *io, struct line *line, struct line *end)
6290         while (line < end) {
6291                 if (!io_write(io, line->data, strlen(line->data)) ||
6292                     !io_write(io, "\n", 1))
6293                         return FALSE;
6294                 line++;
6295                 if (line->type == LINE_DIFF_CHUNK ||
6296                     line->type == LINE_DIFF_HEADER)
6297                         break;
6298         }
6300         return TRUE;
6303 static struct line *
6304 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6306         for (; view->line < line; line--)
6307                 if (line->type == type)
6308                         return line;
6310         return NULL;
6313 static bool
6314 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6316         const char *apply_argv[SIZEOF_ARG] = {
6317                 "git", "apply", "--whitespace=nowarn", NULL
6318         };
6319         struct line *diff_hdr;
6320         struct io io;
6321         int argc = 3;
6323         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6324         if (!diff_hdr)
6325                 return FALSE;
6327         if (!revert)
6328                 apply_argv[argc++] = "--cached";
6329         if (revert || stage_line_type == LINE_STAT_STAGED)
6330                 apply_argv[argc++] = "-R";
6331         apply_argv[argc++] = "-";
6332         apply_argv[argc++] = NULL;
6333         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6334                 return FALSE;
6336         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6337             !stage_diff_write(&io, chunk, view->line + view->lines))
6338                 chunk = NULL;
6340         io_done(&io);
6341         io_run_bg(update_index_argv);
6343         return chunk ? TRUE : FALSE;
6346 static bool
6347 stage_update(struct view *view, struct line *line)
6349         struct line *chunk = NULL;
6351         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6352                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6354         if (chunk) {
6355                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6356                         report("Failed to apply chunk");
6357                         return FALSE;
6358                 }
6360         } else if (!stage_status.status) {
6361                 view = VIEW(REQ_VIEW_STATUS);
6363                 for (line = view->line; line < view->line + view->lines; line++)
6364                         if (line->type == stage_line_type)
6365                                 break;
6367                 if (!status_update_files(view, line + 1)) {
6368                         report("Failed to update files");
6369                         return FALSE;
6370                 }
6372         } else if (!status_update_file(&stage_status, stage_line_type)) {
6373                 report("Failed to update file");
6374                 return FALSE;
6375         }
6377         return TRUE;
6380 static bool
6381 stage_revert(struct view *view, struct line *line)
6383         struct line *chunk = NULL;
6385         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6386                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6388         if (chunk) {
6389                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6390                         return FALSE;
6392                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6393                         report("Failed to revert chunk");
6394                         return FALSE;
6395                 }
6396                 return TRUE;
6398         } else {
6399                 return status_revert(stage_status.status ? &stage_status : NULL,
6400                                      stage_line_type, FALSE);
6401         }
6405 static void
6406 stage_next(struct view *view, struct line *line)
6408         int i;
6410         if (!stage_chunks) {
6411                 for (line = view->line; line < view->line + view->lines; line++) {
6412                         if (line->type != LINE_DIFF_CHUNK)
6413                                 continue;
6415                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6416                                 report("Allocation failure");
6417                                 return;
6418                         }
6420                         stage_chunk[stage_chunks++] = line - view->line;
6421                 }
6422         }
6424         for (i = 0; i < stage_chunks; i++) {
6425                 if (stage_chunk[i] > view->lineno) {
6426                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6427                         report("Chunk %d of %d", i + 1, stage_chunks);
6428                         return;
6429                 }
6430         }
6432         report("No next chunk found");
6435 static enum request
6436 stage_request(struct view *view, enum request request, struct line *line)
6438         switch (request) {
6439         case REQ_STATUS_UPDATE:
6440                 if (!stage_update(view, line))
6441                         return REQ_NONE;
6442                 break;
6444         case REQ_STATUS_REVERT:
6445                 if (!stage_revert(view, line))
6446                         return REQ_NONE;
6447                 break;
6449         case REQ_STAGE_NEXT:
6450                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6451                         report("File is untracked; press %s to add",
6452                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6453                         return REQ_NONE;
6454                 }
6455                 stage_next(view, line);
6456                 return REQ_NONE;
6458         case REQ_EDIT:
6459                 if (!stage_status.new.name[0])
6460                         return request;
6461                 if (stage_status.status == 'D') {
6462                         report("File has been deleted.");
6463                         return REQ_NONE;
6464                 }
6466                 open_editor(stage_status.new.name);
6467                 break;
6469         case REQ_REFRESH:
6470                 /* Reload everything ... */
6471                 break;
6473         case REQ_VIEW_BLAME:
6474                 if (stage_status.new.name[0]) {
6475                         string_copy(opt_file, stage_status.new.name);
6476                         opt_ref[0] = 0;
6477                 }
6478                 return request;
6480         case REQ_ENTER:
6481                 return pager_request(view, request, line);
6483         default:
6484                 return request;
6485         }
6487         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6488         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6490         /* Check whether the staged entry still exists, and close the
6491          * stage view if it doesn't. */
6492         if (!status_exists(&stage_status, stage_line_type)) {
6493                 status_restore(VIEW(REQ_VIEW_STATUS));
6494                 return REQ_VIEW_CLOSE;
6495         }
6497         if (stage_line_type == LINE_STAT_UNTRACKED) {
6498                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6499                         report("Cannot display a directory");
6500                         return REQ_NONE;
6501                 }
6503                 if (!prepare_update_file(view, stage_status.new.name)) {
6504                         report("Failed to open file: %s", strerror(errno));
6505                         return REQ_NONE;
6506                 }
6507         }
6508         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6510         return REQ_NONE;
6513 static struct view_ops stage_ops = {
6514         "line",
6515         NULL,
6516         NULL,
6517         pager_read,
6518         pager_draw,
6519         stage_request,
6520         pager_grep,
6521         pager_select,
6522 };
6525 /*
6526  * Revision graph
6527  */
6529 struct commit {
6530         char id[SIZEOF_REV];            /* SHA1 ID. */
6531         char title[128];                /* First line of the commit message. */
6532         const char *author;             /* Author of the commit. */
6533         struct time time;               /* Date from the author ident. */
6534         struct ref_list *refs;          /* Repository references. */
6535         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6536         size_t graph_size;              /* The width of the graph array. */
6537         bool has_parents;               /* Rewritten --parents seen. */
6538 };
6540 /* Size of rev graph with no  "padding" columns */
6541 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6543 struct rev_graph {
6544         struct rev_graph *prev, *next, *parents;
6545         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6546         size_t size;
6547         struct commit *commit;
6548         size_t pos;
6549         unsigned int boundary:1;
6550 };
6552 /* Parents of the commit being visualized. */
6553 static struct rev_graph graph_parents[4];
6555 /* The current stack of revisions on the graph. */
6556 static struct rev_graph graph_stacks[4] = {
6557         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6558         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6559         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6560         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6561 };
6563 static inline bool
6564 graph_parent_is_merge(struct rev_graph *graph)
6566         return graph->parents->size > 1;
6569 static inline void
6570 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6572         struct commit *commit = graph->commit;
6574         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6575                 commit->graph[commit->graph_size++] = symbol;
6578 static void
6579 clear_rev_graph(struct rev_graph *graph)
6581         graph->boundary = 0;
6582         graph->size = graph->pos = 0;
6583         graph->commit = NULL;
6584         memset(graph->parents, 0, sizeof(*graph->parents));
6587 static void
6588 done_rev_graph(struct rev_graph *graph)
6590         if (graph_parent_is_merge(graph) &&
6591             graph->pos < graph->size - 1 &&
6592             graph->next->size == graph->size + graph->parents->size - 1) {
6593                 size_t i = graph->pos + graph->parents->size - 1;
6595                 graph->commit->graph_size = i * 2;
6596                 while (i < graph->next->size - 1) {
6597                         append_to_rev_graph(graph, ' ');
6598                         append_to_rev_graph(graph, '\\');
6599                         i++;
6600                 }
6601         }
6603         clear_rev_graph(graph);
6606 static void
6607 push_rev_graph(struct rev_graph *graph, const char *parent)
6609         int i;
6611         /* "Collapse" duplicate parents lines.
6612          *
6613          * FIXME: This needs to also update update the drawn graph but
6614          * for now it just serves as a method for pruning graph lines. */
6615         for (i = 0; i < graph->size; i++)
6616                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6617                         return;
6619         if (graph->size < SIZEOF_REVITEMS) {
6620                 string_copy_rev(graph->rev[graph->size++], parent);
6621         }
6624 static chtype
6625 get_rev_graph_symbol(struct rev_graph *graph)
6627         chtype symbol;
6629         if (graph->boundary)
6630                 symbol = REVGRAPH_BOUND;
6631         else if (graph->parents->size == 0)
6632                 symbol = REVGRAPH_INIT;
6633         else if (graph_parent_is_merge(graph))
6634                 symbol = REVGRAPH_MERGE;
6635         else if (graph->pos >= graph->size)
6636                 symbol = REVGRAPH_BRANCH;
6637         else
6638                 symbol = REVGRAPH_COMMIT;
6640         return symbol;
6643 static void
6644 draw_rev_graph(struct rev_graph *graph)
6646         struct rev_filler {
6647                 chtype separator, line;
6648         };
6649         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6650         static struct rev_filler fillers[] = {
6651                 { ' ',  '|' },
6652                 { '`',  '.' },
6653                 { '\'', ' ' },
6654                 { '/',  ' ' },
6655         };
6656         chtype symbol = get_rev_graph_symbol(graph);
6657         struct rev_filler *filler;
6658         size_t i;
6660         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6661         filler = &fillers[DEFAULT];
6663         for (i = 0; i < graph->pos; i++) {
6664                 append_to_rev_graph(graph, filler->line);
6665                 if (graph_parent_is_merge(graph->prev) &&
6666                     graph->prev->pos == i)
6667                         filler = &fillers[RSHARP];
6669                 append_to_rev_graph(graph, filler->separator);
6670         }
6672         /* Place the symbol for this revision. */
6673         append_to_rev_graph(graph, symbol);
6675         if (graph->prev->size > graph->size)
6676                 filler = &fillers[RDIAG];
6677         else
6678                 filler = &fillers[DEFAULT];
6680         i++;
6682         for (; i < graph->size; i++) {
6683                 append_to_rev_graph(graph, filler->separator);
6684                 append_to_rev_graph(graph, filler->line);
6685                 if (graph_parent_is_merge(graph->prev) &&
6686                     i < graph->prev->pos + graph->parents->size)
6687                         filler = &fillers[RSHARP];
6688                 if (graph->prev->size > graph->size)
6689                         filler = &fillers[LDIAG];
6690         }
6692         if (graph->prev->size > graph->size) {
6693                 append_to_rev_graph(graph, filler->separator);
6694                 if (filler->line != ' ')
6695                         append_to_rev_graph(graph, filler->line);
6696         }
6699 /* Prepare the next rev graph */
6700 static void
6701 prepare_rev_graph(struct rev_graph *graph)
6703         size_t i;
6705         /* First, traverse all lines of revisions up to the active one. */
6706         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6707                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6708                         break;
6710                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6711         }
6713         /* Interleave the new revision parent(s). */
6714         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6715                 push_rev_graph(graph->next, graph->parents->rev[i]);
6717         /* Lastly, put any remaining revisions. */
6718         for (i = graph->pos + 1; i < graph->size; i++)
6719                 push_rev_graph(graph->next, graph->rev[i]);
6722 static void
6723 update_rev_graph(struct view *view, struct rev_graph *graph)
6725         /* If this is the finalizing update ... */
6726         if (graph->commit)
6727                 prepare_rev_graph(graph);
6729         /* Graph visualization needs a one rev look-ahead,
6730          * so the first update doesn't visualize anything. */
6731         if (!graph->prev->commit)
6732                 return;
6734         if (view->lines > 2)
6735                 view->line[view->lines - 3].dirty = 1;
6736         if (view->lines > 1)
6737                 view->line[view->lines - 2].dirty = 1;
6738         draw_rev_graph(graph->prev);
6739         done_rev_graph(graph->prev->prev);
6743 /*
6744  * Main view backend
6745  */
6747 static const char *main_argv[SIZEOF_ARG] = {
6748         "git", "log", "--no-color", "--pretty=raw", "--parents",
6749                 "--topo-order", "%(diff-args)", "%(rev-args)",
6750                 "--", "%(file-args)", NULL
6751 };
6753 static bool
6754 main_draw(struct view *view, struct line *line, unsigned int lineno)
6756         struct commit *commit = line->data;
6758         if (!commit->author)
6759                 return FALSE;
6761         if (opt_date && draw_date(view, &commit->time))
6762                 return TRUE;
6764         if (opt_author && draw_author(view, commit->author))
6765                 return TRUE;
6767         if (opt_rev_graph && commit->graph_size &&
6768             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6769                 return TRUE;
6771         if (opt_show_refs && commit->refs) {
6772                 size_t i;
6774                 for (i = 0; i < commit->refs->size; i++) {
6775                         struct ref *ref = commit->refs->refs[i];
6776                         enum line_type type;
6778                         if (ref->head)
6779                                 type = LINE_MAIN_HEAD;
6780                         else if (ref->ltag)
6781                                 type = LINE_MAIN_LOCAL_TAG;
6782                         else if (ref->tag)
6783                                 type = LINE_MAIN_TAG;
6784                         else if (ref->tracked)
6785                                 type = LINE_MAIN_TRACKED;
6786                         else if (ref->remote)
6787                                 type = LINE_MAIN_REMOTE;
6788                         else
6789                                 type = LINE_MAIN_REF;
6791                         if (draw_text(view, type, "[", TRUE) ||
6792                             draw_text(view, type, ref->name, TRUE) ||
6793                             draw_text(view, type, "]", TRUE))
6794                                 return TRUE;
6796                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6797                                 return TRUE;
6798                 }
6799         }
6801         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6802         return TRUE;
6805 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6806 static bool
6807 main_read(struct view *view, char *line)
6809         static struct rev_graph *graph = graph_stacks;
6810         enum line_type type;
6811         struct commit *commit;
6813         if (!line) {
6814                 int i;
6816                 if (!view->lines && !view->prev)
6817                         die("No revisions match the given arguments.");
6818                 if (view->lines > 0) {
6819                         commit = view->line[view->lines - 1].data;
6820                         view->line[view->lines - 1].dirty = 1;
6821                         if (!commit->author) {
6822                                 view->lines--;
6823                                 free(commit);
6824                                 graph->commit = NULL;
6825                         }
6826                 }
6827                 update_rev_graph(view, graph);
6829                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6830                         clear_rev_graph(&graph_stacks[i]);
6831                 return TRUE;
6832         }
6834         type = get_line_type(line);
6835         if (type == LINE_COMMIT) {
6836                 commit = calloc(1, sizeof(struct commit));
6837                 if (!commit)
6838                         return FALSE;
6840                 line += STRING_SIZE("commit ");
6841                 if (*line == '-') {
6842                         graph->boundary = 1;
6843                         line++;
6844                 }
6846                 string_copy_rev(commit->id, line);
6847                 commit->refs = get_ref_list(commit->id);
6848                 graph->commit = commit;
6849                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6851                 while ((line = strchr(line, ' '))) {
6852                         line++;
6853                         push_rev_graph(graph->parents, line);
6854                         commit->has_parents = TRUE;
6855                 }
6856                 return TRUE;
6857         }
6859         if (!view->lines)
6860                 return TRUE;
6861         commit = view->line[view->lines - 1].data;
6863         switch (type) {
6864         case LINE_PARENT:
6865                 if (commit->has_parents)
6866                         break;
6867                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6868                 break;
6870         case LINE_AUTHOR:
6871                 parse_author_line(line + STRING_SIZE("author "),
6872                                   &commit->author, &commit->time);
6873                 update_rev_graph(view, graph);
6874                 graph = graph->next;
6875                 break;
6877         default:
6878                 /* Fill in the commit title if it has not already been set. */
6879                 if (commit->title[0])
6880                         break;
6882                 /* Require titles to start with a non-space character at the
6883                  * offset used by git log. */
6884                 if (strncmp(line, "    ", 4))
6885                         break;
6886                 line += 4;
6887                 /* Well, if the title starts with a whitespace character,
6888                  * try to be forgiving.  Otherwise we end up with no title. */
6889                 while (isspace(*line))
6890                         line++;
6891                 if (*line == '\0')
6892                         break;
6893                 /* FIXME: More graceful handling of titles; append "..." to
6894                  * shortened titles, etc. */
6896                 string_expand(commit->title, sizeof(commit->title), line, 1);
6897                 view->line[view->lines - 1].dirty = 1;
6898         }
6900         return TRUE;
6903 static enum request
6904 main_request(struct view *view, enum request request, struct line *line)
6906         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6908         switch (request) {
6909         case REQ_ENTER:
6910                 open_view(view, REQ_VIEW_DIFF, flags);
6911                 break;
6912         case REQ_REFRESH:
6913                 load_refs();
6914                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6915                 break;
6916         default:
6917                 return request;
6918         }
6920         return REQ_NONE;
6923 static bool
6924 grep_refs(struct ref_list *list, regex_t *regex)
6926         regmatch_t pmatch;
6927         size_t i;
6929         if (!opt_show_refs || !list)
6930                 return FALSE;
6932         for (i = 0; i < list->size; i++) {
6933                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6934                         return TRUE;
6935         }
6937         return FALSE;
6940 static bool
6941 main_grep(struct view *view, struct line *line)
6943         struct commit *commit = line->data;
6944         const char *text[] = {
6945                 commit->title,
6946                 opt_author ? commit->author : "",
6947                 mkdate(&commit->time, opt_date),
6948                 NULL
6949         };
6951         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6954 static void
6955 main_select(struct view *view, struct line *line)
6957         struct commit *commit = line->data;
6959         string_copy_rev(view->ref, commit->id);
6960         string_copy_rev(ref_commit, view->ref);
6963 static struct view_ops main_ops = {
6964         "commit",
6965         main_argv,
6966         NULL,
6967         main_read,
6968         main_draw,
6969         main_request,
6970         main_grep,
6971         main_select,
6972 };
6975 /*
6976  * Status management
6977  */
6979 /* Whether or not the curses interface has been initialized. */
6980 static bool cursed = FALSE;
6982 /* Terminal hacks and workarounds. */
6983 static bool use_scroll_redrawwin;
6984 static bool use_scroll_status_wclear;
6986 /* The status window is used for polling keystrokes. */
6987 static WINDOW *status_win;
6989 /* Reading from the prompt? */
6990 static bool input_mode = FALSE;
6992 static bool status_empty = FALSE;
6994 /* Update status and title window. */
6995 static void
6996 report(const char *msg, ...)
6998         struct view *view = display[current_view];
7000         if (input_mode)
7001                 return;
7003         if (!view) {
7004                 char buf[SIZEOF_STR];
7005                 va_list args;
7007                 va_start(args, msg);
7008                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7009                         buf[sizeof(buf) - 1] = 0;
7010                         buf[sizeof(buf) - 2] = '.';
7011                         buf[sizeof(buf) - 3] = '.';
7012                         buf[sizeof(buf) - 4] = '.';
7013                 }
7014                 va_end(args);
7015                 die("%s", buf);
7016         }
7018         if (!status_empty || *msg) {
7019                 va_list args;
7021                 va_start(args, msg);
7023                 wmove(status_win, 0, 0);
7024                 if (view->has_scrolled && use_scroll_status_wclear)
7025                         wclear(status_win);
7026                 if (*msg) {
7027                         vwprintw(status_win, msg, args);
7028                         status_empty = FALSE;
7029                 } else {
7030                         status_empty = TRUE;
7031                 }
7032                 wclrtoeol(status_win);
7033                 wnoutrefresh(status_win);
7035                 va_end(args);
7036         }
7038         update_view_title(view);
7041 static void
7042 init_display(void)
7044         const char *term;
7045         int x, y;
7047         /* Initialize the curses library */
7048         if (isatty(STDIN_FILENO)) {
7049                 cursed = !!initscr();
7050                 opt_tty = stdin;
7051         } else {
7052                 /* Leave stdin and stdout alone when acting as a pager. */
7053                 opt_tty = fopen("/dev/tty", "r+");
7054                 if (!opt_tty)
7055                         die("Failed to open /dev/tty");
7056                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7057         }
7059         if (!cursed)
7060                 die("Failed to initialize curses");
7062         nonl();         /* Disable conversion and detect newlines from input. */
7063         cbreak();       /* Take input chars one at a time, no wait for \n */
7064         noecho();       /* Don't echo input */
7065         leaveok(stdscr, FALSE);
7067         if (has_colors())
7068                 init_colors();
7070         getmaxyx(stdscr, y, x);
7071         status_win = newwin(1, 0, y - 1, 0);
7072         if (!status_win)
7073                 die("Failed to create status window");
7075         /* Enable keyboard mapping */
7076         keypad(status_win, TRUE);
7077         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7079         TABSIZE = opt_tab_size;
7081         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7082         if (term && !strcmp(term, "gnome-terminal")) {
7083                 /* In the gnome-terminal-emulator, the message from
7084                  * scrolling up one line when impossible followed by
7085                  * scrolling down one line causes corruption of the
7086                  * status line. This is fixed by calling wclear. */
7087                 use_scroll_status_wclear = TRUE;
7088                 use_scroll_redrawwin = FALSE;
7090         } else if (term && !strcmp(term, "xrvt-xpm")) {
7091                 /* No problems with full optimizations in xrvt-(unicode)
7092                  * and aterm. */
7093                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7095         } else {
7096                 /* When scrolling in (u)xterm the last line in the
7097                  * scrolling direction will update slowly. */
7098                 use_scroll_redrawwin = TRUE;
7099                 use_scroll_status_wclear = FALSE;
7100         }
7103 static int
7104 get_input(int prompt_position)
7106         struct view *view;
7107         int i, key, cursor_y, cursor_x;
7109         if (prompt_position)
7110                 input_mode = TRUE;
7112         while (TRUE) {
7113                 bool loading = FALSE;
7115                 foreach_view (view, i) {
7116                         update_view(view);
7117                         if (view_is_displayed(view) && view->has_scrolled &&
7118                             use_scroll_redrawwin)
7119                                 redrawwin(view->win);
7120                         view->has_scrolled = FALSE;
7121                         if (view->pipe)
7122                                 loading = TRUE;
7123                 }
7125                 /* Update the cursor position. */
7126                 if (prompt_position) {
7127                         getbegyx(status_win, cursor_y, cursor_x);
7128                         cursor_x = prompt_position;
7129                 } else {
7130                         view = display[current_view];
7131                         getbegyx(view->win, cursor_y, cursor_x);
7132                         cursor_x = view->width - 1;
7133                         cursor_y += view->lineno - view->offset;
7134                 }
7135                 setsyx(cursor_y, cursor_x);
7137                 /* Refresh, accept single keystroke of input */
7138                 doupdate();
7139                 nodelay(status_win, loading);
7140                 key = wgetch(status_win);
7142                 /* wgetch() with nodelay() enabled returns ERR when
7143                  * there's no input. */
7144                 if (key == ERR) {
7146                 } else if (key == KEY_RESIZE) {
7147                         int height, width;
7149                         getmaxyx(stdscr, height, width);
7151                         wresize(status_win, 1, width);
7152                         mvwin(status_win, height - 1, 0);
7153                         wnoutrefresh(status_win);
7154                         resize_display();
7155                         redraw_display(TRUE);
7157                 } else {
7158                         input_mode = FALSE;
7159                         return key;
7160                 }
7161         }
7164 static char *
7165 prompt_input(const char *prompt, input_handler handler, void *data)
7167         enum input_status status = INPUT_OK;
7168         static char buf[SIZEOF_STR];
7169         size_t pos = 0;
7171         buf[pos] = 0;
7173         while (status == INPUT_OK || status == INPUT_SKIP) {
7174                 int key;
7176                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7177                 wclrtoeol(status_win);
7179                 key = get_input(pos + 1);
7180                 switch (key) {
7181                 case KEY_RETURN:
7182                 case KEY_ENTER:
7183                 case '\n':
7184                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7185                         break;
7187                 case KEY_BACKSPACE:
7188                         if (pos > 0)
7189                                 buf[--pos] = 0;
7190                         else
7191                                 status = INPUT_CANCEL;
7192                         break;
7194                 case KEY_ESC:
7195                         status = INPUT_CANCEL;
7196                         break;
7198                 default:
7199                         if (pos >= sizeof(buf)) {
7200                                 report("Input string too long");
7201                                 return NULL;
7202                         }
7204                         status = handler(data, buf, key);
7205                         if (status == INPUT_OK)
7206                                 buf[pos++] = (char) key;
7207                 }
7208         }
7210         /* Clear the status window */
7211         status_empty = FALSE;
7212         report("");
7214         if (status == INPUT_CANCEL)
7215                 return NULL;
7217         buf[pos++] = 0;
7219         return buf;
7222 static enum input_status
7223 prompt_yesno_handler(void *data, char *buf, int c)
7225         if (c == 'y' || c == 'Y')
7226                 return INPUT_STOP;
7227         if (c == 'n' || c == 'N')
7228                 return INPUT_CANCEL;
7229         return INPUT_SKIP;
7232 static bool
7233 prompt_yesno(const char *prompt)
7235         char prompt2[SIZEOF_STR];
7237         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7238                 return FALSE;
7240         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7243 static enum input_status
7244 read_prompt_handler(void *data, char *buf, int c)
7246         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7249 static char *
7250 read_prompt(const char *prompt)
7252         return prompt_input(prompt, read_prompt_handler, NULL);
7255 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7257         enum input_status status = INPUT_OK;
7258         int size = 0;
7260         while (items[size].text)
7261                 size++;
7263         while (status == INPUT_OK) {
7264                 const struct menu_item *item = &items[*selected];
7265                 int key;
7266                 int i;
7268                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7269                           prompt, *selected + 1, size);
7270                 if (item->hotkey)
7271                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7272                 wprintw(status_win, "%s", item->text);
7273                 wclrtoeol(status_win);
7275                 key = get_input(COLS - 1);
7276                 switch (key) {
7277                 case KEY_RETURN:
7278                 case KEY_ENTER:
7279                 case '\n':
7280                         status = INPUT_STOP;
7281                         break;
7283                 case KEY_LEFT:
7284                 case KEY_UP:
7285                         *selected = *selected - 1;
7286                         if (*selected < 0)
7287                                 *selected = size - 1;
7288                         break;
7290                 case KEY_RIGHT:
7291                 case KEY_DOWN:
7292                         *selected = (*selected + 1) % size;
7293                         break;
7295                 case KEY_ESC:
7296                         status = INPUT_CANCEL;
7297                         break;
7299                 default:
7300                         for (i = 0; items[i].text; i++)
7301                                 if (items[i].hotkey == key) {
7302                                         *selected = i;
7303                                         status = INPUT_STOP;
7304                                         break;
7305                                 }
7306                 }
7307         }
7309         /* Clear the status window */
7310         status_empty = FALSE;
7311         report("");
7313         return status != INPUT_CANCEL;
7316 /*
7317  * Repository properties
7318  */
7320 static struct ref **refs = NULL;
7321 static size_t refs_size = 0;
7322 static struct ref *refs_head = NULL;
7324 static struct ref_list **ref_lists = NULL;
7325 static size_t ref_lists_size = 0;
7327 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7328 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7329 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7331 static int
7332 compare_refs(const void *ref1_, const void *ref2_)
7334         const struct ref *ref1 = *(const struct ref **)ref1_;
7335         const struct ref *ref2 = *(const struct ref **)ref2_;
7337         if (ref1->tag != ref2->tag)
7338                 return ref2->tag - ref1->tag;
7339         if (ref1->ltag != ref2->ltag)
7340                 return ref2->ltag - ref2->ltag;
7341         if (ref1->head != ref2->head)
7342                 return ref2->head - ref1->head;
7343         if (ref1->tracked != ref2->tracked)
7344                 return ref2->tracked - ref1->tracked;
7345         if (ref1->remote != ref2->remote)
7346                 return ref2->remote - ref1->remote;
7347         return strcmp(ref1->name, ref2->name);
7350 static void
7351 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7353         size_t i;
7355         for (i = 0; i < refs_size; i++)
7356                 if (!visitor(data, refs[i]))
7357                         break;
7360 static struct ref *
7361 get_ref_head()
7363         return refs_head;
7366 static struct ref_list *
7367 get_ref_list(const char *id)
7369         struct ref_list *list;
7370         size_t i;
7372         for (i = 0; i < ref_lists_size; i++)
7373                 if (!strcmp(id, ref_lists[i]->id))
7374                         return ref_lists[i];
7376         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7377                 return NULL;
7378         list = calloc(1, sizeof(*list));
7379         if (!list)
7380                 return NULL;
7382         for (i = 0; i < refs_size; i++) {
7383                 if (!strcmp(id, refs[i]->id) &&
7384                     realloc_refs_list(&list->refs, list->size, 1))
7385                         list->refs[list->size++] = refs[i];
7386         }
7388         if (!list->refs) {
7389                 free(list);
7390                 return NULL;
7391         }
7393         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7394         ref_lists[ref_lists_size++] = list;
7395         return list;
7398 static int
7399 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7401         struct ref *ref = NULL;
7402         bool tag = FALSE;
7403         bool ltag = FALSE;
7404         bool remote = FALSE;
7405         bool tracked = FALSE;
7406         bool head = FALSE;
7407         int from = 0, to = refs_size - 1;
7409         if (!prefixcmp(name, "refs/tags/")) {
7410                 if (!suffixcmp(name, namelen, "^{}")) {
7411                         namelen -= 3;
7412                         name[namelen] = 0;
7413                 } else {
7414                         ltag = TRUE;
7415                 }
7417                 tag = TRUE;
7418                 namelen -= STRING_SIZE("refs/tags/");
7419                 name    += STRING_SIZE("refs/tags/");
7421         } else if (!prefixcmp(name, "refs/remotes/")) {
7422                 remote = TRUE;
7423                 namelen -= STRING_SIZE("refs/remotes/");
7424                 name    += STRING_SIZE("refs/remotes/");
7425                 tracked  = !strcmp(opt_remote, name);
7427         } else if (!prefixcmp(name, "refs/heads/")) {
7428                 namelen -= STRING_SIZE("refs/heads/");
7429                 name    += STRING_SIZE("refs/heads/");
7430                 if (!strncmp(opt_head, name, namelen))
7431                         return OK;
7433         } else if (!strcmp(name, "HEAD")) {
7434                 head     = TRUE;
7435                 if (*opt_head) {
7436                         namelen  = strlen(opt_head);
7437                         name     = opt_head;
7438                 }
7439         }
7441         /* If we are reloading or it's an annotated tag, replace the
7442          * previous SHA1 with the resolved commit id; relies on the fact
7443          * git-ls-remote lists the commit id of an annotated tag right
7444          * before the commit id it points to. */
7445         while (from <= to) {
7446                 size_t pos = (to + from) / 2;
7447                 int cmp = strcmp(name, refs[pos]->name);
7449                 if (!cmp) {
7450                         ref = refs[pos];
7451                         break;
7452                 }
7454                 if (cmp < 0)
7455                         to = pos - 1;
7456                 else
7457                         from = pos + 1;
7458         }
7460         if (!ref) {
7461                 if (!realloc_refs(&refs, refs_size, 1))
7462                         return ERR;
7463                 ref = calloc(1, sizeof(*ref) + namelen);
7464                 if (!ref)
7465                         return ERR;
7466                 memmove(refs + from + 1, refs + from,
7467                         (refs_size - from) * sizeof(*refs));
7468                 refs[from] = ref;
7469                 strncpy(ref->name, name, namelen);
7470                 refs_size++;
7471         }
7473         ref->head = head;
7474         ref->tag = tag;
7475         ref->ltag = ltag;
7476         ref->remote = remote;
7477         ref->tracked = tracked;
7478         string_copy_rev(ref->id, id);
7480         if (head)
7481                 refs_head = ref;
7482         return OK;
7485 static int
7486 load_refs(void)
7488         const char *head_argv[] = {
7489                 "git", "symbolic-ref", "HEAD", NULL
7490         };
7491         static const char *ls_remote_argv[SIZEOF_ARG] = {
7492                 "git", "ls-remote", opt_git_dir, NULL
7493         };
7494         static bool init = FALSE;
7495         size_t i;
7497         if (!init) {
7498                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7499                         die("TIG_LS_REMOTE contains too many arguments");
7500                 init = TRUE;
7501         }
7503         if (!*opt_git_dir)
7504                 return OK;
7506         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7507             !prefixcmp(opt_head, "refs/heads/")) {
7508                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7510                 memmove(opt_head, offset, strlen(offset) + 1);
7511         }
7513         refs_head = NULL;
7514         for (i = 0; i < refs_size; i++)
7515                 refs[i]->id[0] = 0;
7517         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7518                 return ERR;
7520         /* Update the ref lists to reflect changes. */
7521         for (i = 0; i < ref_lists_size; i++) {
7522                 struct ref_list *list = ref_lists[i];
7523                 size_t old, new;
7525                 for (old = new = 0; old < list->size; old++)
7526                         if (!strcmp(list->id, list->refs[old]->id))
7527                                 list->refs[new++] = list->refs[old];
7528                 list->size = new;
7529         }
7531         return OK;
7534 static void
7535 set_remote_branch(const char *name, const char *value, size_t valuelen)
7537         if (!strcmp(name, ".remote")) {
7538                 string_ncopy(opt_remote, value, valuelen);
7540         } else if (*opt_remote && !strcmp(name, ".merge")) {
7541                 size_t from = strlen(opt_remote);
7543                 if (!prefixcmp(value, "refs/heads/"))
7544                         value += STRING_SIZE("refs/heads/");
7546                 if (!string_format_from(opt_remote, &from, "/%s", value))
7547                         opt_remote[0] = 0;
7548         }
7551 static void
7552 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7554         const char *argv[SIZEOF_ARG] = { name, "=" };
7555         int argc = 1 + (cmd == option_set_command);
7556         int error = ERR;
7558         if (!argv_from_string(argv, &argc, value))
7559                 config_msg = "Too many option arguments";
7560         else
7561                 error = cmd(argc, argv);
7563         if (error == ERR)
7564                 warn("Option 'tig.%s': %s", name, config_msg);
7567 static bool
7568 set_environment_variable(const char *name, const char *value)
7570         size_t len = strlen(name) + 1 + strlen(value) + 1;
7571         char *env = malloc(len);
7573         if (env &&
7574             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7575             putenv(env) == 0)
7576                 return TRUE;
7577         free(env);
7578         return FALSE;
7581 static void
7582 set_work_tree(const char *value)
7584         char cwd[SIZEOF_STR];
7586         if (!getcwd(cwd, sizeof(cwd)))
7587                 die("Failed to get cwd path: %s", strerror(errno));
7588         if (chdir(opt_git_dir) < 0)
7589                 die("Failed to chdir(%s): %s", strerror(errno));
7590         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7591                 die("Failed to get git path: %s", strerror(errno));
7592         if (chdir(cwd) < 0)
7593                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7594         if (chdir(value) < 0)
7595                 die("Failed to chdir(%s): %s", value, strerror(errno));
7596         if (!getcwd(cwd, sizeof(cwd)))
7597                 die("Failed to get cwd path: %s", strerror(errno));
7598         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7599                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7600         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7601                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7602         opt_is_inside_work_tree = TRUE;
7605 static int
7606 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7608         if (!strcmp(name, "i18n.commitencoding"))
7609                 string_ncopy(opt_encoding, value, valuelen);
7611         else if (!strcmp(name, "core.editor"))
7612                 string_ncopy(opt_editor, value, valuelen);
7614         else if (!strcmp(name, "core.worktree"))
7615                 set_work_tree(value);
7617         else if (!prefixcmp(name, "tig.color."))
7618                 set_repo_config_option(name + 10, value, option_color_command);
7620         else if (!prefixcmp(name, "tig.bind."))
7621                 set_repo_config_option(name + 9, value, option_bind_command);
7623         else if (!prefixcmp(name, "tig."))
7624                 set_repo_config_option(name + 4, value, option_set_command);
7626         else if (*opt_head && !prefixcmp(name, "branch.") &&
7627                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7628                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7630         return OK;
7633 static int
7634 load_git_config(void)
7636         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7638         return io_run_load(config_list_argv, "=", read_repo_config_option);
7641 static int
7642 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7644         if (!opt_git_dir[0]) {
7645                 string_ncopy(opt_git_dir, name, namelen);
7647         } else if (opt_is_inside_work_tree == -1) {
7648                 /* This can be 3 different values depending on the
7649                  * version of git being used. If git-rev-parse does not
7650                  * understand --is-inside-work-tree it will simply echo
7651                  * the option else either "true" or "false" is printed.
7652                  * Default to true for the unknown case. */
7653                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7655         } else if (*name == '.') {
7656                 string_ncopy(opt_cdup, name, namelen);
7658         } else {
7659                 string_ncopy(opt_prefix, name, namelen);
7660         }
7662         return OK;
7665 static int
7666 load_repo_info(void)
7668         const char *rev_parse_argv[] = {
7669                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7670                         "--show-cdup", "--show-prefix", NULL
7671         };
7673         return io_run_load(rev_parse_argv, "=", read_repo_info);
7677 /*
7678  * Main
7679  */
7681 static const char usage[] =
7682 "tig " TIG_VERSION " (" __DATE__ ")\n"
7683 "\n"
7684 "Usage: tig        [options] [revs] [--] [paths]\n"
7685 "   or: tig show   [options] [revs] [--] [paths]\n"
7686 "   or: tig blame  [rev] path\n"
7687 "   or: tig status\n"
7688 "   or: tig <      [git command output]\n"
7689 "\n"
7690 "Options:\n"
7691 "  -v, --version   Show version and exit\n"
7692 "  -h, --help      Show help message and exit";
7694 static void __NORETURN
7695 quit(int sig)
7697         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7698         if (cursed)
7699                 endwin();
7700         exit(0);
7703 static void __NORETURN
7704 die(const char *err, ...)
7706         va_list args;
7708         endwin();
7710         va_start(args, err);
7711         fputs("tig: ", stderr);
7712         vfprintf(stderr, err, args);
7713         fputs("\n", stderr);
7714         va_end(args);
7716         exit(1);
7719 static void
7720 warn(const char *msg, ...)
7722         va_list args;
7724         va_start(args, msg);
7725         fputs("tig warning: ", stderr);
7726         vfprintf(stderr, msg, args);
7727         fputs("\n", stderr);
7728         va_end(args);
7731 static const char ***filter_args;
7733 static int
7734 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7736         return argv_append(filter_args, name) ? OK : ERR;
7739 static void
7740 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7742         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7743         const char **all_argv = NULL;
7745         filter_args = args;
7746         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7747             !argv_append_array(&all_argv, argv) ||
7748             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7749                 die("Failed to split arguments");
7750         argv_free(all_argv);
7751         free(all_argv);
7754 static void
7755 filter_options(const char *argv[])
7757         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7758         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7759         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7762 static enum request
7763 parse_options(int argc, const char *argv[])
7765         enum request request = REQ_VIEW_MAIN;
7766         const char *subcommand;
7767         bool seen_dashdash = FALSE;
7768         const char **filter_argv = NULL;
7769         int i;
7771         if (!isatty(STDIN_FILENO)) {
7772                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7773                 return REQ_VIEW_PAGER;
7774         }
7776         if (argc <= 1)
7777                 return REQ_VIEW_MAIN;
7779         subcommand = argv[1];
7780         if (!strcmp(subcommand, "status")) {
7781                 if (argc > 2)
7782                         warn("ignoring arguments after `%s'", subcommand);
7783                 return REQ_VIEW_STATUS;
7785         } else if (!strcmp(subcommand, "blame")) {
7786                 if (argc <= 2 || argc > 4)
7787                         die("invalid number of options to blame\n\n%s", usage);
7789                 i = 2;
7790                 if (argc == 4) {
7791                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7792                         i++;
7793                 }
7795                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7796                 return REQ_VIEW_BLAME;
7798         } else if (!strcmp(subcommand, "show")) {
7799                 request = REQ_VIEW_DIFF;
7801         } else {
7802                 subcommand = NULL;
7803         }
7805         for (i = 1 + !!subcommand; i < argc; i++) {
7806                 const char *opt = argv[i];
7808                 if (seen_dashdash) {
7809                         argv_append(&opt_file_args, opt);
7810                         continue;
7812                 } else if (!strcmp(opt, "--")) {
7813                         seen_dashdash = TRUE;
7814                         continue;
7816                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7817                         printf("tig version %s\n", TIG_VERSION);
7818                         quit(0);
7820                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7821                         printf("%s\n", usage);
7822                         quit(0);
7824                 } else if (!strcmp(opt, "--all")) {
7825                         argv_append(&opt_rev_args, opt);
7826                         continue;
7827                 }
7829                 if (!argv_append(&filter_argv, opt))
7830                         die("command too long");
7831         }
7833         if (filter_argv)
7834                 filter_options(filter_argv);
7836         return request;
7839 int
7840 main(int argc, const char *argv[])
7842         const char *codeset = "UTF-8";
7843         enum request request = parse_options(argc, argv);
7844         struct view *view;
7845         size_t i;
7847         signal(SIGINT, quit);
7848         signal(SIGPIPE, SIG_IGN);
7850         if (setlocale(LC_ALL, "")) {
7851                 codeset = nl_langinfo(CODESET);
7852         }
7854         if (load_repo_info() == ERR)
7855                 die("Failed to load repo info.");
7857         if (load_options() == ERR)
7858                 die("Failed to load user config.");
7860         if (load_git_config() == ERR)
7861                 die("Failed to load repo config.");
7863         /* Require a git repository unless when running in pager mode. */
7864         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7865                 die("Not a git repository");
7867         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7868                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7869                 if (opt_iconv_in == ICONV_NONE)
7870                         die("Failed to initialize character set conversion");
7871         }
7873         if (codeset && strcmp(codeset, "UTF-8")) {
7874                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7875                 if (opt_iconv_out == ICONV_NONE)
7876                         die("Failed to initialize character set conversion");
7877         }
7879         if (load_refs() == ERR)
7880                 die("Failed to load refs.");
7882         foreach_view (view, i) {
7883                 if (getenv(view->cmd_env))
7884                         warn("Use of the %s environment variable is deprecated,"
7885                              " use options or TIG_DIFF_ARGS instead",
7886                              view->cmd_env);
7887                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7888                         die("Too many arguments in the `%s` environment variable",
7889                             view->cmd_env);
7890         }
7892         init_display();
7894         while (view_driver(display[current_view], request)) {
7895                 int key = get_input(0);
7897                 view = display[current_view];
7898                 request = get_keybinding(view->keymap, key);
7900                 /* Some low-level request handling. This keeps access to
7901                  * status_win restricted. */
7902                 switch (request) {
7903                 case REQ_NONE:
7904                         report("Unknown key, press %s for help",
7905                                get_key(view->keymap, REQ_VIEW_HELP));
7906                         break;
7907                 case REQ_PROMPT:
7908                 {
7909                         char *cmd = read_prompt(":");
7911                         if (cmd && isdigit(*cmd)) {
7912                                 int lineno = view->lineno + 1;
7914                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7915                                         select_view_line(view, lineno - 1);
7916                                         report("");
7917                                 } else {
7918                                         report("Unable to parse '%s' as a line number", cmd);
7919                                 }
7921                         } else if (cmd) {
7922                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7923                                 const char *argv[SIZEOF_ARG] = { "git" };
7924                                 int argc = 1;
7926                                 /* When running random commands, initially show the
7927                                  * command in the title. However, it maybe later be
7928                                  * overwritten if a commit line is selected. */
7929                                 string_ncopy(next->ref, cmd, strlen(cmd));
7931                                 if (!argv_from_string(argv, &argc, cmd)) {
7932                                         report("Too many arguments");
7933                                 } else if (!prepare_update(next, argv, NULL)) {
7934                                         report("Failed to format command");
7935                                 } else {
7936                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7937                                 }
7938                         }
7940                         request = REQ_NONE;
7941                         break;
7942                 }
7943                 case REQ_SEARCH:
7944                 case REQ_SEARCH_BACK:
7945                 {
7946                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7947                         char *search = read_prompt(prompt);
7949                         if (search)
7950                                 string_ncopy(opt_search, search, strlen(search));
7951                         else if (*opt_search)
7952                                 request = request == REQ_SEARCH ?
7953                                         REQ_FIND_NEXT :
7954                                         REQ_FIND_PREV;
7955                         else
7956                                 request = REQ_NONE;
7957                         break;
7958                 }
7959                 default:
7960                         break;
7961                 }
7962         }
7964         quit(0);
7966         return 0;