Code

Deprecate use of TIG_{MAIN,DIFF,LOG,TREE,BLOB}_CMD environment variables
[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];
4915         if (!view->prev && *opt_prefix) {
4916                 string_copy(path, opt_file);
4917                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4918                         return FALSE;
4919         }
4921         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4922                 const char *blame_cat_file_argv[] = {
4923                         "git", "cat-file", "blob", path, NULL
4924                 };
4926                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4927                     !start_update(view, blame_cat_file_argv, opt_cdup))
4928                         return FALSE;
4929         }
4931         setup_update(view, opt_file);
4932         string_format(view->ref, "%s ...", opt_file);
4934         return TRUE;
4937 static struct blame_commit *
4938 get_blame_commit(struct view *view, const char *id)
4940         size_t i;
4942         for (i = 0; i < view->lines; i++) {
4943                 struct blame *blame = view->line[i].data;
4945                 if (!blame->commit)
4946                         continue;
4948                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4949                         return blame->commit;
4950         }
4952         {
4953                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4955                 if (commit)
4956                         string_ncopy(commit->id, id, SIZEOF_REV);
4957                 return commit;
4958         }
4961 static bool
4962 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4964         const char *pos = *posref;
4966         *posref = NULL;
4967         pos = strchr(pos + 1, ' ');
4968         if (!pos || !isdigit(pos[1]))
4969                 return FALSE;
4970         *number = atoi(pos + 1);
4971         if (*number < min || *number > max)
4972                 return FALSE;
4974         *posref = pos;
4975         return TRUE;
4978 static struct blame_commit *
4979 parse_blame_commit(struct view *view, const char *text, int *blamed)
4981         struct blame_commit *commit;
4982         struct blame *blame;
4983         const char *pos = text + SIZEOF_REV - 2;
4984         size_t orig_lineno = 0;
4985         size_t lineno;
4986         size_t group;
4988         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4989                 return NULL;
4991         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4992             !parse_number(&pos, &lineno, 1, view->lines) ||
4993             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4994                 return NULL;
4996         commit = get_blame_commit(view, text);
4997         if (!commit)
4998                 return NULL;
5000         *blamed += group;
5001         while (group--) {
5002                 struct line *line = &view->line[lineno + group - 1];
5004                 blame = line->data;
5005                 blame->commit = commit;
5006                 blame->lineno = orig_lineno + group - 1;
5007                 line->dirty = 1;
5008         }
5010         return commit;
5013 static bool
5014 blame_read_file(struct view *view, const char *line, bool *read_file)
5016         if (!line) {
5017                 const char *blame_argv[] = {
5018                         "git", "blame", "--incremental",
5019                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5020                 };
5022                 if (view->lines == 0 && !view->prev)
5023                         die("No blame exist for %s", view->vid);
5025                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5026                         report("Failed to load blame data");
5027                         return TRUE;
5028                 }
5030                 *read_file = FALSE;
5031                 return FALSE;
5033         } else {
5034                 size_t linelen = strlen(line);
5035                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5037                 if (!blame)
5038                         return FALSE;
5040                 blame->commit = NULL;
5041                 strncpy(blame->text, line, linelen);
5042                 blame->text[linelen] = 0;
5043                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5044         }
5047 static bool
5048 match_blame_header(const char *name, char **line)
5050         size_t namelen = strlen(name);
5051         bool matched = !strncmp(name, *line, namelen);
5053         if (matched)
5054                 *line += namelen;
5056         return matched;
5059 static bool
5060 blame_read(struct view *view, char *line)
5062         static struct blame_commit *commit = NULL;
5063         static int blamed = 0;
5064         static bool read_file = TRUE;
5066         if (read_file)
5067                 return blame_read_file(view, line, &read_file);
5069         if (!line) {
5070                 /* Reset all! */
5071                 commit = NULL;
5072                 blamed = 0;
5073                 read_file = TRUE;
5074                 string_format(view->ref, "%s", view->vid);
5075                 if (view_is_displayed(view)) {
5076                         update_view_title(view);
5077                         redraw_view_from(view, 0);
5078                 }
5079                 return TRUE;
5080         }
5082         if (!commit) {
5083                 commit = parse_blame_commit(view, line, &blamed);
5084                 string_format(view->ref, "%s %2d%%", view->vid,
5085                               view->lines ? blamed * 100 / view->lines : 0);
5087         } else if (match_blame_header("author ", &line)) {
5088                 commit->author = get_author(line);
5090         } else if (match_blame_header("author-time ", &line)) {
5091                 parse_timesec(&commit->time, line);
5093         } else if (match_blame_header("author-tz ", &line)) {
5094                 parse_timezone(&commit->time, line);
5096         } else if (match_blame_header("summary ", &line)) {
5097                 string_ncopy(commit->title, line, strlen(line));
5099         } else if (match_blame_header("previous ", &line)) {
5100                 commit->has_previous = TRUE;
5102         } else if (match_blame_header("filename ", &line)) {
5103                 string_ncopy(commit->filename, line, strlen(line));
5104                 commit = NULL;
5105         }
5107         return TRUE;
5110 static bool
5111 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5113         struct blame *blame = line->data;
5114         struct time *time = NULL;
5115         const char *id = NULL, *author = NULL;
5116         char text[SIZEOF_STR];
5118         if (blame->commit && *blame->commit->filename) {
5119                 id = blame->commit->id;
5120                 author = blame->commit->author;
5121                 time = &blame->commit->time;
5122         }
5124         if (opt_date && draw_date(view, time))
5125                 return TRUE;
5127         if (opt_author && draw_author(view, author))
5128                 return TRUE;
5130         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5131                 return TRUE;
5133         if (draw_lineno(view, lineno))
5134                 return TRUE;
5136         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5137         draw_text(view, LINE_DEFAULT, text, TRUE);
5138         return TRUE;
5141 static bool
5142 check_blame_commit(struct blame *blame, bool check_null_id)
5144         if (!blame->commit)
5145                 report("Commit data not loaded yet");
5146         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5147                 report("No commit exist for the selected line");
5148         else
5149                 return TRUE;
5150         return FALSE;
5153 static void
5154 setup_blame_parent_line(struct view *view, struct blame *blame)
5156         const char *diff_tree_argv[] = {
5157                 "git", "diff-tree", "-U0", blame->commit->id,
5158                         "--", blame->commit->filename, NULL
5159         };
5160         struct io io;
5161         int parent_lineno = -1;
5162         int blamed_lineno = -1;
5163         char *line;
5165         if (!io_run(&io, IO_RD, NULL, diff_tree_argv))
5166                 return;
5168         while ((line = io_get(&io, '\n', TRUE))) {
5169                 if (*line == '@') {
5170                         char *pos = strchr(line, '+');
5172                         parent_lineno = atoi(line + 4);
5173                         if (pos)
5174                                 blamed_lineno = atoi(pos + 1);
5176                 } else if (*line == '+' && parent_lineno != -1) {
5177                         if (blame->lineno == blamed_lineno - 1 &&
5178                             !strcmp(blame->text, line + 1)) {
5179                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5180                                 break;
5181                         }
5182                         blamed_lineno++;
5183                 }
5184         }
5186         io_done(&io);
5189 static enum request
5190 blame_request(struct view *view, enum request request, struct line *line)
5192         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5193         struct blame *blame = line->data;
5195         switch (request) {
5196         case REQ_VIEW_BLAME:
5197                 if (check_blame_commit(blame, TRUE)) {
5198                         string_copy(opt_ref, blame->commit->id);
5199                         string_copy(opt_file, blame->commit->filename);
5200                         if (blame->lineno)
5201                                 view->lineno = blame->lineno;
5202                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5203                 }
5204                 break;
5206         case REQ_PARENT:
5207                 if (check_blame_commit(blame, TRUE) &&
5208                     select_commit_parent(blame->commit->id, opt_ref,
5209                                          blame->commit->filename)) {
5210                         string_copy(opt_file, blame->commit->filename);
5211                         setup_blame_parent_line(view, blame);
5212                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5213                 }
5214                 break;
5216         case REQ_ENTER:
5217                 if (!check_blame_commit(blame, FALSE))
5218                         break;
5220                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5221                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5222                         break;
5224                 if (!strcmp(blame->commit->id, NULL_ID)) {
5225                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5226                         const char *diff_index_argv[] = {
5227                                 "git", "diff-index", "--root", "--patch-with-stat",
5228                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5229                         };
5231                         if (!blame->commit->has_previous) {
5232                                 diff_index_argv[1] = "diff";
5233                                 diff_index_argv[2] = "--no-color";
5234                                 diff_index_argv[6] = "--";
5235                                 diff_index_argv[7] = "/dev/null";
5236                         }
5238                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5239                                 report("Failed to allocate diff command");
5240                                 break;
5241                         }
5242                         flags |= OPEN_PREPARED;
5243                 }
5245                 open_view(view, REQ_VIEW_DIFF, flags);
5246                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5247                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5248                 break;
5250         default:
5251                 return request;
5252         }
5254         return REQ_NONE;
5257 static bool
5258 blame_grep(struct view *view, struct line *line)
5260         struct blame *blame = line->data;
5261         struct blame_commit *commit = blame->commit;
5262         const char *text[] = {
5263                 blame->text,
5264                 commit ? commit->title : "",
5265                 commit ? commit->id : "",
5266                 commit && opt_author ? commit->author : "",
5267                 commit ? mkdate(&commit->time, opt_date) : "",
5268                 NULL
5269         };
5271         return grep_text(view, text);
5274 static void
5275 blame_select(struct view *view, struct line *line)
5277         struct blame *blame = line->data;
5278         struct blame_commit *commit = blame->commit;
5280         if (!commit)
5281                 return;
5283         if (!strcmp(commit->id, NULL_ID))
5284                 string_ncopy(ref_commit, "HEAD", 4);
5285         else
5286                 string_copy_rev(ref_commit, commit->id);
5289 static struct view_ops blame_ops = {
5290         "line",
5291         NULL,
5292         blame_open,
5293         blame_read,
5294         blame_draw,
5295         blame_request,
5296         blame_grep,
5297         blame_select,
5298 };
5300 /*
5301  * Branch backend
5302  */
5304 struct branch {
5305         const char *author;             /* Author of the last commit. */
5306         struct time time;               /* Date of the last activity. */
5307         const struct ref *ref;          /* Name and commit ID information. */
5308 };
5310 static const struct ref branch_all;
5312 static const enum sort_field branch_sort_fields[] = {
5313         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5314 };
5315 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5317 static int
5318 branch_compare(const void *l1, const void *l2)
5320         const struct branch *branch1 = ((const struct line *) l1)->data;
5321         const struct branch *branch2 = ((const struct line *) l2)->data;
5323         switch (get_sort_field(branch_sort_state)) {
5324         case ORDERBY_DATE:
5325                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5327         case ORDERBY_AUTHOR:
5328                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5330         case ORDERBY_NAME:
5331         default:
5332                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5333         }
5336 static bool
5337 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5339         struct branch *branch = line->data;
5340         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5342         if (opt_date && draw_date(view, &branch->time))
5343                 return TRUE;
5345         if (opt_author && draw_author(view, branch->author))
5346                 return TRUE;
5348         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5349         return TRUE;
5352 static enum request
5353 branch_request(struct view *view, enum request request, struct line *line)
5355         struct branch *branch = line->data;
5357         switch (request) {
5358         case REQ_REFRESH:
5359                 load_refs();
5360                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5361                 return REQ_NONE;
5363         case REQ_TOGGLE_SORT_FIELD:
5364         case REQ_TOGGLE_SORT_ORDER:
5365                 sort_view(view, request, &branch_sort_state, branch_compare);
5366                 return REQ_NONE;
5368         case REQ_ENTER:
5369         {
5370                 const struct ref *ref = branch->ref;
5371                 const char *all_branches_argv[] = {
5372                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5373                               "--topo-order",
5374                               ref == &branch_all ? "--all" : ref->name, NULL
5375                 };
5376                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5378                 if (!prepare_update(main_view, all_branches_argv, NULL))
5379                         report("Failed to load view of all branches");
5380                 else
5381                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5382                 return REQ_NONE;
5383         }
5384         default:
5385                 return request;
5386         }
5389 static bool
5390 branch_read(struct view *view, char *line)
5392         static char id[SIZEOF_REV];
5393         struct branch *reference;
5394         size_t i;
5396         if (!line)
5397                 return TRUE;
5399         switch (get_line_type(line)) {
5400         case LINE_COMMIT:
5401                 string_copy_rev(id, line + STRING_SIZE("commit "));
5402                 return TRUE;
5404         case LINE_AUTHOR:
5405                 for (i = 0, reference = NULL; i < view->lines; i++) {
5406                         struct branch *branch = view->line[i].data;
5408                         if (strcmp(branch->ref->id, id))
5409                                 continue;
5411                         view->line[i].dirty = TRUE;
5412                         if (reference) {
5413                                 branch->author = reference->author;
5414                                 branch->time = reference->time;
5415                                 continue;
5416                         }
5418                         parse_author_line(line + STRING_SIZE("author "),
5419                                           &branch->author, &branch->time);
5420                         reference = branch;
5421                 }
5422                 return TRUE;
5424         default:
5425                 return TRUE;
5426         }
5430 static bool
5431 branch_open_visitor(void *data, const struct ref *ref)
5433         struct view *view = data;
5434         struct branch *branch;
5436         if (ref->tag || ref->ltag || ref->remote)
5437                 return TRUE;
5439         branch = calloc(1, sizeof(*branch));
5440         if (!branch)
5441                 return FALSE;
5443         branch->ref = ref;
5444         return !!add_line_data(view, branch, LINE_DEFAULT);
5447 static bool
5448 branch_open(struct view *view)
5450         const char *branch_log[] = {
5451                 "git", "log", "--no-color", "--pretty=raw",
5452                         "--simplify-by-decoration", "--all", NULL
5453         };
5455         if (!start_update(view, branch_log, NULL)) {
5456                 report("Failed to load branch data");
5457                 return TRUE;
5458         }
5460         setup_update(view, view->id);
5461         branch_open_visitor(view, &branch_all);
5462         foreach_ref(branch_open_visitor, view);
5463         view->p_restore = TRUE;
5465         return TRUE;
5468 static bool
5469 branch_grep(struct view *view, struct line *line)
5471         struct branch *branch = line->data;
5472         const char *text[] = {
5473                 branch->ref->name,
5474                 branch->author,
5475                 NULL
5476         };
5478         return grep_text(view, text);
5481 static void
5482 branch_select(struct view *view, struct line *line)
5484         struct branch *branch = line->data;
5486         string_copy_rev(view->ref, branch->ref->id);
5487         string_copy_rev(ref_commit, branch->ref->id);
5488         string_copy_rev(ref_head, branch->ref->id);
5489         string_copy_rev(ref_branch, branch->ref->name);
5492 static struct view_ops branch_ops = {
5493         "branch",
5494         NULL,
5495         branch_open,
5496         branch_read,
5497         branch_draw,
5498         branch_request,
5499         branch_grep,
5500         branch_select,
5501 };
5503 /*
5504  * Status backend
5505  */
5507 struct status {
5508         char status;
5509         struct {
5510                 mode_t mode;
5511                 char rev[SIZEOF_REV];
5512                 char name[SIZEOF_STR];
5513         } old;
5514         struct {
5515                 mode_t mode;
5516                 char rev[SIZEOF_REV];
5517                 char name[SIZEOF_STR];
5518         } new;
5519 };
5521 static char status_onbranch[SIZEOF_STR];
5522 static struct status stage_status;
5523 static enum line_type stage_line_type;
5524 static size_t stage_chunks;
5525 static int *stage_chunk;
5527 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5529 /* This should work even for the "On branch" line. */
5530 static inline bool
5531 status_has_none(struct view *view, struct line *line)
5533         return line < view->line + view->lines && !line[1].data;
5536 /* Get fields from the diff line:
5537  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5538  */
5539 static inline bool
5540 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5542         const char *old_mode = buf +  1;
5543         const char *new_mode = buf +  8;
5544         const char *old_rev  = buf + 15;
5545         const char *new_rev  = buf + 56;
5546         const char *status   = buf + 97;
5548         if (bufsize < 98 ||
5549             old_mode[-1] != ':' ||
5550             new_mode[-1] != ' ' ||
5551             old_rev[-1]  != ' ' ||
5552             new_rev[-1]  != ' ' ||
5553             status[-1]   != ' ')
5554                 return FALSE;
5556         file->status = *status;
5558         string_copy_rev(file->old.rev, old_rev);
5559         string_copy_rev(file->new.rev, new_rev);
5561         file->old.mode = strtoul(old_mode, NULL, 8);
5562         file->new.mode = strtoul(new_mode, NULL, 8);
5564         file->old.name[0] = file->new.name[0] = 0;
5566         return TRUE;
5569 static bool
5570 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5572         struct status *unmerged = NULL;
5573         char *buf;
5574         struct io io;
5576         if (!io_run(&io, IO_RD, opt_cdup, argv))
5577                 return FALSE;
5579         add_line_data(view, NULL, type);
5581         while ((buf = io_get(&io, 0, TRUE))) {
5582                 struct status *file = unmerged;
5584                 if (!file) {
5585                         file = calloc(1, sizeof(*file));
5586                         if (!file || !add_line_data(view, file, type))
5587                                 goto error_out;
5588                 }
5590                 /* Parse diff info part. */
5591                 if (status) {
5592                         file->status = status;
5593                         if (status == 'A')
5594                                 string_copy(file->old.rev, NULL_ID);
5596                 } else if (!file->status || file == unmerged) {
5597                         if (!status_get_diff(file, buf, strlen(buf)))
5598                                 goto error_out;
5600                         buf = io_get(&io, 0, TRUE);
5601                         if (!buf)
5602                                 break;
5604                         /* Collapse all modified entries that follow an
5605                          * associated unmerged entry. */
5606                         if (unmerged == file) {
5607                                 unmerged->status = 'U';
5608                                 unmerged = NULL;
5609                         } else if (file->status == 'U') {
5610                                 unmerged = file;
5611                         }
5612                 }
5614                 /* Grab the old name for rename/copy. */
5615                 if (!*file->old.name &&
5616                     (file->status == 'R' || file->status == 'C')) {
5617                         string_ncopy(file->old.name, buf, strlen(buf));
5619                         buf = io_get(&io, 0, TRUE);
5620                         if (!buf)
5621                                 break;
5622                 }
5624                 /* git-ls-files just delivers a NUL separated list of
5625                  * file names similar to the second half of the
5626                  * git-diff-* output. */
5627                 string_ncopy(file->new.name, buf, strlen(buf));
5628                 if (!*file->old.name)
5629                         string_copy(file->old.name, file->new.name);
5630                 file = NULL;
5631         }
5633         if (io_error(&io)) {
5634 error_out:
5635                 io_done(&io);
5636                 return FALSE;
5637         }
5639         if (!view->line[view->lines - 1].data)
5640                 add_line_data(view, NULL, LINE_STAT_NONE);
5642         io_done(&io);
5643         return TRUE;
5646 /* Don't show unmerged entries in the staged section. */
5647 static const char *status_diff_index_argv[] = {
5648         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5649                              "--cached", "-M", "HEAD", NULL
5650 };
5652 static const char *status_diff_files_argv[] = {
5653         "git", "diff-files", "-z", NULL
5654 };
5656 static const char *status_list_other_argv[] = {
5657         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5658 };
5660 static const char *status_list_no_head_argv[] = {
5661         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5662 };
5664 static const char *update_index_argv[] = {
5665         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5666 };
5668 /* Restore the previous line number to stay in the context or select a
5669  * line with something that can be updated. */
5670 static void
5671 status_restore(struct view *view)
5673         if (view->p_lineno >= view->lines)
5674                 view->p_lineno = view->lines - 1;
5675         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5676                 view->p_lineno++;
5677         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5678                 view->p_lineno--;
5680         /* If the above fails, always skip the "On branch" line. */
5681         if (view->p_lineno < view->lines)
5682                 view->lineno = view->p_lineno;
5683         else
5684                 view->lineno = 1;
5686         if (view->lineno < view->offset)
5687                 view->offset = view->lineno;
5688         else if (view->offset + view->height <= view->lineno)
5689                 view->offset = view->lineno - view->height + 1;
5691         view->p_restore = FALSE;
5694 static void
5695 status_update_onbranch(void)
5697         static const char *paths[][2] = {
5698                 { "rebase-apply/rebasing",      "Rebasing" },
5699                 { "rebase-apply/applying",      "Applying mailbox" },
5700                 { "rebase-apply/",              "Rebasing mailbox" },
5701                 { "rebase-merge/interactive",   "Interactive rebase" },
5702                 { "rebase-merge/",              "Rebase merge" },
5703                 { "MERGE_HEAD",                 "Merging" },
5704                 { "BISECT_LOG",                 "Bisecting" },
5705                 { "HEAD",                       "On branch" },
5706         };
5707         char buf[SIZEOF_STR];
5708         struct stat stat;
5709         int i;
5711         if (is_initial_commit()) {
5712                 string_copy(status_onbranch, "Initial commit");
5713                 return;
5714         }
5716         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5717                 char *head = opt_head;
5719                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5720                     lstat(buf, &stat) < 0)
5721                         continue;
5723                 if (!*opt_head) {
5724                         struct io io;
5726                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5727                             io_read_buf(&io, buf, sizeof(buf))) {
5728                                 head = buf;
5729                                 if (!prefixcmp(head, "refs/heads/"))
5730                                         head += STRING_SIZE("refs/heads/");
5731                         }
5732                 }
5734                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5735                         string_copy(status_onbranch, opt_head);
5736                 return;
5737         }
5739         string_copy(status_onbranch, "Not currently on any branch");
5742 /* First parse staged info using git-diff-index(1), then parse unstaged
5743  * info using git-diff-files(1), and finally untracked files using
5744  * git-ls-files(1). */
5745 static bool
5746 status_open(struct view *view)
5748         reset_view(view);
5750         add_line_data(view, NULL, LINE_STAT_HEAD);
5751         status_update_onbranch();
5753         io_run_bg(update_index_argv);
5755         if (is_initial_commit()) {
5756                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5757                         return FALSE;
5758         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5759                 return FALSE;
5760         }
5762         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5763             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5764                 return FALSE;
5766         /* Restore the exact position or use the specialized restore
5767          * mode? */
5768         if (!view->p_restore)
5769                 status_restore(view);
5770         return TRUE;
5773 static bool
5774 status_draw(struct view *view, struct line *line, unsigned int lineno)
5776         struct status *status = line->data;
5777         enum line_type type;
5778         const char *text;
5780         if (!status) {
5781                 switch (line->type) {
5782                 case LINE_STAT_STAGED:
5783                         type = LINE_STAT_SECTION;
5784                         text = "Changes to be committed:";
5785                         break;
5787                 case LINE_STAT_UNSTAGED:
5788                         type = LINE_STAT_SECTION;
5789                         text = "Changed but not updated:";
5790                         break;
5792                 case LINE_STAT_UNTRACKED:
5793                         type = LINE_STAT_SECTION;
5794                         text = "Untracked files:";
5795                         break;
5797                 case LINE_STAT_NONE:
5798                         type = LINE_DEFAULT;
5799                         text = "  (no files)";
5800                         break;
5802                 case LINE_STAT_HEAD:
5803                         type = LINE_STAT_HEAD;
5804                         text = status_onbranch;
5805                         break;
5807                 default:
5808                         return FALSE;
5809                 }
5810         } else {
5811                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5813                 buf[0] = status->status;
5814                 if (draw_text(view, line->type, buf, TRUE))
5815                         return TRUE;
5816                 type = LINE_DEFAULT;
5817                 text = status->new.name;
5818         }
5820         draw_text(view, type, text, TRUE);
5821         return TRUE;
5824 static enum request
5825 status_load_error(struct view *view, struct view *stage, const char *path)
5827         if (displayed_views() == 2 || display[current_view] != view)
5828                 maximize_view(view);
5829         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5830         return REQ_NONE;
5833 static enum request
5834 status_enter(struct view *view, struct line *line)
5836         struct status *status = line->data;
5837         const char *oldpath = status ? status->old.name : NULL;
5838         /* Diffs for unmerged entries are empty when passing the new
5839          * path, so leave it empty. */
5840         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5841         const char *info;
5842         enum open_flags split;
5843         struct view *stage = VIEW(REQ_VIEW_STAGE);
5845         if (line->type == LINE_STAT_NONE ||
5846             (!status && line[1].type == LINE_STAT_NONE)) {
5847                 report("No file to diff");
5848                 return REQ_NONE;
5849         }
5851         switch (line->type) {
5852         case LINE_STAT_STAGED:
5853                 if (is_initial_commit()) {
5854                         const char *no_head_diff_argv[] = {
5855                                 "git", "diff", "--no-color", "--patch-with-stat",
5856                                         "--", "/dev/null", newpath, NULL
5857                         };
5859                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5860                                 return status_load_error(view, stage, newpath);
5861                 } else {
5862                         const char *index_show_argv[] = {
5863                                 "git", "diff-index", "--root", "--patch-with-stat",
5864                                         "-C", "-M", "--cached", "HEAD", "--",
5865                                         oldpath, newpath, NULL
5866                         };
5868                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5869                                 return status_load_error(view, stage, newpath);
5870                 }
5872                 if (status)
5873                         info = "Staged changes to %s";
5874                 else
5875                         info = "Staged changes";
5876                 break;
5878         case LINE_STAT_UNSTAGED:
5879         {
5880                 const char *files_show_argv[] = {
5881                         "git", "diff-files", "--root", "--patch-with-stat",
5882                                 "-C", "-M", "--", oldpath, newpath, NULL
5883                 };
5885                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5886                         return status_load_error(view, stage, newpath);
5887                 if (status)
5888                         info = "Unstaged changes to %s";
5889                 else
5890                         info = "Unstaged changes";
5891                 break;
5892         }
5893         case LINE_STAT_UNTRACKED:
5894                 if (!newpath) {
5895                         report("No file to show");
5896                         return REQ_NONE;
5897                 }
5899                 if (!suffixcmp(status->new.name, -1, "/")) {
5900                         report("Cannot display a directory");
5901                         return REQ_NONE;
5902                 }
5904                 if (!prepare_update_file(stage, newpath))
5905                         return status_load_error(view, stage, newpath);
5906                 info = "Untracked file %s";
5907                 break;
5909         case LINE_STAT_HEAD:
5910                 return REQ_NONE;
5912         default:
5913                 die("line type %d not handled in switch", line->type);
5914         }
5916         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5917         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5918         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5919                 if (status) {
5920                         stage_status = *status;
5921                 } else {
5922                         memset(&stage_status, 0, sizeof(stage_status));
5923                 }
5925                 stage_line_type = line->type;
5926                 stage_chunks = 0;
5927                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5928         }
5930         return REQ_NONE;
5933 static bool
5934 status_exists(struct status *status, enum line_type type)
5936         struct view *view = VIEW(REQ_VIEW_STATUS);
5937         unsigned long lineno;
5939         for (lineno = 0; lineno < view->lines; lineno++) {
5940                 struct line *line = &view->line[lineno];
5941                 struct status *pos = line->data;
5943                 if (line->type != type)
5944                         continue;
5945                 if (!pos && (!status || !status->status) && line[1].data) {
5946                         select_view_line(view, lineno);
5947                         return TRUE;
5948                 }
5949                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5950                         select_view_line(view, lineno);
5951                         return TRUE;
5952                 }
5953         }
5955         return FALSE;
5959 static bool
5960 status_update_prepare(struct io *io, enum line_type type)
5962         const char *staged_argv[] = {
5963                 "git", "update-index", "-z", "--index-info", NULL
5964         };
5965         const char *others_argv[] = {
5966                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5967         };
5969         switch (type) {
5970         case LINE_STAT_STAGED:
5971                 return io_run(io, IO_WR, opt_cdup, staged_argv);
5973         case LINE_STAT_UNSTAGED:
5974         case LINE_STAT_UNTRACKED:
5975                 return io_run(io, IO_WR, opt_cdup, others_argv);
5977         default:
5978                 die("line type %d not handled in switch", type);
5979                 return FALSE;
5980         }
5983 static bool
5984 status_update_write(struct io *io, struct status *status, enum line_type type)
5986         char buf[SIZEOF_STR];
5987         size_t bufsize = 0;
5989         switch (type) {
5990         case LINE_STAT_STAGED:
5991                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5992                                         status->old.mode,
5993                                         status->old.rev,
5994                                         status->old.name, 0))
5995                         return FALSE;
5996                 break;
5998         case LINE_STAT_UNSTAGED:
5999         case LINE_STAT_UNTRACKED:
6000                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6001                         return FALSE;
6002                 break;
6004         default:
6005                 die("line type %d not handled in switch", type);
6006         }
6008         return io_write(io, buf, bufsize);
6011 static bool
6012 status_update_file(struct status *status, enum line_type type)
6014         struct io io;
6015         bool result;
6017         if (!status_update_prepare(&io, type))
6018                 return FALSE;
6020         result = status_update_write(&io, status, type);
6021         return io_done(&io) && result;
6024 static bool
6025 status_update_files(struct view *view, struct line *line)
6027         char buf[sizeof(view->ref)];
6028         struct io io;
6029         bool result = TRUE;
6030         struct line *pos = view->line + view->lines;
6031         int files = 0;
6032         int file, done;
6033         int cursor_y = -1, cursor_x = -1;
6035         if (!status_update_prepare(&io, line->type))
6036                 return FALSE;
6038         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6039                 files++;
6041         string_copy(buf, view->ref);
6042         getsyx(cursor_y, cursor_x);
6043         for (file = 0, done = 5; result && file < files; line++, file++) {
6044                 int almost_done = file * 100 / files;
6046                 if (almost_done > done) {
6047                         done = almost_done;
6048                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6049                                       file, files, done);
6050                         update_view_title(view);
6051                         setsyx(cursor_y, cursor_x);
6052                         doupdate();
6053                 }
6054                 result = status_update_write(&io, line->data, line->type);
6055         }
6056         string_copy(view->ref, buf);
6058         return io_done(&io) && result;
6061 static bool
6062 status_update(struct view *view)
6064         struct line *line = &view->line[view->lineno];
6066         assert(view->lines);
6068         if (!line->data) {
6069                 /* This should work even for the "On branch" line. */
6070                 if (line < view->line + view->lines && !line[1].data) {
6071                         report("Nothing to update");
6072                         return FALSE;
6073                 }
6075                 if (!status_update_files(view, line + 1)) {
6076                         report("Failed to update file status");
6077                         return FALSE;
6078                 }
6080         } else if (!status_update_file(line->data, line->type)) {
6081                 report("Failed to update file status");
6082                 return FALSE;
6083         }
6085         return TRUE;
6088 static bool
6089 status_revert(struct status *status, enum line_type type, bool has_none)
6091         if (!status || type != LINE_STAT_UNSTAGED) {
6092                 if (type == LINE_STAT_STAGED) {
6093                         report("Cannot revert changes to staged files");
6094                 } else if (type == LINE_STAT_UNTRACKED) {
6095                         report("Cannot revert changes to untracked files");
6096                 } else if (has_none) {
6097                         report("Nothing to revert");
6098                 } else {
6099                         report("Cannot revert changes to multiple files");
6100                 }
6102         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6103                 char mode[10] = "100644";
6104                 const char *reset_argv[] = {
6105                         "git", "update-index", "--cacheinfo", mode,
6106                                 status->old.rev, status->old.name, NULL
6107                 };
6108                 const char *checkout_argv[] = {
6109                         "git", "checkout", "--", status->old.name, NULL
6110                 };
6112                 if (status->status == 'U') {
6113                         string_format(mode, "%5o", status->old.mode);
6115                         if (status->old.mode == 0 && status->new.mode == 0) {
6116                                 reset_argv[2] = "--force-remove";
6117                                 reset_argv[3] = status->old.name;
6118                                 reset_argv[4] = NULL;
6119                         }
6121                         if (!io_run_fg(reset_argv, opt_cdup))
6122                                 return FALSE;
6123                         if (status->old.mode == 0 && status->new.mode == 0)
6124                                 return TRUE;
6125                 }
6127                 return io_run_fg(checkout_argv, opt_cdup);
6128         }
6130         return FALSE;
6133 static enum request
6134 status_request(struct view *view, enum request request, struct line *line)
6136         struct status *status = line->data;
6138         switch (request) {
6139         case REQ_STATUS_UPDATE:
6140                 if (!status_update(view))
6141                         return REQ_NONE;
6142                 break;
6144         case REQ_STATUS_REVERT:
6145                 if (!status_revert(status, line->type, status_has_none(view, line)))
6146                         return REQ_NONE;
6147                 break;
6149         case REQ_STATUS_MERGE:
6150                 if (!status || status->status != 'U') {
6151                         report("Merging only possible for files with unmerged status ('U').");
6152                         return REQ_NONE;
6153                 }
6154                 open_mergetool(status->new.name);
6155                 break;
6157         case REQ_EDIT:
6158                 if (!status)
6159                         return request;
6160                 if (status->status == 'D') {
6161                         report("File has been deleted.");
6162                         return REQ_NONE;
6163                 }
6165                 open_editor(status->new.name);
6166                 break;
6168         case REQ_VIEW_BLAME:
6169                 if (status)
6170                         opt_ref[0] = 0;
6171                 return request;
6173         case REQ_ENTER:
6174                 /* After returning the status view has been split to
6175                  * show the stage view. No further reloading is
6176                  * necessary. */
6177                 return status_enter(view, line);
6179         case REQ_REFRESH:
6180                 /* Simply reload the view. */
6181                 break;
6183         default:
6184                 return request;
6185         }
6187         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6189         return REQ_NONE;
6192 static void
6193 status_select(struct view *view, struct line *line)
6195         struct status *status = line->data;
6196         char file[SIZEOF_STR] = "all files";
6197         const char *text;
6198         const char *key;
6200         if (status && !string_format(file, "'%s'", status->new.name))
6201                 return;
6203         if (!status && line[1].type == LINE_STAT_NONE)
6204                 line++;
6206         switch (line->type) {
6207         case LINE_STAT_STAGED:
6208                 text = "Press %s to unstage %s for commit";
6209                 break;
6211         case LINE_STAT_UNSTAGED:
6212                 text = "Press %s to stage %s for commit";
6213                 break;
6215         case LINE_STAT_UNTRACKED:
6216                 text = "Press %s to stage %s for addition";
6217                 break;
6219         case LINE_STAT_HEAD:
6220         case LINE_STAT_NONE:
6221                 text = "Nothing to update";
6222                 break;
6224         default:
6225                 die("line type %d not handled in switch", line->type);
6226         }
6228         if (status && status->status == 'U') {
6229                 text = "Press %s to resolve conflict in %s";
6230                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6232         } else {
6233                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6234         }
6236         string_format(view->ref, text, key, file);
6237         if (status)
6238                 string_copy(opt_file, status->new.name);
6241 static bool
6242 status_grep(struct view *view, struct line *line)
6244         struct status *status = line->data;
6246         if (status) {
6247                 const char buf[2] = { status->status, 0 };
6248                 const char *text[] = { status->new.name, buf, NULL };
6250                 return grep_text(view, text);
6251         }
6253         return FALSE;
6256 static struct view_ops status_ops = {
6257         "file",
6258         NULL,
6259         status_open,
6260         NULL,
6261         status_draw,
6262         status_request,
6263         status_grep,
6264         status_select,
6265 };
6268 static bool
6269 stage_diff_write(struct io *io, struct line *line, struct line *end)
6271         while (line < end) {
6272                 if (!io_write(io, line->data, strlen(line->data)) ||
6273                     !io_write(io, "\n", 1))
6274                         return FALSE;
6275                 line++;
6276                 if (line->type == LINE_DIFF_CHUNK ||
6277                     line->type == LINE_DIFF_HEADER)
6278                         break;
6279         }
6281         return TRUE;
6284 static struct line *
6285 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6287         for (; view->line < line; line--)
6288                 if (line->type == type)
6289                         return line;
6291         return NULL;
6294 static bool
6295 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6297         const char *apply_argv[SIZEOF_ARG] = {
6298                 "git", "apply", "--whitespace=nowarn", NULL
6299         };
6300         struct line *diff_hdr;
6301         struct io io;
6302         int argc = 3;
6304         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6305         if (!diff_hdr)
6306                 return FALSE;
6308         if (!revert)
6309                 apply_argv[argc++] = "--cached";
6310         if (revert || stage_line_type == LINE_STAT_STAGED)
6311                 apply_argv[argc++] = "-R";
6312         apply_argv[argc++] = "-";
6313         apply_argv[argc++] = NULL;
6314         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6315                 return FALSE;
6317         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6318             !stage_diff_write(&io, chunk, view->line + view->lines))
6319                 chunk = NULL;
6321         io_done(&io);
6322         io_run_bg(update_index_argv);
6324         return chunk ? TRUE : FALSE;
6327 static bool
6328 stage_update(struct view *view, struct line *line)
6330         struct line *chunk = NULL;
6332         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6333                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6335         if (chunk) {
6336                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6337                         report("Failed to apply chunk");
6338                         return FALSE;
6339                 }
6341         } else if (!stage_status.status) {
6342                 view = VIEW(REQ_VIEW_STATUS);
6344                 for (line = view->line; line < view->line + view->lines; line++)
6345                         if (line->type == stage_line_type)
6346                                 break;
6348                 if (!status_update_files(view, line + 1)) {
6349                         report("Failed to update files");
6350                         return FALSE;
6351                 }
6353         } else if (!status_update_file(&stage_status, stage_line_type)) {
6354                 report("Failed to update file");
6355                 return FALSE;
6356         }
6358         return TRUE;
6361 static bool
6362 stage_revert(struct view *view, struct line *line)
6364         struct line *chunk = NULL;
6366         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6367                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6369         if (chunk) {
6370                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6371                         return FALSE;
6373                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6374                         report("Failed to revert chunk");
6375                         return FALSE;
6376                 }
6377                 return TRUE;
6379         } else {
6380                 return status_revert(stage_status.status ? &stage_status : NULL,
6381                                      stage_line_type, FALSE);
6382         }
6386 static void
6387 stage_next(struct view *view, struct line *line)
6389         int i;
6391         if (!stage_chunks) {
6392                 for (line = view->line; line < view->line + view->lines; line++) {
6393                         if (line->type != LINE_DIFF_CHUNK)
6394                                 continue;
6396                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6397                                 report("Allocation failure");
6398                                 return;
6399                         }
6401                         stage_chunk[stage_chunks++] = line - view->line;
6402                 }
6403         }
6405         for (i = 0; i < stage_chunks; i++) {
6406                 if (stage_chunk[i] > view->lineno) {
6407                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6408                         report("Chunk %d of %d", i + 1, stage_chunks);
6409                         return;
6410                 }
6411         }
6413         report("No next chunk found");
6416 static enum request
6417 stage_request(struct view *view, enum request request, struct line *line)
6419         switch (request) {
6420         case REQ_STATUS_UPDATE:
6421                 if (!stage_update(view, line))
6422                         return REQ_NONE;
6423                 break;
6425         case REQ_STATUS_REVERT:
6426                 if (!stage_revert(view, line))
6427                         return REQ_NONE;
6428                 break;
6430         case REQ_STAGE_NEXT:
6431                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6432                         report("File is untracked; press %s to add",
6433                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6434                         return REQ_NONE;
6435                 }
6436                 stage_next(view, line);
6437                 return REQ_NONE;
6439         case REQ_EDIT:
6440                 if (!stage_status.new.name[0])
6441                         return request;
6442                 if (stage_status.status == 'D') {
6443                         report("File has been deleted.");
6444                         return REQ_NONE;
6445                 }
6447                 open_editor(stage_status.new.name);
6448                 break;
6450         case REQ_REFRESH:
6451                 /* Reload everything ... */
6452                 break;
6454         case REQ_VIEW_BLAME:
6455                 if (stage_status.new.name[0]) {
6456                         string_copy(opt_file, stage_status.new.name);
6457                         opt_ref[0] = 0;
6458                 }
6459                 return request;
6461         case REQ_ENTER:
6462                 return pager_request(view, request, line);
6464         default:
6465                 return request;
6466         }
6468         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6469         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6471         /* Check whether the staged entry still exists, and close the
6472          * stage view if it doesn't. */
6473         if (!status_exists(&stage_status, stage_line_type)) {
6474                 status_restore(VIEW(REQ_VIEW_STATUS));
6475                 return REQ_VIEW_CLOSE;
6476         }
6478         if (stage_line_type == LINE_STAT_UNTRACKED) {
6479                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6480                         report("Cannot display a directory");
6481                         return REQ_NONE;
6482                 }
6484                 if (!prepare_update_file(view, stage_status.new.name)) {
6485                         report("Failed to open file: %s", strerror(errno));
6486                         return REQ_NONE;
6487                 }
6488         }
6489         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6491         return REQ_NONE;
6494 static struct view_ops stage_ops = {
6495         "line",
6496         NULL,
6497         NULL,
6498         pager_read,
6499         pager_draw,
6500         stage_request,
6501         pager_grep,
6502         pager_select,
6503 };
6506 /*
6507  * Revision graph
6508  */
6510 struct commit {
6511         char id[SIZEOF_REV];            /* SHA1 ID. */
6512         char title[128];                /* First line of the commit message. */
6513         const char *author;             /* Author of the commit. */
6514         struct time time;               /* Date from the author ident. */
6515         struct ref_list *refs;          /* Repository references. */
6516         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6517         size_t graph_size;              /* The width of the graph array. */
6518         bool has_parents;               /* Rewritten --parents seen. */
6519 };
6521 /* Size of rev graph with no  "padding" columns */
6522 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6524 struct rev_graph {
6525         struct rev_graph *prev, *next, *parents;
6526         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6527         size_t size;
6528         struct commit *commit;
6529         size_t pos;
6530         unsigned int boundary:1;
6531 };
6533 /* Parents of the commit being visualized. */
6534 static struct rev_graph graph_parents[4];
6536 /* The current stack of revisions on the graph. */
6537 static struct rev_graph graph_stacks[4] = {
6538         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6539         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6540         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6541         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6542 };
6544 static inline bool
6545 graph_parent_is_merge(struct rev_graph *graph)
6547         return graph->parents->size > 1;
6550 static inline void
6551 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6553         struct commit *commit = graph->commit;
6555         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6556                 commit->graph[commit->graph_size++] = symbol;
6559 static void
6560 clear_rev_graph(struct rev_graph *graph)
6562         graph->boundary = 0;
6563         graph->size = graph->pos = 0;
6564         graph->commit = NULL;
6565         memset(graph->parents, 0, sizeof(*graph->parents));
6568 static void
6569 done_rev_graph(struct rev_graph *graph)
6571         if (graph_parent_is_merge(graph) &&
6572             graph->pos < graph->size - 1 &&
6573             graph->next->size == graph->size + graph->parents->size - 1) {
6574                 size_t i = graph->pos + graph->parents->size - 1;
6576                 graph->commit->graph_size = i * 2;
6577                 while (i < graph->next->size - 1) {
6578                         append_to_rev_graph(graph, ' ');
6579                         append_to_rev_graph(graph, '\\');
6580                         i++;
6581                 }
6582         }
6584         clear_rev_graph(graph);
6587 static void
6588 push_rev_graph(struct rev_graph *graph, const char *parent)
6590         int i;
6592         /* "Collapse" duplicate parents lines.
6593          *
6594          * FIXME: This needs to also update update the drawn graph but
6595          * for now it just serves as a method for pruning graph lines. */
6596         for (i = 0; i < graph->size; i++)
6597                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6598                         return;
6600         if (graph->size < SIZEOF_REVITEMS) {
6601                 string_copy_rev(graph->rev[graph->size++], parent);
6602         }
6605 static chtype
6606 get_rev_graph_symbol(struct rev_graph *graph)
6608         chtype symbol;
6610         if (graph->boundary)
6611                 symbol = REVGRAPH_BOUND;
6612         else if (graph->parents->size == 0)
6613                 symbol = REVGRAPH_INIT;
6614         else if (graph_parent_is_merge(graph))
6615                 symbol = REVGRAPH_MERGE;
6616         else if (graph->pos >= graph->size)
6617                 symbol = REVGRAPH_BRANCH;
6618         else
6619                 symbol = REVGRAPH_COMMIT;
6621         return symbol;
6624 static void
6625 draw_rev_graph(struct rev_graph *graph)
6627         struct rev_filler {
6628                 chtype separator, line;
6629         };
6630         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6631         static struct rev_filler fillers[] = {
6632                 { ' ',  '|' },
6633                 { '`',  '.' },
6634                 { '\'', ' ' },
6635                 { '/',  ' ' },
6636         };
6637         chtype symbol = get_rev_graph_symbol(graph);
6638         struct rev_filler *filler;
6639         size_t i;
6641         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6642         filler = &fillers[DEFAULT];
6644         for (i = 0; i < graph->pos; i++) {
6645                 append_to_rev_graph(graph, filler->line);
6646                 if (graph_parent_is_merge(graph->prev) &&
6647                     graph->prev->pos == i)
6648                         filler = &fillers[RSHARP];
6650                 append_to_rev_graph(graph, filler->separator);
6651         }
6653         /* Place the symbol for this revision. */
6654         append_to_rev_graph(graph, symbol);
6656         if (graph->prev->size > graph->size)
6657                 filler = &fillers[RDIAG];
6658         else
6659                 filler = &fillers[DEFAULT];
6661         i++;
6663         for (; i < graph->size; i++) {
6664                 append_to_rev_graph(graph, filler->separator);
6665                 append_to_rev_graph(graph, filler->line);
6666                 if (graph_parent_is_merge(graph->prev) &&
6667                     i < graph->prev->pos + graph->parents->size)
6668                         filler = &fillers[RSHARP];
6669                 if (graph->prev->size > graph->size)
6670                         filler = &fillers[LDIAG];
6671         }
6673         if (graph->prev->size > graph->size) {
6674                 append_to_rev_graph(graph, filler->separator);
6675                 if (filler->line != ' ')
6676                         append_to_rev_graph(graph, filler->line);
6677         }
6680 /* Prepare the next rev graph */
6681 static void
6682 prepare_rev_graph(struct rev_graph *graph)
6684         size_t i;
6686         /* First, traverse all lines of revisions up to the active one. */
6687         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6688                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6689                         break;
6691                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6692         }
6694         /* Interleave the new revision parent(s). */
6695         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6696                 push_rev_graph(graph->next, graph->parents->rev[i]);
6698         /* Lastly, put any remaining revisions. */
6699         for (i = graph->pos + 1; i < graph->size; i++)
6700                 push_rev_graph(graph->next, graph->rev[i]);
6703 static void
6704 update_rev_graph(struct view *view, struct rev_graph *graph)
6706         /* If this is the finalizing update ... */
6707         if (graph->commit)
6708                 prepare_rev_graph(graph);
6710         /* Graph visualization needs a one rev look-ahead,
6711          * so the first update doesn't visualize anything. */
6712         if (!graph->prev->commit)
6713                 return;
6715         if (view->lines > 2)
6716                 view->line[view->lines - 3].dirty = 1;
6717         if (view->lines > 1)
6718                 view->line[view->lines - 2].dirty = 1;
6719         draw_rev_graph(graph->prev);
6720         done_rev_graph(graph->prev->prev);
6724 /*
6725  * Main view backend
6726  */
6728 static const char *main_argv[SIZEOF_ARG] = {
6729         "git", "log", "--no-color", "--pretty=raw", "--parents",
6730                 "--topo-order", "%(diff-args)", "%(rev-args)",
6731                 "--", "%(file-args)", NULL
6732 };
6734 static bool
6735 main_draw(struct view *view, struct line *line, unsigned int lineno)
6737         struct commit *commit = line->data;
6739         if (!commit->author)
6740                 return FALSE;
6742         if (opt_date && draw_date(view, &commit->time))
6743                 return TRUE;
6745         if (opt_author && draw_author(view, commit->author))
6746                 return TRUE;
6748         if (opt_rev_graph && commit->graph_size &&
6749             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6750                 return TRUE;
6752         if (opt_show_refs && commit->refs) {
6753                 size_t i;
6755                 for (i = 0; i < commit->refs->size; i++) {
6756                         struct ref *ref = commit->refs->refs[i];
6757                         enum line_type type;
6759                         if (ref->head)
6760                                 type = LINE_MAIN_HEAD;
6761                         else if (ref->ltag)
6762                                 type = LINE_MAIN_LOCAL_TAG;
6763                         else if (ref->tag)
6764                                 type = LINE_MAIN_TAG;
6765                         else if (ref->tracked)
6766                                 type = LINE_MAIN_TRACKED;
6767                         else if (ref->remote)
6768                                 type = LINE_MAIN_REMOTE;
6769                         else
6770                                 type = LINE_MAIN_REF;
6772                         if (draw_text(view, type, "[", TRUE) ||
6773                             draw_text(view, type, ref->name, TRUE) ||
6774                             draw_text(view, type, "]", TRUE))
6775                                 return TRUE;
6777                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6778                                 return TRUE;
6779                 }
6780         }
6782         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6783         return TRUE;
6786 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6787 static bool
6788 main_read(struct view *view, char *line)
6790         static struct rev_graph *graph = graph_stacks;
6791         enum line_type type;
6792         struct commit *commit;
6794         if (!line) {
6795                 int i;
6797                 if (!view->lines && !view->prev)
6798                         die("No revisions match the given arguments.");
6799                 if (view->lines > 0) {
6800                         commit = view->line[view->lines - 1].data;
6801                         view->line[view->lines - 1].dirty = 1;
6802                         if (!commit->author) {
6803                                 view->lines--;
6804                                 free(commit);
6805                                 graph->commit = NULL;
6806                         }
6807                 }
6808                 update_rev_graph(view, graph);
6810                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6811                         clear_rev_graph(&graph_stacks[i]);
6812                 return TRUE;
6813         }
6815         type = get_line_type(line);
6816         if (type == LINE_COMMIT) {
6817                 commit = calloc(1, sizeof(struct commit));
6818                 if (!commit)
6819                         return FALSE;
6821                 line += STRING_SIZE("commit ");
6822                 if (*line == '-') {
6823                         graph->boundary = 1;
6824                         line++;
6825                 }
6827                 string_copy_rev(commit->id, line);
6828                 commit->refs = get_ref_list(commit->id);
6829                 graph->commit = commit;
6830                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6832                 while ((line = strchr(line, ' '))) {
6833                         line++;
6834                         push_rev_graph(graph->parents, line);
6835                         commit->has_parents = TRUE;
6836                 }
6837                 return TRUE;
6838         }
6840         if (!view->lines)
6841                 return TRUE;
6842         commit = view->line[view->lines - 1].data;
6844         switch (type) {
6845         case LINE_PARENT:
6846                 if (commit->has_parents)
6847                         break;
6848                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6849                 break;
6851         case LINE_AUTHOR:
6852                 parse_author_line(line + STRING_SIZE("author "),
6853                                   &commit->author, &commit->time);
6854                 update_rev_graph(view, graph);
6855                 graph = graph->next;
6856                 break;
6858         default:
6859                 /* Fill in the commit title if it has not already been set. */
6860                 if (commit->title[0])
6861                         break;
6863                 /* Require titles to start with a non-space character at the
6864                  * offset used by git log. */
6865                 if (strncmp(line, "    ", 4))
6866                         break;
6867                 line += 4;
6868                 /* Well, if the title starts with a whitespace character,
6869                  * try to be forgiving.  Otherwise we end up with no title. */
6870                 while (isspace(*line))
6871                         line++;
6872                 if (*line == '\0')
6873                         break;
6874                 /* FIXME: More graceful handling of titles; append "..." to
6875                  * shortened titles, etc. */
6877                 string_expand(commit->title, sizeof(commit->title), line, 1);
6878                 view->line[view->lines - 1].dirty = 1;
6879         }
6881         return TRUE;
6884 static enum request
6885 main_request(struct view *view, enum request request, struct line *line)
6887         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6889         switch (request) {
6890         case REQ_ENTER:
6891                 open_view(view, REQ_VIEW_DIFF, flags);
6892                 break;
6893         case REQ_REFRESH:
6894                 load_refs();
6895                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6896                 break;
6897         default:
6898                 return request;
6899         }
6901         return REQ_NONE;
6904 static bool
6905 grep_refs(struct ref_list *list, regex_t *regex)
6907         regmatch_t pmatch;
6908         size_t i;
6910         if (!opt_show_refs || !list)
6911                 return FALSE;
6913         for (i = 0; i < list->size; i++) {
6914                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6915                         return TRUE;
6916         }
6918         return FALSE;
6921 static bool
6922 main_grep(struct view *view, struct line *line)
6924         struct commit *commit = line->data;
6925         const char *text[] = {
6926                 commit->title,
6927                 opt_author ? commit->author : "",
6928                 mkdate(&commit->time, opt_date),
6929                 NULL
6930         };
6932         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6935 static void
6936 main_select(struct view *view, struct line *line)
6938         struct commit *commit = line->data;
6940         string_copy_rev(view->ref, commit->id);
6941         string_copy_rev(ref_commit, view->ref);
6944 static struct view_ops main_ops = {
6945         "commit",
6946         main_argv,
6947         NULL,
6948         main_read,
6949         main_draw,
6950         main_request,
6951         main_grep,
6952         main_select,
6953 };
6956 /*
6957  * Status management
6958  */
6960 /* Whether or not the curses interface has been initialized. */
6961 static bool cursed = FALSE;
6963 /* Terminal hacks and workarounds. */
6964 static bool use_scroll_redrawwin;
6965 static bool use_scroll_status_wclear;
6967 /* The status window is used for polling keystrokes. */
6968 static WINDOW *status_win;
6970 /* Reading from the prompt? */
6971 static bool input_mode = FALSE;
6973 static bool status_empty = FALSE;
6975 /* Update status and title window. */
6976 static void
6977 report(const char *msg, ...)
6979         struct view *view = display[current_view];
6981         if (input_mode)
6982                 return;
6984         if (!view) {
6985                 char buf[SIZEOF_STR];
6986                 va_list args;
6988                 va_start(args, msg);
6989                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6990                         buf[sizeof(buf) - 1] = 0;
6991                         buf[sizeof(buf) - 2] = '.';
6992                         buf[sizeof(buf) - 3] = '.';
6993                         buf[sizeof(buf) - 4] = '.';
6994                 }
6995                 va_end(args);
6996                 die("%s", buf);
6997         }
6999         if (!status_empty || *msg) {
7000                 va_list args;
7002                 va_start(args, msg);
7004                 wmove(status_win, 0, 0);
7005                 if (view->has_scrolled && use_scroll_status_wclear)
7006                         wclear(status_win);
7007                 if (*msg) {
7008                         vwprintw(status_win, msg, args);
7009                         status_empty = FALSE;
7010                 } else {
7011                         status_empty = TRUE;
7012                 }
7013                 wclrtoeol(status_win);
7014                 wnoutrefresh(status_win);
7016                 va_end(args);
7017         }
7019         update_view_title(view);
7022 static void
7023 init_display(void)
7025         const char *term;
7026         int x, y;
7028         /* Initialize the curses library */
7029         if (isatty(STDIN_FILENO)) {
7030                 cursed = !!initscr();
7031                 opt_tty = stdin;
7032         } else {
7033                 /* Leave stdin and stdout alone when acting as a pager. */
7034                 opt_tty = fopen("/dev/tty", "r+");
7035                 if (!opt_tty)
7036                         die("Failed to open /dev/tty");
7037                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7038         }
7040         if (!cursed)
7041                 die("Failed to initialize curses");
7043         nonl();         /* Disable conversion and detect newlines from input. */
7044         cbreak();       /* Take input chars one at a time, no wait for \n */
7045         noecho();       /* Don't echo input */
7046         leaveok(stdscr, FALSE);
7048         if (has_colors())
7049                 init_colors();
7051         getmaxyx(stdscr, y, x);
7052         status_win = newwin(1, 0, y - 1, 0);
7053         if (!status_win)
7054                 die("Failed to create status window");
7056         /* Enable keyboard mapping */
7057         keypad(status_win, TRUE);
7058         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7060         TABSIZE = opt_tab_size;
7062         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7063         if (term && !strcmp(term, "gnome-terminal")) {
7064                 /* In the gnome-terminal-emulator, the message from
7065                  * scrolling up one line when impossible followed by
7066                  * scrolling down one line causes corruption of the
7067                  * status line. This is fixed by calling wclear. */
7068                 use_scroll_status_wclear = TRUE;
7069                 use_scroll_redrawwin = FALSE;
7071         } else if (term && !strcmp(term, "xrvt-xpm")) {
7072                 /* No problems with full optimizations in xrvt-(unicode)
7073                  * and aterm. */
7074                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7076         } else {
7077                 /* When scrolling in (u)xterm the last line in the
7078                  * scrolling direction will update slowly. */
7079                 use_scroll_redrawwin = TRUE;
7080                 use_scroll_status_wclear = FALSE;
7081         }
7084 static int
7085 get_input(int prompt_position)
7087         struct view *view;
7088         int i, key, cursor_y, cursor_x;
7089         bool loading = FALSE;
7091         if (prompt_position)
7092                 input_mode = TRUE;
7094         while (TRUE) {
7095                 foreach_view (view, i) {
7096                         update_view(view);
7097                         if (view_is_displayed(view) && view->has_scrolled &&
7098                             use_scroll_redrawwin)
7099                                 redrawwin(view->win);
7100                         view->has_scrolled = FALSE;
7101                         if (view->pipe)
7102                                 loading = TRUE;
7103                 }
7105                 /* Update the cursor position. */
7106                 if (prompt_position) {
7107                         getbegyx(status_win, cursor_y, cursor_x);
7108                         cursor_x = prompt_position;
7109                 } else {
7110                         view = display[current_view];
7111                         getbegyx(view->win, cursor_y, cursor_x);
7112                         cursor_x = view->width - 1;
7113                         cursor_y += view->lineno - view->offset;
7114                 }
7115                 setsyx(cursor_y, cursor_x);
7117                 /* Refresh, accept single keystroke of input */
7118                 doupdate();
7119                 nodelay(status_win, loading);
7120                 key = wgetch(status_win);
7122                 /* wgetch() with nodelay() enabled returns ERR when
7123                  * there's no input. */
7124                 if (key == ERR) {
7126                 } else if (key == KEY_RESIZE) {
7127                         int height, width;
7129                         getmaxyx(stdscr, height, width);
7131                         wresize(status_win, 1, width);
7132                         mvwin(status_win, height - 1, 0);
7133                         wnoutrefresh(status_win);
7134                         resize_display();
7135                         redraw_display(TRUE);
7137                 } else {
7138                         input_mode = FALSE;
7139                         return key;
7140                 }
7141         }
7144 static char *
7145 prompt_input(const char *prompt, input_handler handler, void *data)
7147         enum input_status status = INPUT_OK;
7148         static char buf[SIZEOF_STR];
7149         size_t pos = 0;
7151         buf[pos] = 0;
7153         while (status == INPUT_OK || status == INPUT_SKIP) {
7154                 int key;
7156                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7157                 wclrtoeol(status_win);
7159                 key = get_input(pos + 1);
7160                 switch (key) {
7161                 case KEY_RETURN:
7162                 case KEY_ENTER:
7163                 case '\n':
7164                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7165                         break;
7167                 case KEY_BACKSPACE:
7168                         if (pos > 0)
7169                                 buf[--pos] = 0;
7170                         else
7171                                 status = INPUT_CANCEL;
7172                         break;
7174                 case KEY_ESC:
7175                         status = INPUT_CANCEL;
7176                         break;
7178                 default:
7179                         if (pos >= sizeof(buf)) {
7180                                 report("Input string too long");
7181                                 return NULL;
7182                         }
7184                         status = handler(data, buf, key);
7185                         if (status == INPUT_OK)
7186                                 buf[pos++] = (char) key;
7187                 }
7188         }
7190         /* Clear the status window */
7191         status_empty = FALSE;
7192         report("");
7194         if (status == INPUT_CANCEL)
7195                 return NULL;
7197         buf[pos++] = 0;
7199         return buf;
7202 static enum input_status
7203 prompt_yesno_handler(void *data, char *buf, int c)
7205         if (c == 'y' || c == 'Y')
7206                 return INPUT_STOP;
7207         if (c == 'n' || c == 'N')
7208                 return INPUT_CANCEL;
7209         return INPUT_SKIP;
7212 static bool
7213 prompt_yesno(const char *prompt)
7215         char prompt2[SIZEOF_STR];
7217         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7218                 return FALSE;
7220         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7223 static enum input_status
7224 read_prompt_handler(void *data, char *buf, int c)
7226         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7229 static char *
7230 read_prompt(const char *prompt)
7232         return prompt_input(prompt, read_prompt_handler, NULL);
7235 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7237         enum input_status status = INPUT_OK;
7238         int size = 0;
7240         while (items[size].text)
7241                 size++;
7243         while (status == INPUT_OK) {
7244                 const struct menu_item *item = &items[*selected];
7245                 int key;
7246                 int i;
7248                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7249                           prompt, *selected + 1, size);
7250                 if (item->hotkey)
7251                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7252                 wprintw(status_win, "%s", item->text);
7253                 wclrtoeol(status_win);
7255                 key = get_input(COLS - 1);
7256                 switch (key) {
7257                 case KEY_RETURN:
7258                 case KEY_ENTER:
7259                 case '\n':
7260                         status = INPUT_STOP;
7261                         break;
7263                 case KEY_LEFT:
7264                 case KEY_UP:
7265                         *selected = *selected - 1;
7266                         if (*selected < 0)
7267                                 *selected = size - 1;
7268                         break;
7270                 case KEY_RIGHT:
7271                 case KEY_DOWN:
7272                         *selected = (*selected + 1) % size;
7273                         break;
7275                 case KEY_ESC:
7276                         status = INPUT_CANCEL;
7277                         break;
7279                 default:
7280                         for (i = 0; items[i].text; i++)
7281                                 if (items[i].hotkey == key) {
7282                                         *selected = i;
7283                                         status = INPUT_STOP;
7284                                         break;
7285                                 }
7286                 }
7287         }
7289         /* Clear the status window */
7290         status_empty = FALSE;
7291         report("");
7293         return status != INPUT_CANCEL;
7296 /*
7297  * Repository properties
7298  */
7300 static struct ref **refs = NULL;
7301 static size_t refs_size = 0;
7302 static struct ref *refs_head = NULL;
7304 static struct ref_list **ref_lists = NULL;
7305 static size_t ref_lists_size = 0;
7307 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7308 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7309 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7311 static int
7312 compare_refs(const void *ref1_, const void *ref2_)
7314         const struct ref *ref1 = *(const struct ref **)ref1_;
7315         const struct ref *ref2 = *(const struct ref **)ref2_;
7317         if (ref1->tag != ref2->tag)
7318                 return ref2->tag - ref1->tag;
7319         if (ref1->ltag != ref2->ltag)
7320                 return ref2->ltag - ref2->ltag;
7321         if (ref1->head != ref2->head)
7322                 return ref2->head - ref1->head;
7323         if (ref1->tracked != ref2->tracked)
7324                 return ref2->tracked - ref1->tracked;
7325         if (ref1->remote != ref2->remote)
7326                 return ref2->remote - ref1->remote;
7327         return strcmp(ref1->name, ref2->name);
7330 static void
7331 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7333         size_t i;
7335         for (i = 0; i < refs_size; i++)
7336                 if (!visitor(data, refs[i]))
7337                         break;
7340 static struct ref *
7341 get_ref_head()
7343         return refs_head;
7346 static struct ref_list *
7347 get_ref_list(const char *id)
7349         struct ref_list *list;
7350         size_t i;
7352         for (i = 0; i < ref_lists_size; i++)
7353                 if (!strcmp(id, ref_lists[i]->id))
7354                         return ref_lists[i];
7356         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7357                 return NULL;
7358         list = calloc(1, sizeof(*list));
7359         if (!list)
7360                 return NULL;
7362         for (i = 0; i < refs_size; i++) {
7363                 if (!strcmp(id, refs[i]->id) &&
7364                     realloc_refs_list(&list->refs, list->size, 1))
7365                         list->refs[list->size++] = refs[i];
7366         }
7368         if (!list->refs) {
7369                 free(list);
7370                 return NULL;
7371         }
7373         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7374         ref_lists[ref_lists_size++] = list;
7375         return list;
7378 static int
7379 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7381         struct ref *ref = NULL;
7382         bool tag = FALSE;
7383         bool ltag = FALSE;
7384         bool remote = FALSE;
7385         bool tracked = FALSE;
7386         bool head = FALSE;
7387         int from = 0, to = refs_size - 1;
7389         if (!prefixcmp(name, "refs/tags/")) {
7390                 if (!suffixcmp(name, namelen, "^{}")) {
7391                         namelen -= 3;
7392                         name[namelen] = 0;
7393                 } else {
7394                         ltag = TRUE;
7395                 }
7397                 tag = TRUE;
7398                 namelen -= STRING_SIZE("refs/tags/");
7399                 name    += STRING_SIZE("refs/tags/");
7401         } else if (!prefixcmp(name, "refs/remotes/")) {
7402                 remote = TRUE;
7403                 namelen -= STRING_SIZE("refs/remotes/");
7404                 name    += STRING_SIZE("refs/remotes/");
7405                 tracked  = !strcmp(opt_remote, name);
7407         } else if (!prefixcmp(name, "refs/heads/")) {
7408                 namelen -= STRING_SIZE("refs/heads/");
7409                 name    += STRING_SIZE("refs/heads/");
7410                 if (!strncmp(opt_head, name, namelen))
7411                         return OK;
7413         } else if (!strcmp(name, "HEAD")) {
7414                 head     = TRUE;
7415                 if (*opt_head) {
7416                         namelen  = strlen(opt_head);
7417                         name     = opt_head;
7418                 }
7419         }
7421         /* If we are reloading or it's an annotated tag, replace the
7422          * previous SHA1 with the resolved commit id; relies on the fact
7423          * git-ls-remote lists the commit id of an annotated tag right
7424          * before the commit id it points to. */
7425         while (from <= to) {
7426                 size_t pos = (to + from) / 2;
7427                 int cmp = strcmp(name, refs[pos]->name);
7429                 if (!cmp) {
7430                         ref = refs[pos];
7431                         break;
7432                 }
7434                 if (cmp < 0)
7435                         to = pos - 1;
7436                 else
7437                         from = pos + 1;
7438         }
7440         if (!ref) {
7441                 if (!realloc_refs(&refs, refs_size, 1))
7442                         return ERR;
7443                 ref = calloc(1, sizeof(*ref) + namelen);
7444                 if (!ref)
7445                         return ERR;
7446                 memmove(refs + from + 1, refs + from,
7447                         (refs_size - from) * sizeof(*refs));
7448                 refs[from] = ref;
7449                 strncpy(ref->name, name, namelen);
7450                 refs_size++;
7451         }
7453         ref->head = head;
7454         ref->tag = tag;
7455         ref->ltag = ltag;
7456         ref->remote = remote;
7457         ref->tracked = tracked;
7458         string_copy_rev(ref->id, id);
7460         if (head)
7461                 refs_head = ref;
7462         return OK;
7465 static int
7466 load_refs(void)
7468         const char *head_argv[] = {
7469                 "git", "symbolic-ref", "HEAD", NULL
7470         };
7471         static const char *ls_remote_argv[SIZEOF_ARG] = {
7472                 "git", "ls-remote", opt_git_dir, NULL
7473         };
7474         static bool init = FALSE;
7475         size_t i;
7477         if (!init) {
7478                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7479                         die("TIG_LS_REMOTE contains too many arguments");
7480                 init = TRUE;
7481         }
7483         if (!*opt_git_dir)
7484                 return OK;
7486         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7487             !prefixcmp(opt_head, "refs/heads/")) {
7488                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7490                 memmove(opt_head, offset, strlen(offset) + 1);
7491         }
7493         refs_head = NULL;
7494         for (i = 0; i < refs_size; i++)
7495                 refs[i]->id[0] = 0;
7497         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7498                 return ERR;
7500         /* Update the ref lists to reflect changes. */
7501         for (i = 0; i < ref_lists_size; i++) {
7502                 struct ref_list *list = ref_lists[i];
7503                 size_t old, new;
7505                 for (old = new = 0; old < list->size; old++)
7506                         if (!strcmp(list->id, list->refs[old]->id))
7507                                 list->refs[new++] = list->refs[old];
7508                 list->size = new;
7509         }
7511         return OK;
7514 static void
7515 set_remote_branch(const char *name, const char *value, size_t valuelen)
7517         if (!strcmp(name, ".remote")) {
7518                 string_ncopy(opt_remote, value, valuelen);
7520         } else if (*opt_remote && !strcmp(name, ".merge")) {
7521                 size_t from = strlen(opt_remote);
7523                 if (!prefixcmp(value, "refs/heads/"))
7524                         value += STRING_SIZE("refs/heads/");
7526                 if (!string_format_from(opt_remote, &from, "/%s", value))
7527                         opt_remote[0] = 0;
7528         }
7531 static void
7532 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7534         const char *argv[SIZEOF_ARG] = { name, "=" };
7535         int argc = 1 + (cmd == option_set_command);
7536         int error = ERR;
7538         if (!argv_from_string(argv, &argc, value))
7539                 config_msg = "Too many option arguments";
7540         else
7541                 error = cmd(argc, argv);
7543         if (error == ERR)
7544                 warn("Option 'tig.%s': %s", name, config_msg);
7547 static bool
7548 set_environment_variable(const char *name, const char *value)
7550         size_t len = strlen(name) + 1 + strlen(value) + 1;
7551         char *env = malloc(len);
7553         if (env &&
7554             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7555             putenv(env) == 0)
7556                 return TRUE;
7557         free(env);
7558         return FALSE;
7561 static void
7562 set_work_tree(const char *value)
7564         char cwd[SIZEOF_STR];
7566         if (!getcwd(cwd, sizeof(cwd)))
7567                 die("Failed to get cwd path: %s", strerror(errno));
7568         if (chdir(opt_git_dir) < 0)
7569                 die("Failed to chdir(%s): %s", strerror(errno));
7570         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7571                 die("Failed to get git path: %s", strerror(errno));
7572         if (chdir(cwd) < 0)
7573                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7574         if (chdir(value) < 0)
7575                 die("Failed to chdir(%s): %s", value, strerror(errno));
7576         if (!getcwd(cwd, sizeof(cwd)))
7577                 die("Failed to get cwd path: %s", strerror(errno));
7578         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7579                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7580         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7581                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7582         opt_is_inside_work_tree = TRUE;
7585 static int
7586 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7588         if (!strcmp(name, "i18n.commitencoding"))
7589                 string_ncopy(opt_encoding, value, valuelen);
7591         else if (!strcmp(name, "core.editor"))
7592                 string_ncopy(opt_editor, value, valuelen);
7594         else if (!strcmp(name, "core.worktree"))
7595                 set_work_tree(value);
7597         else if (!prefixcmp(name, "tig.color."))
7598                 set_repo_config_option(name + 10, value, option_color_command);
7600         else if (!prefixcmp(name, "tig.bind."))
7601                 set_repo_config_option(name + 9, value, option_bind_command);
7603         else if (!prefixcmp(name, "tig."))
7604                 set_repo_config_option(name + 4, value, option_set_command);
7606         else if (*opt_head && !prefixcmp(name, "branch.") &&
7607                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7608                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7610         return OK;
7613 static int
7614 load_git_config(void)
7616         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7618         return io_run_load(config_list_argv, "=", read_repo_config_option);
7621 static int
7622 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7624         if (!opt_git_dir[0]) {
7625                 string_ncopy(opt_git_dir, name, namelen);
7627         } else if (opt_is_inside_work_tree == -1) {
7628                 /* This can be 3 different values depending on the
7629                  * version of git being used. If git-rev-parse does not
7630                  * understand --is-inside-work-tree it will simply echo
7631                  * the option else either "true" or "false" is printed.
7632                  * Default to true for the unknown case. */
7633                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7635         } else if (*name == '.') {
7636                 string_ncopy(opt_cdup, name, namelen);
7638         } else {
7639                 string_ncopy(opt_prefix, name, namelen);
7640         }
7642         return OK;
7645 static int
7646 load_repo_info(void)
7648         const char *rev_parse_argv[] = {
7649                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7650                         "--show-cdup", "--show-prefix", NULL
7651         };
7653         return io_run_load(rev_parse_argv, "=", read_repo_info);
7657 /*
7658  * Main
7659  */
7661 static const char usage[] =
7662 "tig " TIG_VERSION " (" __DATE__ ")\n"
7663 "\n"
7664 "Usage: tig        [options] [revs] [--] [paths]\n"
7665 "   or: tig show   [options] [revs] [--] [paths]\n"
7666 "   or: tig blame  [rev] path\n"
7667 "   or: tig status\n"
7668 "   or: tig <      [git command output]\n"
7669 "\n"
7670 "Options:\n"
7671 "  -v, --version   Show version and exit\n"
7672 "  -h, --help      Show help message and exit";
7674 static void __NORETURN
7675 quit(int sig)
7677         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7678         if (cursed)
7679                 endwin();
7680         exit(0);
7683 static void __NORETURN
7684 die(const char *err, ...)
7686         va_list args;
7688         endwin();
7690         va_start(args, err);
7691         fputs("tig: ", stderr);
7692         vfprintf(stderr, err, args);
7693         fputs("\n", stderr);
7694         va_end(args);
7696         exit(1);
7699 static void
7700 warn(const char *msg, ...)
7702         va_list args;
7704         va_start(args, msg);
7705         fputs("tig warning: ", stderr);
7706         vfprintf(stderr, msg, args);
7707         fputs("\n", stderr);
7708         va_end(args);
7711 static const char ***filter_args;
7713 static int
7714 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7716         return argv_append(filter_args, name) ? OK : ERR;
7719 static void
7720 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7722         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7723         const char **all_argv = NULL;
7725         filter_args = args;
7726         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7727             !argv_append_array(&all_argv, argv) ||
7728             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7729                 die("Failed to split arguments");
7730         argv_free(all_argv);
7731         free(all_argv);
7734 static void
7735 filter_options(const char *argv[])
7737         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7738         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7739         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7742 static enum request
7743 parse_options(int argc, const char *argv[])
7745         enum request request = REQ_VIEW_MAIN;
7746         const char *subcommand;
7747         bool seen_dashdash = FALSE;
7748         const char **filter_argv = NULL;
7749         int i;
7751         if (!isatty(STDIN_FILENO)) {
7752                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7753                 return REQ_VIEW_PAGER;
7754         }
7756         if (argc <= 1)
7757                 return REQ_VIEW_MAIN;
7759         subcommand = argv[1];
7760         if (!strcmp(subcommand, "status")) {
7761                 if (argc > 2)
7762                         warn("ignoring arguments after `%s'", subcommand);
7763                 return REQ_VIEW_STATUS;
7765         } else if (!strcmp(subcommand, "blame")) {
7766                 if (argc <= 2 || argc > 4)
7767                         die("invalid number of options to blame\n\n%s", usage);
7769                 i = 2;
7770                 if (argc == 4) {
7771                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7772                         i++;
7773                 }
7775                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7776                 return REQ_VIEW_BLAME;
7778         } else if (!strcmp(subcommand, "show")) {
7779                 request = REQ_VIEW_DIFF;
7781         } else {
7782                 subcommand = NULL;
7783         }
7785         for (i = 1 + !!subcommand; i < argc; i++) {
7786                 const char *opt = argv[i];
7788                 if (seen_dashdash) {
7789                         argv_append(&opt_file_args, opt);
7790                         continue;
7792                 } else if (!strcmp(opt, "--")) {
7793                         seen_dashdash = TRUE;
7794                         continue;
7796                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7797                         printf("tig version %s\n", TIG_VERSION);
7798                         quit(0);
7800                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7801                         printf("%s\n", usage);
7802                         quit(0);
7804                 } else if (!strcmp(opt, "--all")) {
7805                         argv_append(&opt_rev_args, opt);
7806                         continue;
7807                 }
7809                 if (!argv_append(&filter_argv, opt))
7810                         die("command too long");
7811         }
7813         if (filter_argv)
7814                 filter_options(filter_argv);
7816         return request;
7819 int
7820 main(int argc, const char *argv[])
7822         const char *codeset = "UTF-8";
7823         enum request request = parse_options(argc, argv);
7824         struct view *view;
7825         size_t i;
7827         signal(SIGINT, quit);
7828         signal(SIGPIPE, SIG_IGN);
7830         if (setlocale(LC_ALL, "")) {
7831                 codeset = nl_langinfo(CODESET);
7832         }
7834         if (load_repo_info() == ERR)
7835                 die("Failed to load repo info.");
7837         if (load_options() == ERR)
7838                 die("Failed to load user config.");
7840         if (load_git_config() == ERR)
7841                 die("Failed to load repo config.");
7843         /* Require a git repository unless when running in pager mode. */
7844         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7845                 die("Not a git repository");
7847         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7848                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7849                 if (opt_iconv_in == ICONV_NONE)
7850                         die("Failed to initialize character set conversion");
7851         }
7853         if (codeset && strcmp(codeset, "UTF-8")) {
7854                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7855                 if (opt_iconv_out == ICONV_NONE)
7856                         die("Failed to initialize character set conversion");
7857         }
7859         if (load_refs() == ERR)
7860                 die("Failed to load refs.");
7862         foreach_view (view, i) {
7863                 if (getenv(view->cmd_env))
7864                         warn("Use of the %s environment variable is deprecated,"
7865                              " use options or TIG_DIFF_ARGS instead",
7866                              view->cmd_env);
7867                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7868                         die("Too many arguments in the `%s` environment variable",
7869                             view->cmd_env);
7870         }
7872         init_display();
7874         while (view_driver(display[current_view], request)) {
7875                 int key = get_input(0);
7877                 view = display[current_view];
7878                 request = get_keybinding(view->keymap, key);
7880                 /* Some low-level request handling. This keeps access to
7881                  * status_win restricted. */
7882                 switch (request) {
7883                 case REQ_NONE:
7884                         report("Unknown key, press %s for help",
7885                                get_key(view->keymap, REQ_VIEW_HELP));
7886                         break;
7887                 case REQ_PROMPT:
7888                 {
7889                         char *cmd = read_prompt(":");
7891                         if (cmd && isdigit(*cmd)) {
7892                                 int lineno = view->lineno + 1;
7894                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7895                                         select_view_line(view, lineno - 1);
7896                                         report("");
7897                                 } else {
7898                                         report("Unable to parse '%s' as a line number", cmd);
7899                                 }
7901                         } else if (cmd) {
7902                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7903                                 const char *argv[SIZEOF_ARG] = { "git" };
7904                                 int argc = 1;
7906                                 /* When running random commands, initially show the
7907                                  * command in the title. However, it maybe later be
7908                                  * overwritten if a commit line is selected. */
7909                                 string_ncopy(next->ref, cmd, strlen(cmd));
7911                                 if (!argv_from_string(argv, &argc, cmd)) {
7912                                         report("Too many arguments");
7913                                 } else if (!prepare_update(next, argv, NULL)) {
7914                                         report("Failed to format command");
7915                                 } else {
7916                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7917                                 }
7918                         }
7920                         request = REQ_NONE;
7921                         break;
7922                 }
7923                 case REQ_SEARCH:
7924                 case REQ_SEARCH_BACK:
7925                 {
7926                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7927                         char *search = read_prompt(prompt);
7929                         if (search)
7930                                 string_ncopy(opt_search, search, strlen(search));
7931                         else if (*opt_search)
7932                                 request = request == REQ_SEARCH ?
7933                                         REQ_FIND_NEXT :
7934                                         REQ_FIND_PREV;
7935                         else
7936                                 request = REQ_NONE;
7937                         break;
7938                 }
7939                 default:
7940                         break;
7941                 }
7942         }
7944         quit(0);
7946         return 0;