Code

dd155e35e23a6b11fd3aedc9e5883085372ebdb7
[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 /*
3981  * Pager backend
3982  */
3984 static bool
3985 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3987         char text[SIZEOF_STR];
3989         if (opt_line_number && draw_lineno(view, lineno))
3990                 return TRUE;
3992         string_expand(text, sizeof(text), line->data, opt_tab_size);
3993         draw_text(view, line->type, text, TRUE);
3994         return TRUE;
3997 static bool
3998 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4000         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4001         char ref[SIZEOF_STR];
4003         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4004                 return TRUE;
4006         /* This is the only fatal call, since it can "corrupt" the buffer. */
4007         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4008                 return FALSE;
4010         return TRUE;
4013 static void
4014 add_pager_refs(struct view *view, struct line *line)
4016         char buf[SIZEOF_STR];
4017         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4018         struct ref_list *list;
4019         size_t bufpos = 0, i;
4020         const char *sep = "Refs: ";
4021         bool is_tag = FALSE;
4023         assert(line->type == LINE_COMMIT);
4025         list = get_ref_list(commit_id);
4026         if (!list) {
4027                 if (view->type == VIEW_DIFF)
4028                         goto try_add_describe_ref;
4029                 return;
4030         }
4032         for (i = 0; i < list->size; i++) {
4033                 struct ref *ref = list->refs[i];
4034                 const char *fmt = ref->tag    ? "%s[%s]" :
4035                                   ref->remote ? "%s<%s>" : "%s%s";
4037                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4038                         return;
4039                 sep = ", ";
4040                 if (ref->tag)
4041                         is_tag = TRUE;
4042         }
4044         if (!is_tag && view->type == VIEW_DIFF) {
4045 try_add_describe_ref:
4046                 /* Add <tag>-g<commit_id> "fake" reference. */
4047                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4048                         return;
4049         }
4051         if (bufpos == 0)
4052                 return;
4054         add_line_text(view, buf, LINE_PP_REFS);
4057 static bool
4058 pager_read(struct view *view, char *data)
4060         struct line *line;
4062         if (!data)
4063                 return TRUE;
4065         line = add_line_text(view, data, get_line_type(data));
4066         if (!line)
4067                 return FALSE;
4069         if (line->type == LINE_COMMIT &&
4070             (view->type == VIEW_DIFF ||
4071              view->type == VIEW_LOG))
4072                 add_pager_refs(view, line);
4074         return TRUE;
4077 static enum request
4078 pager_request(struct view *view, enum request request, struct line *line)
4080         int split = 0;
4082         if (request != REQ_ENTER)
4083                 return request;
4085         if (line->type == LINE_COMMIT &&
4086            (view->type == VIEW_LOG ||
4087             view->type == VIEW_PAGER)) {
4088                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4089                 split = 1;
4090         }
4092         /* Always scroll the view even if it was split. That way
4093          * you can use Enter to scroll through the log view and
4094          * split open each commit diff. */
4095         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4097         /* FIXME: A minor workaround. Scrolling the view will call report("")
4098          * but if we are scrolling a non-current view this won't properly
4099          * update the view title. */
4100         if (split)
4101                 update_view_title(view);
4103         return REQ_NONE;
4106 static bool
4107 pager_grep(struct view *view, struct line *line)
4109         const char *text[] = { line->data, NULL };
4111         return grep_text(view, text);
4114 static void
4115 pager_select(struct view *view, struct line *line)
4117         if (line->type == LINE_COMMIT) {
4118                 char *text = (char *)line->data + STRING_SIZE("commit ");
4120                 if (view->type != VIEW_PAGER)
4121                         string_copy_rev(view->ref, text);
4122                 string_copy_rev(ref_commit, text);
4123         }
4126 static struct view_ops pager_ops = {
4127         "line",
4128         NULL,
4129         NULL,
4130         pager_read,
4131         pager_draw,
4132         pager_request,
4133         pager_grep,
4134         pager_select,
4135 };
4137 static const char *log_argv[SIZEOF_ARG] = {
4138         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4139 };
4141 static enum request
4142 log_request(struct view *view, enum request request, struct line *line)
4144         switch (request) {
4145         case REQ_REFRESH:
4146                 load_refs();
4147                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4148                 return REQ_NONE;
4149         default:
4150                 return pager_request(view, request, line);
4151         }
4154 static struct view_ops log_ops = {
4155         "line",
4156         log_argv,
4157         NULL,
4158         pager_read,
4159         pager_draw,
4160         log_request,
4161         pager_grep,
4162         pager_select,
4163 };
4165 static const char *diff_argv[SIZEOF_ARG] = {
4166         "git", "show", "--pretty=fuller", "--no-color", "--root",
4167                 "--patch-with-stat", "--find-copies-harder", "-C",
4168                 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4169 };
4171 static struct view_ops diff_ops = {
4172         "line",
4173         diff_argv,
4174         NULL,
4175         pager_read,
4176         pager_draw,
4177         pager_request,
4178         pager_grep,
4179         pager_select,
4180 };
4182 /*
4183  * Help backend
4184  */
4186 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4188 static bool
4189 help_open_keymap_title(struct view *view, enum keymap keymap)
4191         struct line *line;
4193         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4194                                help_keymap_hidden[keymap] ? '+' : '-',
4195                                enum_name(keymap_table[keymap]));
4196         if (line)
4197                 line->other = keymap;
4199         return help_keymap_hidden[keymap];
4202 static void
4203 help_open_keymap(struct view *view, enum keymap keymap)
4205         const char *group = NULL;
4206         char buf[SIZEOF_STR];
4207         size_t bufpos;
4208         bool add_title = TRUE;
4209         int i;
4211         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4212                 const char *key = NULL;
4214                 if (req_info[i].request == REQ_NONE)
4215                         continue;
4217                 if (!req_info[i].request) {
4218                         group = req_info[i].help;
4219                         continue;
4220                 }
4222                 key = get_keys(keymap, req_info[i].request, TRUE);
4223                 if (!key || !*key)
4224                         continue;
4226                 if (add_title && help_open_keymap_title(view, keymap))
4227                         return;
4228                 add_title = FALSE;
4230                 if (group) {
4231                         add_line_text(view, group, LINE_HELP_GROUP);
4232                         group = NULL;
4233                 }
4235                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4236                                 enum_name(req_info[i]), req_info[i].help);
4237         }
4239         group = "External commands:";
4241         for (i = 0; i < run_requests; i++) {
4242                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4243                 const char *key;
4244                 int argc;
4246                 if (!req || req->keymap != keymap)
4247                         continue;
4249                 key = get_key_name(req->key);
4250                 if (!*key)
4251                         key = "(no key defined)";
4253                 if (add_title && help_open_keymap_title(view, keymap))
4254                         return;
4255                 if (group) {
4256                         add_line_text(view, group, LINE_HELP_GROUP);
4257                         group = NULL;
4258                 }
4260                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4261                         if (!string_format_from(buf, &bufpos, "%s%s",
4262                                                 argc ? " " : "", req->argv[argc]))
4263                                 return;
4265                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4266         }
4269 static bool
4270 help_open(struct view *view)
4272         enum keymap keymap;
4274         reset_view(view);
4275         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4276         add_line_text(view, "", LINE_DEFAULT);
4278         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4279                 help_open_keymap(view, keymap);
4281         return TRUE;
4284 static enum request
4285 help_request(struct view *view, enum request request, struct line *line)
4287         switch (request) {
4288         case REQ_ENTER:
4289                 if (line->type == LINE_HELP_KEYMAP) {
4290                         help_keymap_hidden[line->other] =
4291                                 !help_keymap_hidden[line->other];
4292                         view->p_restore = TRUE;
4293                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4294                 }
4296                 return REQ_NONE;
4297         default:
4298                 return pager_request(view, request, line);
4299         }
4302 static struct view_ops help_ops = {
4303         "line",
4304         NULL,
4305         help_open,
4306         NULL,
4307         pager_draw,
4308         help_request,
4309         pager_grep,
4310         pager_select,
4311 };
4314 /*
4315  * Tree backend
4316  */
4318 struct tree_stack_entry {
4319         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4320         unsigned long lineno;           /* Line number to restore */
4321         char *name;                     /* Position of name in opt_path */
4322 };
4324 /* The top of the path stack. */
4325 static struct tree_stack_entry *tree_stack = NULL;
4326 unsigned long tree_lineno = 0;
4328 static void
4329 pop_tree_stack_entry(void)
4331         struct tree_stack_entry *entry = tree_stack;
4333         tree_lineno = entry->lineno;
4334         entry->name[0] = 0;
4335         tree_stack = entry->prev;
4336         free(entry);
4339 static void
4340 push_tree_stack_entry(const char *name, unsigned long lineno)
4342         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4343         size_t pathlen = strlen(opt_path);
4345         if (!entry)
4346                 return;
4348         entry->prev = tree_stack;
4349         entry->name = opt_path + pathlen;
4350         tree_stack = entry;
4352         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4353                 pop_tree_stack_entry();
4354                 return;
4355         }
4357         /* Move the current line to the first tree entry. */
4358         tree_lineno = 1;
4359         entry->lineno = lineno;
4362 /* Parse output from git-ls-tree(1):
4363  *
4364  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4365  */
4367 #define SIZEOF_TREE_ATTR \
4368         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4370 #define SIZEOF_TREE_MODE \
4371         STRING_SIZE("100644 ")
4373 #define TREE_ID_OFFSET \
4374         STRING_SIZE("100644 blob ")
4376 struct tree_entry {
4377         char id[SIZEOF_REV];
4378         mode_t mode;
4379         struct time time;               /* Date from the author ident. */
4380         const char *author;             /* Author of the commit. */
4381         char name[1];
4382 };
4384 static const char *
4385 tree_path(const struct line *line)
4387         return ((struct tree_entry *) line->data)->name;
4390 static int
4391 tree_compare_entry(const struct line *line1, const struct line *line2)
4393         if (line1->type != line2->type)
4394                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4395         return strcmp(tree_path(line1), tree_path(line2));
4398 static const enum sort_field tree_sort_fields[] = {
4399         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4400 };
4401 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4403 static int
4404 tree_compare(const void *l1, const void *l2)
4406         const struct line *line1 = (const struct line *) l1;
4407         const struct line *line2 = (const struct line *) l2;
4408         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4409         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4411         if (line1->type == LINE_TREE_HEAD)
4412                 return -1;
4413         if (line2->type == LINE_TREE_HEAD)
4414                 return 1;
4416         switch (get_sort_field(tree_sort_state)) {
4417         case ORDERBY_DATE:
4418                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4420         case ORDERBY_AUTHOR:
4421                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4423         case ORDERBY_NAME:
4424         default:
4425                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4426         }
4430 static struct line *
4431 tree_entry(struct view *view, enum line_type type, const char *path,
4432            const char *mode, const char *id)
4434         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4435         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4437         if (!entry || !line) {
4438                 free(entry);
4439                 return NULL;
4440         }
4442         strncpy(entry->name, path, strlen(path));
4443         if (mode)
4444                 entry->mode = strtoul(mode, NULL, 8);
4445         if (id)
4446                 string_copy_rev(entry->id, id);
4448         return line;
4451 static bool
4452 tree_read_date(struct view *view, char *text, bool *read_date)
4454         static const char *author_name;
4455         static struct time author_time;
4457         if (!text && *read_date) {
4458                 *read_date = FALSE;
4459                 return TRUE;
4461         } else if (!text) {
4462                 char *path = *opt_path ? opt_path : ".";
4463                 /* Find next entry to process */
4464                 const char *log_file[] = {
4465                         "git", "log", "--no-color", "--pretty=raw",
4466                                 "--cc", "--raw", view->id, "--", path, NULL
4467                 };
4469                 if (!view->lines) {
4470                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4471                         report("Tree is empty");
4472                         return TRUE;
4473                 }
4475                 if (!start_update(view, log_file, opt_cdup)) {
4476                         report("Failed to load tree data");
4477                         return TRUE;
4478                 }
4480                 *read_date = TRUE;
4481                 return FALSE;
4483         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4484                 parse_author_line(text + STRING_SIZE("author "),
4485                                   &author_name, &author_time);
4487         } else if (*text == ':') {
4488                 char *pos;
4489                 size_t annotated = 1;
4490                 size_t i;
4492                 pos = strchr(text, '\t');
4493                 if (!pos)
4494                         return TRUE;
4495                 text = pos + 1;
4496                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4497                         text += strlen(opt_path);
4498                 pos = strchr(text, '/');
4499                 if (pos)
4500                         *pos = 0;
4502                 for (i = 1; i < view->lines; i++) {
4503                         struct line *line = &view->line[i];
4504                         struct tree_entry *entry = line->data;
4506                         annotated += !!entry->author;
4507                         if (entry->author || strcmp(entry->name, text))
4508                                 continue;
4510                         entry->author = author_name;
4511                         entry->time = author_time;
4512                         line->dirty = 1;
4513                         break;
4514                 }
4516                 if (annotated == view->lines)
4517                         io_kill(view->pipe);
4518         }
4519         return TRUE;
4522 static bool
4523 tree_read(struct view *view, char *text)
4525         static bool read_date = FALSE;
4526         struct tree_entry *data;
4527         struct line *entry, *line;
4528         enum line_type type;
4529         size_t textlen = text ? strlen(text) : 0;
4530         char *path = text + SIZEOF_TREE_ATTR;
4532         if (read_date || !text)
4533                 return tree_read_date(view, text, &read_date);
4535         if (textlen <= SIZEOF_TREE_ATTR)
4536                 return FALSE;
4537         if (view->lines == 0 &&
4538             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4539                 return FALSE;
4541         /* Strip the path part ... */
4542         if (*opt_path) {
4543                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4544                 size_t striplen = strlen(opt_path);
4546                 if (pathlen > striplen)
4547                         memmove(path, path + striplen,
4548                                 pathlen - striplen + 1);
4550                 /* Insert "link" to parent directory. */
4551                 if (view->lines == 1 &&
4552                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4553                         return FALSE;
4554         }
4556         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4557         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4558         if (!entry)
4559                 return FALSE;
4560         data = entry->data;
4562         /* Skip "Directory ..." and ".." line. */
4563         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4564                 if (tree_compare_entry(line, entry) <= 0)
4565                         continue;
4567                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4569                 line->data = data;
4570                 line->type = type;
4571                 for (; line <= entry; line++)
4572                         line->dirty = line->cleareol = 1;
4573                 return TRUE;
4574         }
4576         if (tree_lineno > view->lineno) {
4577                 view->lineno = tree_lineno;
4578                 tree_lineno = 0;
4579         }
4581         return TRUE;
4584 static bool
4585 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4587         struct tree_entry *entry = line->data;
4589         if (line->type == LINE_TREE_HEAD) {
4590                 if (draw_text(view, line->type, "Directory path /", TRUE))
4591                         return TRUE;
4592         } else {
4593                 if (draw_mode(view, entry->mode))
4594                         return TRUE;
4596                 if (opt_author && draw_author(view, entry->author))
4597                         return TRUE;
4599                 if (opt_date && draw_date(view, &entry->time))
4600                         return TRUE;
4601         }
4602         if (draw_text(view, line->type, entry->name, TRUE))
4603                 return TRUE;
4604         return TRUE;
4607 static void
4608 open_blob_editor(const char *id)
4610         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4611         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4612         int fd = mkstemp(file);
4614         if (fd == -1)
4615                 report("Failed to create temporary file");
4616         else if (!io_run_append(blob_argv, fd))
4617                 report("Failed to save blob data to file");
4618         else
4619                 open_editor(file);
4620         if (fd != -1)
4621                 unlink(file);
4624 static enum request
4625 tree_request(struct view *view, enum request request, struct line *line)
4627         enum open_flags flags;
4628         struct tree_entry *entry = line->data;
4630         switch (request) {
4631         case REQ_VIEW_BLAME:
4632                 if (line->type != LINE_TREE_FILE) {
4633                         report("Blame only supported for files");
4634                         return REQ_NONE;
4635                 }
4637                 string_copy(opt_ref, view->vid);
4638                 return request;
4640         case REQ_EDIT:
4641                 if (line->type != LINE_TREE_FILE) {
4642                         report("Edit only supported for files");
4643                 } else if (!is_head_commit(view->vid)) {
4644                         open_blob_editor(entry->id);
4645                 } else {
4646                         open_editor(opt_file);
4647                 }
4648                 return REQ_NONE;
4650         case REQ_TOGGLE_SORT_FIELD:
4651         case REQ_TOGGLE_SORT_ORDER:
4652                 sort_view(view, request, &tree_sort_state, tree_compare);
4653                 return REQ_NONE;
4655         case REQ_PARENT:
4656                 if (!*opt_path) {
4657                         /* quit view if at top of tree */
4658                         return REQ_VIEW_CLOSE;
4659                 }
4660                 /* fake 'cd  ..' */
4661                 line = &view->line[1];
4662                 break;
4664         case REQ_ENTER:
4665                 break;
4667         default:
4668                 return request;
4669         }
4671         /* Cleanup the stack if the tree view is at a different tree. */
4672         while (!*opt_path && tree_stack)
4673                 pop_tree_stack_entry();
4675         switch (line->type) {
4676         case LINE_TREE_DIR:
4677                 /* Depending on whether it is a subdirectory or parent link
4678                  * mangle the path buffer. */
4679                 if (line == &view->line[1] && *opt_path) {
4680                         pop_tree_stack_entry();
4682                 } else {
4683                         const char *basename = tree_path(line);
4685                         push_tree_stack_entry(basename, view->lineno);
4686                 }
4688                 /* Trees and subtrees share the same ID, so they are not not
4689                  * unique like blobs. */
4690                 flags = OPEN_RELOAD;
4691                 request = REQ_VIEW_TREE;
4692                 break;
4694         case LINE_TREE_FILE:
4695                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4696                 request = REQ_VIEW_BLOB;
4697                 break;
4699         default:
4700                 return REQ_NONE;
4701         }
4703         open_view(view, request, flags);
4704         if (request == REQ_VIEW_TREE)
4705                 view->lineno = tree_lineno;
4707         return REQ_NONE;
4710 static bool
4711 tree_grep(struct view *view, struct line *line)
4713         struct tree_entry *entry = line->data;
4714         const char *text[] = {
4715                 entry->name,
4716                 opt_author ? entry->author : "",
4717                 mkdate(&entry->time, opt_date),
4718                 NULL
4719         };
4721         return grep_text(view, text);
4724 static void
4725 tree_select(struct view *view, struct line *line)
4727         struct tree_entry *entry = line->data;
4729         if (line->type == LINE_TREE_FILE) {
4730                 string_copy_rev(ref_blob, entry->id);
4731                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4733         } else if (line->type != LINE_TREE_DIR) {
4734                 return;
4735         }
4737         string_copy_rev(view->ref, entry->id);
4740 static bool
4741 tree_prepare(struct view *view)
4743         if (view->lines == 0 && opt_prefix[0]) {
4744                 char *pos = opt_prefix;
4746                 while (pos && *pos) {
4747                         char *end = strchr(pos, '/');
4749                         if (end)
4750                                 *end = 0;
4751                         push_tree_stack_entry(pos, 0);
4752                         pos = end;
4753                         if (end) {
4754                                 *end = '/';
4755                                 pos++;
4756                         }
4757                 }
4759         } else if (strcmp(view->vid, view->id)) {
4760                 opt_path[0] = 0;
4761         }
4763         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4766 static const char *tree_argv[SIZEOF_ARG] = {
4767         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4768 };
4770 static struct view_ops tree_ops = {
4771         "file",
4772         tree_argv,
4773         NULL,
4774         tree_read,
4775         tree_draw,
4776         tree_request,
4777         tree_grep,
4778         tree_select,
4779         tree_prepare,
4780 };
4782 static bool
4783 blob_read(struct view *view, char *line)
4785         if (!line)
4786                 return TRUE;
4787         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4790 static enum request
4791 blob_request(struct view *view, enum request request, struct line *line)
4793         switch (request) {
4794         case REQ_EDIT:
4795                 open_blob_editor(view->vid);
4796                 return REQ_NONE;
4797         default:
4798                 return pager_request(view, request, line);
4799         }
4802 static const char *blob_argv[SIZEOF_ARG] = {
4803         "git", "cat-file", "blob", "%(blob)", NULL
4804 };
4806 static struct view_ops blob_ops = {
4807         "line",
4808         blob_argv,
4809         NULL,
4810         blob_read,
4811         pager_draw,
4812         blob_request,
4813         pager_grep,
4814         pager_select,
4815 };
4817 /*
4818  * Blame backend
4819  *
4820  * Loading the blame view is a two phase job:
4821  *
4822  *  1. File content is read either using opt_file from the
4823  *     filesystem or using git-cat-file.
4824  *  2. Then blame information is incrementally added by
4825  *     reading output from git-blame.
4826  */
4828 struct blame_commit {
4829         char id[SIZEOF_REV];            /* SHA1 ID. */
4830         char title[128];                /* First line of the commit message. */
4831         const char *author;             /* Author of the commit. */
4832         struct time time;               /* Date from the author ident. */
4833         char filename[128];             /* Name of file. */
4834         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4835         char parent_filename[128];      /* Parent/previous name of file. */
4836 };
4838 struct blame {
4839         struct blame_commit *commit;
4840         unsigned long lineno;
4841         char text[1];
4842 };
4844 static bool
4845 blame_open(struct view *view)
4847         char path[SIZEOF_STR];
4848         size_t i;
4850         if (!view->prev && *opt_prefix) {
4851                 string_copy(path, opt_file);
4852                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4853                         return FALSE;
4854         }
4856         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4857                 const char *blame_cat_file_argv[] = {
4858                         "git", "cat-file", "blob", path, NULL
4859                 };
4861                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4862                     !start_update(view, blame_cat_file_argv, opt_cdup))
4863                         return FALSE;
4864         }
4866         /* First pass: remove multiple references to the same commit. */
4867         for (i = 0; i < view->lines; i++) {
4868                 struct blame *blame = view->line[i].data;
4870                 if (blame->commit && blame->commit->id[0])
4871                         blame->commit->id[0] = 0;
4872                 else
4873                         blame->commit = NULL;
4874         }
4876         /* Second pass: free existing references. */
4877         for (i = 0; i < view->lines; i++) {
4878                 struct blame *blame = view->line[i].data;
4880                 if (blame->commit)
4881                         free(blame->commit);
4882         }
4884         setup_update(view, opt_file);
4885         string_format(view->ref, "%s ...", opt_file);
4887         return TRUE;
4890 static struct blame_commit *
4891 get_blame_commit(struct view *view, const char *id)
4893         size_t i;
4895         for (i = 0; i < view->lines; i++) {
4896                 struct blame *blame = view->line[i].data;
4898                 if (!blame->commit)
4899                         continue;
4901                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4902                         return blame->commit;
4903         }
4905         {
4906                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4908                 if (commit)
4909                         string_ncopy(commit->id, id, SIZEOF_REV);
4910                 return commit;
4911         }
4914 static bool
4915 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4917         const char *pos = *posref;
4919         *posref = NULL;
4920         pos = strchr(pos + 1, ' ');
4921         if (!pos || !isdigit(pos[1]))
4922                 return FALSE;
4923         *number = atoi(pos + 1);
4924         if (*number < min || *number > max)
4925                 return FALSE;
4927         *posref = pos;
4928         return TRUE;
4931 static struct blame_commit *
4932 parse_blame_commit(struct view *view, const char *text, int *blamed)
4934         struct blame_commit *commit;
4935         struct blame *blame;
4936         const char *pos = text + SIZEOF_REV - 2;
4937         size_t orig_lineno = 0;
4938         size_t lineno;
4939         size_t group;
4941         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4942                 return NULL;
4944         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4945             !parse_number(&pos, &lineno, 1, view->lines) ||
4946             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4947                 return NULL;
4949         commit = get_blame_commit(view, text);
4950         if (!commit)
4951                 return NULL;
4953         *blamed += group;
4954         while (group--) {
4955                 struct line *line = &view->line[lineno + group - 1];
4957                 blame = line->data;
4958                 blame->commit = commit;
4959                 blame->lineno = orig_lineno + group - 1;
4960                 line->dirty = 1;
4961         }
4963         return commit;
4966 static bool
4967 blame_read_file(struct view *view, const char *line, bool *read_file)
4969         if (!line) {
4970                 const char *blame_argv[] = {
4971                         "git", "blame", "--incremental",
4972                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4973                 };
4975                 if (view->lines == 0 && !view->prev)
4976                         die("No blame exist for %s", view->vid);
4978                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4979                         report("Failed to load blame data");
4980                         return TRUE;
4981                 }
4983                 *read_file = FALSE;
4984                 return FALSE;
4986         } else {
4987                 size_t linelen = strlen(line);
4988                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4990                 if (!blame)
4991                         return FALSE;
4993                 blame->commit = NULL;
4994                 strncpy(blame->text, line, linelen);
4995                 blame->text[linelen] = 0;
4996                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4997         }
5000 static bool
5001 match_blame_header(const char *name, char **line)
5003         size_t namelen = strlen(name);
5004         bool matched = !strncmp(name, *line, namelen);
5006         if (matched)
5007                 *line += namelen;
5009         return matched;
5012 static bool
5013 blame_read(struct view *view, char *line)
5015         static struct blame_commit *commit = NULL;
5016         static int blamed = 0;
5017         static bool read_file = TRUE;
5019         if (read_file)
5020                 return blame_read_file(view, line, &read_file);
5022         if (!line) {
5023                 /* Reset all! */
5024                 commit = NULL;
5025                 blamed = 0;
5026                 read_file = TRUE;
5027                 string_format(view->ref, "%s", view->vid);
5028                 if (view_is_displayed(view)) {
5029                         update_view_title(view);
5030                         redraw_view_from(view, 0);
5031                 }
5032                 return TRUE;
5033         }
5035         if (!commit) {
5036                 commit = parse_blame_commit(view, line, &blamed);
5037                 string_format(view->ref, "%s %2d%%", view->vid,
5038                               view->lines ? blamed * 100 / view->lines : 0);
5040         } else if (match_blame_header("author ", &line)) {
5041                 commit->author = get_author(line);
5043         } else if (match_blame_header("author-time ", &line)) {
5044                 parse_timesec(&commit->time, line);
5046         } else if (match_blame_header("author-tz ", &line)) {
5047                 parse_timezone(&commit->time, line);
5049         } else if (match_blame_header("summary ", &line)) {
5050                 string_ncopy(commit->title, line, strlen(line));
5052         } else if (match_blame_header("previous ", &line)) {
5053                 if (strlen(line) <= SIZEOF_REV)
5054                         return FALSE;
5055                 string_copy_rev(commit->parent_id, line);
5056                 line += SIZEOF_REV;
5057                 string_ncopy(commit->parent_filename, line, strlen(line));
5059         } else if (match_blame_header("filename ", &line)) {
5060                 string_ncopy(commit->filename, line, strlen(line));
5061                 commit = NULL;
5062         }
5064         return TRUE;
5067 static bool
5068 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5070         struct blame *blame = line->data;
5071         struct time *time = NULL;
5072         const char *id = NULL, *author = NULL;
5073         char text[SIZEOF_STR];
5075         if (blame->commit && *blame->commit->filename) {
5076                 id = blame->commit->id;
5077                 author = blame->commit->author;
5078                 time = &blame->commit->time;
5079         }
5081         if (opt_date && draw_date(view, time))
5082                 return TRUE;
5084         if (opt_author && draw_author(view, author))
5085                 return TRUE;
5087         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5088                 return TRUE;
5090         if (draw_lineno(view, lineno))
5091                 return TRUE;
5093         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5094         draw_text(view, LINE_DEFAULT, text, TRUE);
5095         return TRUE;
5098 static bool
5099 check_blame_commit(struct blame *blame, bool check_null_id)
5101         if (!blame->commit)
5102                 report("Commit data not loaded yet");
5103         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5104                 report("No commit exist for the selected line");
5105         else
5106                 return TRUE;
5107         return FALSE;
5110 static void
5111 setup_blame_parent_line(struct view *view, struct blame *blame)
5113         char from[SIZEOF_REF + SIZEOF_STR];
5114         char to[SIZEOF_REF + SIZEOF_STR];
5115         const char *diff_tree_argv[] = {
5116                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5117                         "-U0", from, to, "--", NULL
5118         };
5119         struct io io;
5120         int parent_lineno = -1;
5121         int blamed_lineno = -1;
5122         char *line;
5124         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5125             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5126             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5127                 return;
5129         while ((line = io_get(&io, '\n', TRUE))) {
5130                 if (*line == '@') {
5131                         char *pos = strchr(line, '+');
5133                         parent_lineno = atoi(line + 4);
5134                         if (pos)
5135                                 blamed_lineno = atoi(pos + 1);
5137                 } else if (*line == '+' && parent_lineno != -1) {
5138                         if (blame->lineno == blamed_lineno - 1 &&
5139                             !strcmp(blame->text, line + 1)) {
5140                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5141                                 break;
5142                         }
5143                         blamed_lineno++;
5144                 }
5145         }
5147         io_done(&io);
5150 static enum request
5151 blame_request(struct view *view, enum request request, struct line *line)
5153         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5154         struct blame *blame = line->data;
5156         switch (request) {
5157         case REQ_VIEW_BLAME:
5158                 if (check_blame_commit(blame, TRUE)) {
5159                         string_copy(opt_ref, blame->commit->id);
5160                         string_copy(opt_file, blame->commit->filename);
5161                         if (blame->lineno)
5162                                 view->lineno = blame->lineno;
5163                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5164                 }
5165                 break;
5167         case REQ_PARENT:
5168                 if (!check_blame_commit(blame, TRUE))
5169                         break;
5170                 if (!*blame->commit->parent_id) {
5171                         report("The selected commit has no parents");
5172                 } else {
5173                         string_copy_rev(opt_ref, blame->commit->parent_id);
5174                         string_copy(opt_file, blame->commit->parent_filename);
5175                         setup_blame_parent_line(view, blame);
5176                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5177                 }
5178                 break;
5180         case REQ_ENTER:
5181                 if (!check_blame_commit(blame, FALSE))
5182                         break;
5184                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5185                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5186                         break;
5188                 if (!strcmp(blame->commit->id, NULL_ID)) {
5189                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5190                         const char *diff_index_argv[] = {
5191                                 "git", "diff-index", "--root", "--patch-with-stat",
5192                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5193                         };
5195                         if (!*blame->commit->parent_id) {
5196                                 diff_index_argv[1] = "diff";
5197                                 diff_index_argv[2] = "--no-color";
5198                                 diff_index_argv[6] = "--";
5199                                 diff_index_argv[7] = "/dev/null";
5200                         }
5202                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5203                                 report("Failed to allocate diff command");
5204                                 break;
5205                         }
5206                         flags |= OPEN_PREPARED;
5207                 }
5209                 open_view(view, REQ_VIEW_DIFF, flags);
5210                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5211                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5212                 break;
5214         default:
5215                 return request;
5216         }
5218         return REQ_NONE;
5221 static bool
5222 blame_grep(struct view *view, struct line *line)
5224         struct blame *blame = line->data;
5225         struct blame_commit *commit = blame->commit;
5226         const char *text[] = {
5227                 blame->text,
5228                 commit ? commit->title : "",
5229                 commit ? commit->id : "",
5230                 commit && opt_author ? commit->author : "",
5231                 commit ? mkdate(&commit->time, opt_date) : "",
5232                 NULL
5233         };
5235         return grep_text(view, text);
5238 static void
5239 blame_select(struct view *view, struct line *line)
5241         struct blame *blame = line->data;
5242         struct blame_commit *commit = blame->commit;
5244         if (!commit)
5245                 return;
5247         if (!strcmp(commit->id, NULL_ID))
5248                 string_ncopy(ref_commit, "HEAD", 4);
5249         else
5250                 string_copy_rev(ref_commit, commit->id);
5253 static struct view_ops blame_ops = {
5254         "line",
5255         NULL,
5256         blame_open,
5257         blame_read,
5258         blame_draw,
5259         blame_request,
5260         blame_grep,
5261         blame_select,
5262 };
5264 /*
5265  * Branch backend
5266  */
5268 struct branch {
5269         const char *author;             /* Author of the last commit. */
5270         struct time time;               /* Date of the last activity. */
5271         const struct ref *ref;          /* Name and commit ID information. */
5272 };
5274 static const struct ref branch_all;
5276 static const enum sort_field branch_sort_fields[] = {
5277         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5278 };
5279 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5281 static int
5282 branch_compare(const void *l1, const void *l2)
5284         const struct branch *branch1 = ((const struct line *) l1)->data;
5285         const struct branch *branch2 = ((const struct line *) l2)->data;
5287         switch (get_sort_field(branch_sort_state)) {
5288         case ORDERBY_DATE:
5289                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5291         case ORDERBY_AUTHOR:
5292                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5294         case ORDERBY_NAME:
5295         default:
5296                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5297         }
5300 static bool
5301 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5303         struct branch *branch = line->data;
5304         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5306         if (opt_date && draw_date(view, &branch->time))
5307                 return TRUE;
5309         if (opt_author && draw_author(view, branch->author))
5310                 return TRUE;
5312         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5313         return TRUE;
5316 static enum request
5317 branch_request(struct view *view, enum request request, struct line *line)
5319         struct branch *branch = line->data;
5321         switch (request) {
5322         case REQ_REFRESH:
5323                 load_refs();
5324                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5325                 return REQ_NONE;
5327         case REQ_TOGGLE_SORT_FIELD:
5328         case REQ_TOGGLE_SORT_ORDER:
5329                 sort_view(view, request, &branch_sort_state, branch_compare);
5330                 return REQ_NONE;
5332         case REQ_ENTER:
5333         {
5334                 const struct ref *ref = branch->ref;
5335                 const char *all_branches_argv[] = {
5336                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5337                               "--topo-order",
5338                               ref == &branch_all ? "--all" : ref->name, NULL
5339                 };
5340                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5342                 if (!prepare_update(main_view, all_branches_argv, NULL))
5343                         report("Failed to load view of all branches");
5344                 else
5345                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5346                 return REQ_NONE;
5347         }
5348         default:
5349                 return request;
5350         }
5353 static bool
5354 branch_read(struct view *view, char *line)
5356         static char id[SIZEOF_REV];
5357         struct branch *reference;
5358         size_t i;
5360         if (!line)
5361                 return TRUE;
5363         switch (get_line_type(line)) {
5364         case LINE_COMMIT:
5365                 string_copy_rev(id, line + STRING_SIZE("commit "));
5366                 return TRUE;
5368         case LINE_AUTHOR:
5369                 for (i = 0, reference = NULL; i < view->lines; i++) {
5370                         struct branch *branch = view->line[i].data;
5372                         if (strcmp(branch->ref->id, id))
5373                                 continue;
5375                         view->line[i].dirty = TRUE;
5376                         if (reference) {
5377                                 branch->author = reference->author;
5378                                 branch->time = reference->time;
5379                                 continue;
5380                         }
5382                         parse_author_line(line + STRING_SIZE("author "),
5383                                           &branch->author, &branch->time);
5384                         reference = branch;
5385                 }
5386                 return TRUE;
5388         default:
5389                 return TRUE;
5390         }
5394 static bool
5395 branch_open_visitor(void *data, const struct ref *ref)
5397         struct view *view = data;
5398         struct branch *branch;
5400         if (ref->tag || ref->ltag || ref->remote)
5401                 return TRUE;
5403         branch = calloc(1, sizeof(*branch));
5404         if (!branch)
5405                 return FALSE;
5407         branch->ref = ref;
5408         return !!add_line_data(view, branch, LINE_DEFAULT);
5411 static bool
5412 branch_open(struct view *view)
5414         const char *branch_log[] = {
5415                 "git", "log", "--no-color", "--pretty=raw",
5416                         "--simplify-by-decoration", "--all", NULL
5417         };
5419         if (!start_update(view, branch_log, NULL)) {
5420                 report("Failed to load branch data");
5421                 return TRUE;
5422         }
5424         setup_update(view, view->id);
5425         branch_open_visitor(view, &branch_all);
5426         foreach_ref(branch_open_visitor, view);
5427         view->p_restore = TRUE;
5429         return TRUE;
5432 static bool
5433 branch_grep(struct view *view, struct line *line)
5435         struct branch *branch = line->data;
5436         const char *text[] = {
5437                 branch->ref->name,
5438                 branch->author,
5439                 NULL
5440         };
5442         return grep_text(view, text);
5445 static void
5446 branch_select(struct view *view, struct line *line)
5448         struct branch *branch = line->data;
5450         string_copy_rev(view->ref, branch->ref->id);
5451         string_copy_rev(ref_commit, branch->ref->id);
5452         string_copy_rev(ref_head, branch->ref->id);
5453         string_copy_rev(ref_branch, branch->ref->name);
5456 static struct view_ops branch_ops = {
5457         "branch",
5458         NULL,
5459         branch_open,
5460         branch_read,
5461         branch_draw,
5462         branch_request,
5463         branch_grep,
5464         branch_select,
5465 };
5467 /*
5468  * Status backend
5469  */
5471 struct status {
5472         char status;
5473         struct {
5474                 mode_t mode;
5475                 char rev[SIZEOF_REV];
5476                 char name[SIZEOF_STR];
5477         } old;
5478         struct {
5479                 mode_t mode;
5480                 char rev[SIZEOF_REV];
5481                 char name[SIZEOF_STR];
5482         } new;
5483 };
5485 static char status_onbranch[SIZEOF_STR];
5486 static struct status stage_status;
5487 static enum line_type stage_line_type;
5488 static size_t stage_chunks;
5489 static int *stage_chunk;
5491 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5493 /* This should work even for the "On branch" line. */
5494 static inline bool
5495 status_has_none(struct view *view, struct line *line)
5497         return line < view->line + view->lines && !line[1].data;
5500 /* Get fields from the diff line:
5501  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5502  */
5503 static inline bool
5504 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5506         const char *old_mode = buf +  1;
5507         const char *new_mode = buf +  8;
5508         const char *old_rev  = buf + 15;
5509         const char *new_rev  = buf + 56;
5510         const char *status   = buf + 97;
5512         if (bufsize < 98 ||
5513             old_mode[-1] != ':' ||
5514             new_mode[-1] != ' ' ||
5515             old_rev[-1]  != ' ' ||
5516             new_rev[-1]  != ' ' ||
5517             status[-1]   != ' ')
5518                 return FALSE;
5520         file->status = *status;
5522         string_copy_rev(file->old.rev, old_rev);
5523         string_copy_rev(file->new.rev, new_rev);
5525         file->old.mode = strtoul(old_mode, NULL, 8);
5526         file->new.mode = strtoul(new_mode, NULL, 8);
5528         file->old.name[0] = file->new.name[0] = 0;
5530         return TRUE;
5533 static bool
5534 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5536         struct status *unmerged = NULL;
5537         char *buf;
5538         struct io io;
5540         if (!io_run(&io, IO_RD, opt_cdup, argv))
5541                 return FALSE;
5543         add_line_data(view, NULL, type);
5545         while ((buf = io_get(&io, 0, TRUE))) {
5546                 struct status *file = unmerged;
5548                 if (!file) {
5549                         file = calloc(1, sizeof(*file));
5550                         if (!file || !add_line_data(view, file, type))
5551                                 goto error_out;
5552                 }
5554                 /* Parse diff info part. */
5555                 if (status) {
5556                         file->status = status;
5557                         if (status == 'A')
5558                                 string_copy(file->old.rev, NULL_ID);
5560                 } else if (!file->status || file == unmerged) {
5561                         if (!status_get_diff(file, buf, strlen(buf)))
5562                                 goto error_out;
5564                         buf = io_get(&io, 0, TRUE);
5565                         if (!buf)
5566                                 break;
5568                         /* Collapse all modified entries that follow an
5569                          * associated unmerged entry. */
5570                         if (unmerged == file) {
5571                                 unmerged->status = 'U';
5572                                 unmerged = NULL;
5573                         } else if (file->status == 'U') {
5574                                 unmerged = file;
5575                         }
5576                 }
5578                 /* Grab the old name for rename/copy. */
5579                 if (!*file->old.name &&
5580                     (file->status == 'R' || file->status == 'C')) {
5581                         string_ncopy(file->old.name, buf, strlen(buf));
5583                         buf = io_get(&io, 0, TRUE);
5584                         if (!buf)
5585                                 break;
5586                 }
5588                 /* git-ls-files just delivers a NUL separated list of
5589                  * file names similar to the second half of the
5590                  * git-diff-* output. */
5591                 string_ncopy(file->new.name, buf, strlen(buf));
5592                 if (!*file->old.name)
5593                         string_copy(file->old.name, file->new.name);
5594                 file = NULL;
5595         }
5597         if (io_error(&io)) {
5598 error_out:
5599                 io_done(&io);
5600                 return FALSE;
5601         }
5603         if (!view->line[view->lines - 1].data)
5604                 add_line_data(view, NULL, LINE_STAT_NONE);
5606         io_done(&io);
5607         return TRUE;
5610 /* Don't show unmerged entries in the staged section. */
5611 static const char *status_diff_index_argv[] = {
5612         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5613                              "--cached", "-M", "HEAD", NULL
5614 };
5616 static const char *status_diff_files_argv[] = {
5617         "git", "diff-files", "-z", NULL
5618 };
5620 static const char *status_list_other_argv[] = {
5621         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5622 };
5624 static const char *status_list_no_head_argv[] = {
5625         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5626 };
5628 static const char *update_index_argv[] = {
5629         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5630 };
5632 /* Restore the previous line number to stay in the context or select a
5633  * line with something that can be updated. */
5634 static void
5635 status_restore(struct view *view)
5637         if (view->p_lineno >= view->lines)
5638                 view->p_lineno = view->lines - 1;
5639         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5640                 view->p_lineno++;
5641         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5642                 view->p_lineno--;
5644         /* If the above fails, always skip the "On branch" line. */
5645         if (view->p_lineno < view->lines)
5646                 view->lineno = view->p_lineno;
5647         else
5648                 view->lineno = 1;
5650         if (view->lineno < view->offset)
5651                 view->offset = view->lineno;
5652         else if (view->offset + view->height <= view->lineno)
5653                 view->offset = view->lineno - view->height + 1;
5655         view->p_restore = FALSE;
5658 static void
5659 status_update_onbranch(void)
5661         static const char *paths[][2] = {
5662                 { "rebase-apply/rebasing",      "Rebasing" },
5663                 { "rebase-apply/applying",      "Applying mailbox" },
5664                 { "rebase-apply/",              "Rebasing mailbox" },
5665                 { "rebase-merge/interactive",   "Interactive rebase" },
5666                 { "rebase-merge/",              "Rebase merge" },
5667                 { "MERGE_HEAD",                 "Merging" },
5668                 { "BISECT_LOG",                 "Bisecting" },
5669                 { "HEAD",                       "On branch" },
5670         };
5671         char buf[SIZEOF_STR];
5672         struct stat stat;
5673         int i;
5675         if (is_initial_commit()) {
5676                 string_copy(status_onbranch, "Initial commit");
5677                 return;
5678         }
5680         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5681                 char *head = opt_head;
5683                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5684                     lstat(buf, &stat) < 0)
5685                         continue;
5687                 if (!*opt_head) {
5688                         struct io io;
5690                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5691                             io_read_buf(&io, buf, sizeof(buf))) {
5692                                 head = buf;
5693                                 if (!prefixcmp(head, "refs/heads/"))
5694                                         head += STRING_SIZE("refs/heads/");
5695                         }
5696                 }
5698                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5699                         string_copy(status_onbranch, opt_head);
5700                 return;
5701         }
5703         string_copy(status_onbranch, "Not currently on any branch");
5706 /* First parse staged info using git-diff-index(1), then parse unstaged
5707  * info using git-diff-files(1), and finally untracked files using
5708  * git-ls-files(1). */
5709 static bool
5710 status_open(struct view *view)
5712         reset_view(view);
5714         add_line_data(view, NULL, LINE_STAT_HEAD);
5715         status_update_onbranch();
5717         io_run_bg(update_index_argv);
5719         if (is_initial_commit()) {
5720                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5721                         return FALSE;
5722         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5723                 return FALSE;
5724         }
5726         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5727             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5728                 return FALSE;
5730         /* Restore the exact position or use the specialized restore
5731          * mode? */
5732         if (!view->p_restore)
5733                 status_restore(view);
5734         return TRUE;
5737 static bool
5738 status_draw(struct view *view, struct line *line, unsigned int lineno)
5740         struct status *status = line->data;
5741         enum line_type type;
5742         const char *text;
5744         if (!status) {
5745                 switch (line->type) {
5746                 case LINE_STAT_STAGED:
5747                         type = LINE_STAT_SECTION;
5748                         text = "Changes to be committed:";
5749                         break;
5751                 case LINE_STAT_UNSTAGED:
5752                         type = LINE_STAT_SECTION;
5753                         text = "Changed but not updated:";
5754                         break;
5756                 case LINE_STAT_UNTRACKED:
5757                         type = LINE_STAT_SECTION;
5758                         text = "Untracked files:";
5759                         break;
5761                 case LINE_STAT_NONE:
5762                         type = LINE_DEFAULT;
5763                         text = "  (no files)";
5764                         break;
5766                 case LINE_STAT_HEAD:
5767                         type = LINE_STAT_HEAD;
5768                         text = status_onbranch;
5769                         break;
5771                 default:
5772                         return FALSE;
5773                 }
5774         } else {
5775                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5777                 buf[0] = status->status;
5778                 if (draw_text(view, line->type, buf, TRUE))
5779                         return TRUE;
5780                 type = LINE_DEFAULT;
5781                 text = status->new.name;
5782         }
5784         draw_text(view, type, text, TRUE);
5785         return TRUE;
5788 static enum request
5789 status_load_error(struct view *view, struct view *stage, const char *path)
5791         if (displayed_views() == 2 || display[current_view] != view)
5792                 maximize_view(view);
5793         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5794         return REQ_NONE;
5797 static enum request
5798 status_enter(struct view *view, struct line *line)
5800         struct status *status = line->data;
5801         const char *oldpath = status ? status->old.name : NULL;
5802         /* Diffs for unmerged entries are empty when passing the new
5803          * path, so leave it empty. */
5804         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5805         const char *info;
5806         enum open_flags split;
5807         struct view *stage = VIEW(REQ_VIEW_STAGE);
5809         if (line->type == LINE_STAT_NONE ||
5810             (!status && line[1].type == LINE_STAT_NONE)) {
5811                 report("No file to diff");
5812                 return REQ_NONE;
5813         }
5815         switch (line->type) {
5816         case LINE_STAT_STAGED:
5817                 if (is_initial_commit()) {
5818                         const char *no_head_diff_argv[] = {
5819                                 "git", "diff", "--no-color", "--patch-with-stat",
5820                                         "--", "/dev/null", newpath, NULL
5821                         };
5823                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5824                                 return status_load_error(view, stage, newpath);
5825                 } else {
5826                         const char *index_show_argv[] = {
5827                                 "git", "diff-index", "--root", "--patch-with-stat",
5828                                         "-C", "-M", "--cached", "HEAD", "--",
5829                                         oldpath, newpath, NULL
5830                         };
5832                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5833                                 return status_load_error(view, stage, newpath);
5834                 }
5836                 if (status)
5837                         info = "Staged changes to %s";
5838                 else
5839                         info = "Staged changes";
5840                 break;
5842         case LINE_STAT_UNSTAGED:
5843         {
5844                 const char *files_show_argv[] = {
5845                         "git", "diff-files", "--root", "--patch-with-stat",
5846                                 "-C", "-M", "--", oldpath, newpath, NULL
5847                 };
5849                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5850                         return status_load_error(view, stage, newpath);
5851                 if (status)
5852                         info = "Unstaged changes to %s";
5853                 else
5854                         info = "Unstaged changes";
5855                 break;
5856         }
5857         case LINE_STAT_UNTRACKED:
5858                 if (!newpath) {
5859                         report("No file to show");
5860                         return REQ_NONE;
5861                 }
5863                 if (!suffixcmp(status->new.name, -1, "/")) {
5864                         report("Cannot display a directory");
5865                         return REQ_NONE;
5866                 }
5868                 if (!prepare_update_file(stage, newpath))
5869                         return status_load_error(view, stage, newpath);
5870                 info = "Untracked file %s";
5871                 break;
5873         case LINE_STAT_HEAD:
5874                 return REQ_NONE;
5876         default:
5877                 die("line type %d not handled in switch", line->type);
5878         }
5880         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5881         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5882         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5883                 if (status) {
5884                         stage_status = *status;
5885                 } else {
5886                         memset(&stage_status, 0, sizeof(stage_status));
5887                 }
5889                 stage_line_type = line->type;
5890                 stage_chunks = 0;
5891                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5892         }
5894         return REQ_NONE;
5897 static bool
5898 status_exists(struct status *status, enum line_type type)
5900         struct view *view = VIEW(REQ_VIEW_STATUS);
5901         unsigned long lineno;
5903         for (lineno = 0; lineno < view->lines; lineno++) {
5904                 struct line *line = &view->line[lineno];
5905                 struct status *pos = line->data;
5907                 if (line->type != type)
5908                         continue;
5909                 if (!pos && (!status || !status->status) && line[1].data) {
5910                         select_view_line(view, lineno);
5911                         return TRUE;
5912                 }
5913                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5914                         select_view_line(view, lineno);
5915                         return TRUE;
5916                 }
5917         }
5919         return FALSE;
5923 static bool
5924 status_update_prepare(struct io *io, enum line_type type)
5926         const char *staged_argv[] = {
5927                 "git", "update-index", "-z", "--index-info", NULL
5928         };
5929         const char *others_argv[] = {
5930                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5931         };
5933         switch (type) {
5934         case LINE_STAT_STAGED:
5935                 return io_run(io, IO_WR, opt_cdup, staged_argv);
5937         case LINE_STAT_UNSTAGED:
5938         case LINE_STAT_UNTRACKED:
5939                 return io_run(io, IO_WR, opt_cdup, others_argv);
5941         default:
5942                 die("line type %d not handled in switch", type);
5943                 return FALSE;
5944         }
5947 static bool
5948 status_update_write(struct io *io, struct status *status, enum line_type type)
5950         char buf[SIZEOF_STR];
5951         size_t bufsize = 0;
5953         switch (type) {
5954         case LINE_STAT_STAGED:
5955                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5956                                         status->old.mode,
5957                                         status->old.rev,
5958                                         status->old.name, 0))
5959                         return FALSE;
5960                 break;
5962         case LINE_STAT_UNSTAGED:
5963         case LINE_STAT_UNTRACKED:
5964                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5965                         return FALSE;
5966                 break;
5968         default:
5969                 die("line type %d not handled in switch", type);
5970         }
5972         return io_write(io, buf, bufsize);
5975 static bool
5976 status_update_file(struct status *status, enum line_type type)
5978         struct io io;
5979         bool result;
5981         if (!status_update_prepare(&io, type))
5982                 return FALSE;
5984         result = status_update_write(&io, status, type);
5985         return io_done(&io) && result;
5988 static bool
5989 status_update_files(struct view *view, struct line *line)
5991         char buf[sizeof(view->ref)];
5992         struct io io;
5993         bool result = TRUE;
5994         struct line *pos = view->line + view->lines;
5995         int files = 0;
5996         int file, done;
5997         int cursor_y = -1, cursor_x = -1;
5999         if (!status_update_prepare(&io, line->type))
6000                 return FALSE;
6002         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6003                 files++;
6005         string_copy(buf, view->ref);
6006         getsyx(cursor_y, cursor_x);
6007         for (file = 0, done = 5; result && file < files; line++, file++) {
6008                 int almost_done = file * 100 / files;
6010                 if (almost_done > done) {
6011                         done = almost_done;
6012                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6013                                       file, files, done);
6014                         update_view_title(view);
6015                         setsyx(cursor_y, cursor_x);
6016                         doupdate();
6017                 }
6018                 result = status_update_write(&io, line->data, line->type);
6019         }
6020         string_copy(view->ref, buf);
6022         return io_done(&io) && result;
6025 static bool
6026 status_update(struct view *view)
6028         struct line *line = &view->line[view->lineno];
6030         assert(view->lines);
6032         if (!line->data) {
6033                 /* This should work even for the "On branch" line. */
6034                 if (line < view->line + view->lines && !line[1].data) {
6035                         report("Nothing to update");
6036                         return FALSE;
6037                 }
6039                 if (!status_update_files(view, line + 1)) {
6040                         report("Failed to update file status");
6041                         return FALSE;
6042                 }
6044         } else if (!status_update_file(line->data, line->type)) {
6045                 report("Failed to update file status");
6046                 return FALSE;
6047         }
6049         return TRUE;
6052 static bool
6053 status_revert(struct status *status, enum line_type type, bool has_none)
6055         if (!status || type != LINE_STAT_UNSTAGED) {
6056                 if (type == LINE_STAT_STAGED) {
6057                         report("Cannot revert changes to staged files");
6058                 } else if (type == LINE_STAT_UNTRACKED) {
6059                         report("Cannot revert changes to untracked files");
6060                 } else if (has_none) {
6061                         report("Nothing to revert");
6062                 } else {
6063                         report("Cannot revert changes to multiple files");
6064                 }
6066         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6067                 char mode[10] = "100644";
6068                 const char *reset_argv[] = {
6069                         "git", "update-index", "--cacheinfo", mode,
6070                                 status->old.rev, status->old.name, NULL
6071                 };
6072                 const char *checkout_argv[] = {
6073                         "git", "checkout", "--", status->old.name, NULL
6074                 };
6076                 if (status->status == 'U') {
6077                         string_format(mode, "%5o", status->old.mode);
6079                         if (status->old.mode == 0 && status->new.mode == 0) {
6080                                 reset_argv[2] = "--force-remove";
6081                                 reset_argv[3] = status->old.name;
6082                                 reset_argv[4] = NULL;
6083                         }
6085                         if (!io_run_fg(reset_argv, opt_cdup))
6086                                 return FALSE;
6087                         if (status->old.mode == 0 && status->new.mode == 0)
6088                                 return TRUE;
6089                 }
6091                 return io_run_fg(checkout_argv, opt_cdup);
6092         }
6094         return FALSE;
6097 static enum request
6098 status_request(struct view *view, enum request request, struct line *line)
6100         struct status *status = line->data;
6102         switch (request) {
6103         case REQ_STATUS_UPDATE:
6104                 if (!status_update(view))
6105                         return REQ_NONE;
6106                 break;
6108         case REQ_STATUS_REVERT:
6109                 if (!status_revert(status, line->type, status_has_none(view, line)))
6110                         return REQ_NONE;
6111                 break;
6113         case REQ_STATUS_MERGE:
6114                 if (!status || status->status != 'U') {
6115                         report("Merging only possible for files with unmerged status ('U').");
6116                         return REQ_NONE;
6117                 }
6118                 open_mergetool(status->new.name);
6119                 break;
6121         case REQ_EDIT:
6122                 if (!status)
6123                         return request;
6124                 if (status->status == 'D') {
6125                         report("File has been deleted.");
6126                         return REQ_NONE;
6127                 }
6129                 open_editor(status->new.name);
6130                 break;
6132         case REQ_VIEW_BLAME:
6133                 if (status)
6134                         opt_ref[0] = 0;
6135                 return request;
6137         case REQ_ENTER:
6138                 /* After returning the status view has been split to
6139                  * show the stage view. No further reloading is
6140                  * necessary. */
6141                 return status_enter(view, line);
6143         case REQ_REFRESH:
6144                 /* Simply reload the view. */
6145                 break;
6147         default:
6148                 return request;
6149         }
6151         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6153         return REQ_NONE;
6156 static void
6157 status_select(struct view *view, struct line *line)
6159         struct status *status = line->data;
6160         char file[SIZEOF_STR] = "all files";
6161         const char *text;
6162         const char *key;
6164         if (status && !string_format(file, "'%s'", status->new.name))
6165                 return;
6167         if (!status && line[1].type == LINE_STAT_NONE)
6168                 line++;
6170         switch (line->type) {
6171         case LINE_STAT_STAGED:
6172                 text = "Press %s to unstage %s for commit";
6173                 break;
6175         case LINE_STAT_UNSTAGED:
6176                 text = "Press %s to stage %s for commit";
6177                 break;
6179         case LINE_STAT_UNTRACKED:
6180                 text = "Press %s to stage %s for addition";
6181                 break;
6183         case LINE_STAT_HEAD:
6184         case LINE_STAT_NONE:
6185                 text = "Nothing to update";
6186                 break;
6188         default:
6189                 die("line type %d not handled in switch", line->type);
6190         }
6192         if (status && status->status == 'U') {
6193                 text = "Press %s to resolve conflict in %s";
6194                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6196         } else {
6197                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6198         }
6200         string_format(view->ref, text, key, file);
6201         if (status)
6202                 string_copy(opt_file, status->new.name);
6205 static bool
6206 status_grep(struct view *view, struct line *line)
6208         struct status *status = line->data;
6210         if (status) {
6211                 const char buf[2] = { status->status, 0 };
6212                 const char *text[] = { status->new.name, buf, NULL };
6214                 return grep_text(view, text);
6215         }
6217         return FALSE;
6220 static struct view_ops status_ops = {
6221         "file",
6222         NULL,
6223         status_open,
6224         NULL,
6225         status_draw,
6226         status_request,
6227         status_grep,
6228         status_select,
6229 };
6232 static bool
6233 stage_diff_write(struct io *io, struct line *line, struct line *end)
6235         while (line < end) {
6236                 if (!io_write(io, line->data, strlen(line->data)) ||
6237                     !io_write(io, "\n", 1))
6238                         return FALSE;
6239                 line++;
6240                 if (line->type == LINE_DIFF_CHUNK ||
6241                     line->type == LINE_DIFF_HEADER)
6242                         break;
6243         }
6245         return TRUE;
6248 static struct line *
6249 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6251         for (; view->line < line; line--)
6252                 if (line->type == type)
6253                         return line;
6255         return NULL;
6258 static bool
6259 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6261         const char *apply_argv[SIZEOF_ARG] = {
6262                 "git", "apply", "--whitespace=nowarn", NULL
6263         };
6264         struct line *diff_hdr;
6265         struct io io;
6266         int argc = 3;
6268         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6269         if (!diff_hdr)
6270                 return FALSE;
6272         if (!revert)
6273                 apply_argv[argc++] = "--cached";
6274         if (revert || stage_line_type == LINE_STAT_STAGED)
6275                 apply_argv[argc++] = "-R";
6276         apply_argv[argc++] = "-";
6277         apply_argv[argc++] = NULL;
6278         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6279                 return FALSE;
6281         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6282             !stage_diff_write(&io, chunk, view->line + view->lines))
6283                 chunk = NULL;
6285         io_done(&io);
6286         io_run_bg(update_index_argv);
6288         return chunk ? TRUE : FALSE;
6291 static bool
6292 stage_update(struct view *view, struct line *line)
6294         struct line *chunk = NULL;
6296         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6297                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6299         if (chunk) {
6300                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6301                         report("Failed to apply chunk");
6302                         return FALSE;
6303                 }
6305         } else if (!stage_status.status) {
6306                 view = VIEW(REQ_VIEW_STATUS);
6308                 for (line = view->line; line < view->line + view->lines; line++)
6309                         if (line->type == stage_line_type)
6310                                 break;
6312                 if (!status_update_files(view, line + 1)) {
6313                         report("Failed to update files");
6314                         return FALSE;
6315                 }
6317         } else if (!status_update_file(&stage_status, stage_line_type)) {
6318                 report("Failed to update file");
6319                 return FALSE;
6320         }
6322         return TRUE;
6325 static bool
6326 stage_revert(struct view *view, struct line *line)
6328         struct line *chunk = NULL;
6330         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6331                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6333         if (chunk) {
6334                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6335                         return FALSE;
6337                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6338                         report("Failed to revert chunk");
6339                         return FALSE;
6340                 }
6341                 return TRUE;
6343         } else {
6344                 return status_revert(stage_status.status ? &stage_status : NULL,
6345                                      stage_line_type, FALSE);
6346         }
6350 static void
6351 stage_next(struct view *view, struct line *line)
6353         int i;
6355         if (!stage_chunks) {
6356                 for (line = view->line; line < view->line + view->lines; line++) {
6357                         if (line->type != LINE_DIFF_CHUNK)
6358                                 continue;
6360                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6361                                 report("Allocation failure");
6362                                 return;
6363                         }
6365                         stage_chunk[stage_chunks++] = line - view->line;
6366                 }
6367         }
6369         for (i = 0; i < stage_chunks; i++) {
6370                 if (stage_chunk[i] > view->lineno) {
6371                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6372                         report("Chunk %d of %d", i + 1, stage_chunks);
6373                         return;
6374                 }
6375         }
6377         report("No next chunk found");
6380 static enum request
6381 stage_request(struct view *view, enum request request, struct line *line)
6383         switch (request) {
6384         case REQ_STATUS_UPDATE:
6385                 if (!stage_update(view, line))
6386                         return REQ_NONE;
6387                 break;
6389         case REQ_STATUS_REVERT:
6390                 if (!stage_revert(view, line))
6391                         return REQ_NONE;
6392                 break;
6394         case REQ_STAGE_NEXT:
6395                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6396                         report("File is untracked; press %s to add",
6397                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6398                         return REQ_NONE;
6399                 }
6400                 stage_next(view, line);
6401                 return REQ_NONE;
6403         case REQ_EDIT:
6404                 if (!stage_status.new.name[0])
6405                         return request;
6406                 if (stage_status.status == 'D') {
6407                         report("File has been deleted.");
6408                         return REQ_NONE;
6409                 }
6411                 open_editor(stage_status.new.name);
6412                 break;
6414         case REQ_REFRESH:
6415                 /* Reload everything ... */
6416                 break;
6418         case REQ_VIEW_BLAME:
6419                 if (stage_status.new.name[0]) {
6420                         string_copy(opt_file, stage_status.new.name);
6421                         opt_ref[0] = 0;
6422                 }
6423                 return request;
6425         case REQ_ENTER:
6426                 return pager_request(view, request, line);
6428         default:
6429                 return request;
6430         }
6432         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6433         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6435         /* Check whether the staged entry still exists, and close the
6436          * stage view if it doesn't. */
6437         if (!status_exists(&stage_status, stage_line_type)) {
6438                 status_restore(VIEW(REQ_VIEW_STATUS));
6439                 return REQ_VIEW_CLOSE;
6440         }
6442         if (stage_line_type == LINE_STAT_UNTRACKED) {
6443                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6444                         report("Cannot display a directory");
6445                         return REQ_NONE;
6446                 }
6448                 if (!prepare_update_file(view, stage_status.new.name)) {
6449                         report("Failed to open file: %s", strerror(errno));
6450                         return REQ_NONE;
6451                 }
6452         }
6453         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6455         return REQ_NONE;
6458 static struct view_ops stage_ops = {
6459         "line",
6460         NULL,
6461         NULL,
6462         pager_read,
6463         pager_draw,
6464         stage_request,
6465         pager_grep,
6466         pager_select,
6467 };
6470 /*
6471  * Revision graph
6472  */
6474 struct commit {
6475         char id[SIZEOF_REV];            /* SHA1 ID. */
6476         char title[128];                /* First line of the commit message. */
6477         const char *author;             /* Author of the commit. */
6478         struct time time;               /* Date from the author ident. */
6479         struct ref_list *refs;          /* Repository references. */
6480         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6481         size_t graph_size;              /* The width of the graph array. */
6482         bool has_parents;               /* Rewritten --parents seen. */
6483 };
6485 /* Size of rev graph with no  "padding" columns */
6486 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6488 struct rev_graph {
6489         struct rev_graph *prev, *next, *parents;
6490         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6491         size_t size;
6492         struct commit *commit;
6493         size_t pos;
6494         unsigned int boundary:1;
6495 };
6497 /* Parents of the commit being visualized. */
6498 static struct rev_graph graph_parents[4];
6500 /* The current stack of revisions on the graph. */
6501 static struct rev_graph graph_stacks[4] = {
6502         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6503         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6504         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6505         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6506 };
6508 static inline bool
6509 graph_parent_is_merge(struct rev_graph *graph)
6511         return graph->parents->size > 1;
6514 static inline void
6515 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6517         struct commit *commit = graph->commit;
6519         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6520                 commit->graph[commit->graph_size++] = symbol;
6523 static void
6524 clear_rev_graph(struct rev_graph *graph)
6526         graph->boundary = 0;
6527         graph->size = graph->pos = 0;
6528         graph->commit = NULL;
6529         memset(graph->parents, 0, sizeof(*graph->parents));
6532 static void
6533 done_rev_graph(struct rev_graph *graph)
6535         if (graph_parent_is_merge(graph) &&
6536             graph->pos < graph->size - 1 &&
6537             graph->next->size == graph->size + graph->parents->size - 1) {
6538                 size_t i = graph->pos + graph->parents->size - 1;
6540                 graph->commit->graph_size = i * 2;
6541                 while (i < graph->next->size - 1) {
6542                         append_to_rev_graph(graph, ' ');
6543                         append_to_rev_graph(graph, '\\');
6544                         i++;
6545                 }
6546         }
6548         clear_rev_graph(graph);
6551 static void
6552 push_rev_graph(struct rev_graph *graph, const char *parent)
6554         int i;
6556         /* "Collapse" duplicate parents lines.
6557          *
6558          * FIXME: This needs to also update update the drawn graph but
6559          * for now it just serves as a method for pruning graph lines. */
6560         for (i = 0; i < graph->size; i++)
6561                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6562                         return;
6564         if (graph->size < SIZEOF_REVITEMS) {
6565                 string_copy_rev(graph->rev[graph->size++], parent);
6566         }
6569 static chtype
6570 get_rev_graph_symbol(struct rev_graph *graph)
6572         chtype symbol;
6574         if (graph->boundary)
6575                 symbol = REVGRAPH_BOUND;
6576         else if (graph->parents->size == 0)
6577                 symbol = REVGRAPH_INIT;
6578         else if (graph_parent_is_merge(graph))
6579                 symbol = REVGRAPH_MERGE;
6580         else if (graph->pos >= graph->size)
6581                 symbol = REVGRAPH_BRANCH;
6582         else
6583                 symbol = REVGRAPH_COMMIT;
6585         return symbol;
6588 static void
6589 draw_rev_graph(struct rev_graph *graph)
6591         struct rev_filler {
6592                 chtype separator, line;
6593         };
6594         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6595         static struct rev_filler fillers[] = {
6596                 { ' ',  '|' },
6597                 { '`',  '.' },
6598                 { '\'', ' ' },
6599                 { '/',  ' ' },
6600         };
6601         chtype symbol = get_rev_graph_symbol(graph);
6602         struct rev_filler *filler;
6603         size_t i;
6605         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6606         filler = &fillers[DEFAULT];
6608         for (i = 0; i < graph->pos; i++) {
6609                 append_to_rev_graph(graph, filler->line);
6610                 if (graph_parent_is_merge(graph->prev) &&
6611                     graph->prev->pos == i)
6612                         filler = &fillers[RSHARP];
6614                 append_to_rev_graph(graph, filler->separator);
6615         }
6617         /* Place the symbol for this revision. */
6618         append_to_rev_graph(graph, symbol);
6620         if (graph->prev->size > graph->size)
6621                 filler = &fillers[RDIAG];
6622         else
6623                 filler = &fillers[DEFAULT];
6625         i++;
6627         for (; i < graph->size; i++) {
6628                 append_to_rev_graph(graph, filler->separator);
6629                 append_to_rev_graph(graph, filler->line);
6630                 if (graph_parent_is_merge(graph->prev) &&
6631                     i < graph->prev->pos + graph->parents->size)
6632                         filler = &fillers[RSHARP];
6633                 if (graph->prev->size > graph->size)
6634                         filler = &fillers[LDIAG];
6635         }
6637         if (graph->prev->size > graph->size) {
6638                 append_to_rev_graph(graph, filler->separator);
6639                 if (filler->line != ' ')
6640                         append_to_rev_graph(graph, filler->line);
6641         }
6644 /* Prepare the next rev graph */
6645 static void
6646 prepare_rev_graph(struct rev_graph *graph)
6648         size_t i;
6650         /* First, traverse all lines of revisions up to the active one. */
6651         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6652                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6653                         break;
6655                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6656         }
6658         /* Interleave the new revision parent(s). */
6659         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6660                 push_rev_graph(graph->next, graph->parents->rev[i]);
6662         /* Lastly, put any remaining revisions. */
6663         for (i = graph->pos + 1; i < graph->size; i++)
6664                 push_rev_graph(graph->next, graph->rev[i]);
6667 static void
6668 update_rev_graph(struct view *view, struct rev_graph *graph)
6670         /* If this is the finalizing update ... */
6671         if (graph->commit)
6672                 prepare_rev_graph(graph);
6674         /* Graph visualization needs a one rev look-ahead,
6675          * so the first update doesn't visualize anything. */
6676         if (!graph->prev->commit)
6677                 return;
6679         if (view->lines > 2)
6680                 view->line[view->lines - 3].dirty = 1;
6681         if (view->lines > 1)
6682                 view->line[view->lines - 2].dirty = 1;
6683         draw_rev_graph(graph->prev);
6684         done_rev_graph(graph->prev->prev);
6688 /*
6689  * Main view backend
6690  */
6692 static const char *main_argv[SIZEOF_ARG] = {
6693         "git", "log", "--no-color", "--pretty=raw", "--parents",
6694                 "--topo-order", "%(diff-args)", "%(rev-args)",
6695                 "--", "%(file-args)", NULL
6696 };
6698 static bool
6699 main_draw(struct view *view, struct line *line, unsigned int lineno)
6701         struct commit *commit = line->data;
6703         if (!commit->author)
6704                 return FALSE;
6706         if (opt_date && draw_date(view, &commit->time))
6707                 return TRUE;
6709         if (opt_author && draw_author(view, commit->author))
6710                 return TRUE;
6712         if (opt_rev_graph && commit->graph_size &&
6713             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6714                 return TRUE;
6716         if (opt_show_refs && commit->refs) {
6717                 size_t i;
6719                 for (i = 0; i < commit->refs->size; i++) {
6720                         struct ref *ref = commit->refs->refs[i];
6721                         enum line_type type;
6723                         if (ref->head)
6724                                 type = LINE_MAIN_HEAD;
6725                         else if (ref->ltag)
6726                                 type = LINE_MAIN_LOCAL_TAG;
6727                         else if (ref->tag)
6728                                 type = LINE_MAIN_TAG;
6729                         else if (ref->tracked)
6730                                 type = LINE_MAIN_TRACKED;
6731                         else if (ref->remote)
6732                                 type = LINE_MAIN_REMOTE;
6733                         else
6734                                 type = LINE_MAIN_REF;
6736                         if (draw_text(view, type, "[", TRUE) ||
6737                             draw_text(view, type, ref->name, TRUE) ||
6738                             draw_text(view, type, "]", TRUE))
6739                                 return TRUE;
6741                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6742                                 return TRUE;
6743                 }
6744         }
6746         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6747         return TRUE;
6750 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6751 static bool
6752 main_read(struct view *view, char *line)
6754         static struct rev_graph *graph = graph_stacks;
6755         enum line_type type;
6756         struct commit *commit;
6758         if (!line) {
6759                 int i;
6761                 if (!view->lines && !view->prev)
6762                         die("No revisions match the given arguments.");
6763                 if (view->lines > 0) {
6764                         commit = view->line[view->lines - 1].data;
6765                         view->line[view->lines - 1].dirty = 1;
6766                         if (!commit->author) {
6767                                 view->lines--;
6768                                 free(commit);
6769                                 graph->commit = NULL;
6770                         }
6771                 }
6772                 update_rev_graph(view, graph);
6774                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6775                         clear_rev_graph(&graph_stacks[i]);
6776                 return TRUE;
6777         }
6779         type = get_line_type(line);
6780         if (type == LINE_COMMIT) {
6781                 commit = calloc(1, sizeof(struct commit));
6782                 if (!commit)
6783                         return FALSE;
6785                 line += STRING_SIZE("commit ");
6786                 if (*line == '-') {
6787                         graph->boundary = 1;
6788                         line++;
6789                 }
6791                 string_copy_rev(commit->id, line);
6792                 commit->refs = get_ref_list(commit->id);
6793                 graph->commit = commit;
6794                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6796                 while ((line = strchr(line, ' '))) {
6797                         line++;
6798                         push_rev_graph(graph->parents, line);
6799                         commit->has_parents = TRUE;
6800                 }
6801                 return TRUE;
6802         }
6804         if (!view->lines)
6805                 return TRUE;
6806         commit = view->line[view->lines - 1].data;
6808         switch (type) {
6809         case LINE_PARENT:
6810                 if (commit->has_parents)
6811                         break;
6812                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6813                 break;
6815         case LINE_AUTHOR:
6816                 parse_author_line(line + STRING_SIZE("author "),
6817                                   &commit->author, &commit->time);
6818                 update_rev_graph(view, graph);
6819                 graph = graph->next;
6820                 break;
6822         default:
6823                 /* Fill in the commit title if it has not already been set. */
6824                 if (commit->title[0])
6825                         break;
6827                 /* Require titles to start with a non-space character at the
6828                  * offset used by git log. */
6829                 if (strncmp(line, "    ", 4))
6830                         break;
6831                 line += 4;
6832                 /* Well, if the title starts with a whitespace character,
6833                  * try to be forgiving.  Otherwise we end up with no title. */
6834                 while (isspace(*line))
6835                         line++;
6836                 if (*line == '\0')
6837                         break;
6838                 /* FIXME: More graceful handling of titles; append "..." to
6839                  * shortened titles, etc. */
6841                 string_expand(commit->title, sizeof(commit->title), line, 1);
6842                 view->line[view->lines - 1].dirty = 1;
6843         }
6845         return TRUE;
6848 static enum request
6849 main_request(struct view *view, enum request request, struct line *line)
6851         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6853         switch (request) {
6854         case REQ_ENTER:
6855                 open_view(view, REQ_VIEW_DIFF, flags);
6856                 break;
6857         case REQ_REFRESH:
6858                 load_refs();
6859                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6860                 break;
6861         default:
6862                 return request;
6863         }
6865         return REQ_NONE;
6868 static bool
6869 grep_refs(struct ref_list *list, regex_t *regex)
6871         regmatch_t pmatch;
6872         size_t i;
6874         if (!opt_show_refs || !list)
6875                 return FALSE;
6877         for (i = 0; i < list->size; i++) {
6878                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6879                         return TRUE;
6880         }
6882         return FALSE;
6885 static bool
6886 main_grep(struct view *view, struct line *line)
6888         struct commit *commit = line->data;
6889         const char *text[] = {
6890                 commit->title,
6891                 opt_author ? commit->author : "",
6892                 mkdate(&commit->time, opt_date),
6893                 NULL
6894         };
6896         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6899 static void
6900 main_select(struct view *view, struct line *line)
6902         struct commit *commit = line->data;
6904         string_copy_rev(view->ref, commit->id);
6905         string_copy_rev(ref_commit, view->ref);
6908 static struct view_ops main_ops = {
6909         "commit",
6910         main_argv,
6911         NULL,
6912         main_read,
6913         main_draw,
6914         main_request,
6915         main_grep,
6916         main_select,
6917 };
6920 /*
6921  * Status management
6922  */
6924 /* Whether or not the curses interface has been initialized. */
6925 static bool cursed = FALSE;
6927 /* Terminal hacks and workarounds. */
6928 static bool use_scroll_redrawwin;
6929 static bool use_scroll_status_wclear;
6931 /* The status window is used for polling keystrokes. */
6932 static WINDOW *status_win;
6934 /* Reading from the prompt? */
6935 static bool input_mode = FALSE;
6937 static bool status_empty = FALSE;
6939 /* Update status and title window. */
6940 static void
6941 report(const char *msg, ...)
6943         struct view *view = display[current_view];
6945         if (input_mode)
6946                 return;
6948         if (!view) {
6949                 char buf[SIZEOF_STR];
6950                 va_list args;
6952                 va_start(args, msg);
6953                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6954                         buf[sizeof(buf) - 1] = 0;
6955                         buf[sizeof(buf) - 2] = '.';
6956                         buf[sizeof(buf) - 3] = '.';
6957                         buf[sizeof(buf) - 4] = '.';
6958                 }
6959                 va_end(args);
6960                 die("%s", buf);
6961         }
6963         if (!status_empty || *msg) {
6964                 va_list args;
6966                 va_start(args, msg);
6968                 wmove(status_win, 0, 0);
6969                 if (view->has_scrolled && use_scroll_status_wclear)
6970                         wclear(status_win);
6971                 if (*msg) {
6972                         vwprintw(status_win, msg, args);
6973                         status_empty = FALSE;
6974                 } else {
6975                         status_empty = TRUE;
6976                 }
6977                 wclrtoeol(status_win);
6978                 wnoutrefresh(status_win);
6980                 va_end(args);
6981         }
6983         update_view_title(view);
6986 static void
6987 init_display(void)
6989         const char *term;
6990         int x, y;
6992         /* Initialize the curses library */
6993         if (isatty(STDIN_FILENO)) {
6994                 cursed = !!initscr();
6995                 opt_tty = stdin;
6996         } else {
6997                 /* Leave stdin and stdout alone when acting as a pager. */
6998                 opt_tty = fopen("/dev/tty", "r+");
6999                 if (!opt_tty)
7000                         die("Failed to open /dev/tty");
7001                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7002         }
7004         if (!cursed)
7005                 die("Failed to initialize curses");
7007         nonl();         /* Disable conversion and detect newlines from input. */
7008         cbreak();       /* Take input chars one at a time, no wait for \n */
7009         noecho();       /* Don't echo input */
7010         leaveok(stdscr, FALSE);
7012         if (has_colors())
7013                 init_colors();
7015         getmaxyx(stdscr, y, x);
7016         status_win = newwin(1, 0, y - 1, 0);
7017         if (!status_win)
7018                 die("Failed to create status window");
7020         /* Enable keyboard mapping */
7021         keypad(status_win, TRUE);
7022         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7024         TABSIZE = opt_tab_size;
7026         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7027         if (term && !strcmp(term, "gnome-terminal")) {
7028                 /* In the gnome-terminal-emulator, the message from
7029                  * scrolling up one line when impossible followed by
7030                  * scrolling down one line causes corruption of the
7031                  * status line. This is fixed by calling wclear. */
7032                 use_scroll_status_wclear = TRUE;
7033                 use_scroll_redrawwin = FALSE;
7035         } else if (term && !strcmp(term, "xrvt-xpm")) {
7036                 /* No problems with full optimizations in xrvt-(unicode)
7037                  * and aterm. */
7038                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7040         } else {
7041                 /* When scrolling in (u)xterm the last line in the
7042                  * scrolling direction will update slowly. */
7043                 use_scroll_redrawwin = TRUE;
7044                 use_scroll_status_wclear = FALSE;
7045         }
7048 static int
7049 get_input(int prompt_position)
7051         struct view *view;
7052         int i, key, cursor_y, cursor_x;
7054         if (prompt_position)
7055                 input_mode = TRUE;
7057         while (TRUE) {
7058                 bool loading = FALSE;
7060                 foreach_view (view, i) {
7061                         update_view(view);
7062                         if (view_is_displayed(view) && view->has_scrolled &&
7063                             use_scroll_redrawwin)
7064                                 redrawwin(view->win);
7065                         view->has_scrolled = FALSE;
7066                         if (view->pipe)
7067                                 loading = TRUE;
7068                 }
7070                 /* Update the cursor position. */
7071                 if (prompt_position) {
7072                         getbegyx(status_win, cursor_y, cursor_x);
7073                         cursor_x = prompt_position;
7074                 } else {
7075                         view = display[current_view];
7076                         getbegyx(view->win, cursor_y, cursor_x);
7077                         cursor_x = view->width - 1;
7078                         cursor_y += view->lineno - view->offset;
7079                 }
7080                 setsyx(cursor_y, cursor_x);
7082                 /* Refresh, accept single keystroke of input */
7083                 doupdate();
7084                 nodelay(status_win, loading);
7085                 key = wgetch(status_win);
7087                 /* wgetch() with nodelay() enabled returns ERR when
7088                  * there's no input. */
7089                 if (key == ERR) {
7091                 } else if (key == KEY_RESIZE) {
7092                         int height, width;
7094                         getmaxyx(stdscr, height, width);
7096                         wresize(status_win, 1, width);
7097                         mvwin(status_win, height - 1, 0);
7098                         wnoutrefresh(status_win);
7099                         resize_display();
7100                         redraw_display(TRUE);
7102                 } else {
7103                         input_mode = FALSE;
7104                         return key;
7105                 }
7106         }
7109 static char *
7110 prompt_input(const char *prompt, input_handler handler, void *data)
7112         enum input_status status = INPUT_OK;
7113         static char buf[SIZEOF_STR];
7114         size_t pos = 0;
7116         buf[pos] = 0;
7118         while (status == INPUT_OK || status == INPUT_SKIP) {
7119                 int key;
7121                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7122                 wclrtoeol(status_win);
7124                 key = get_input(pos + 1);
7125                 switch (key) {
7126                 case KEY_RETURN:
7127                 case KEY_ENTER:
7128                 case '\n':
7129                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7130                         break;
7132                 case KEY_BACKSPACE:
7133                         if (pos > 0)
7134                                 buf[--pos] = 0;
7135                         else
7136                                 status = INPUT_CANCEL;
7137                         break;
7139                 case KEY_ESC:
7140                         status = INPUT_CANCEL;
7141                         break;
7143                 default:
7144                         if (pos >= sizeof(buf)) {
7145                                 report("Input string too long");
7146                                 return NULL;
7147                         }
7149                         status = handler(data, buf, key);
7150                         if (status == INPUT_OK)
7151                                 buf[pos++] = (char) key;
7152                 }
7153         }
7155         /* Clear the status window */
7156         status_empty = FALSE;
7157         report("");
7159         if (status == INPUT_CANCEL)
7160                 return NULL;
7162         buf[pos++] = 0;
7164         return buf;
7167 static enum input_status
7168 prompt_yesno_handler(void *data, char *buf, int c)
7170         if (c == 'y' || c == 'Y')
7171                 return INPUT_STOP;
7172         if (c == 'n' || c == 'N')
7173                 return INPUT_CANCEL;
7174         return INPUT_SKIP;
7177 static bool
7178 prompt_yesno(const char *prompt)
7180         char prompt2[SIZEOF_STR];
7182         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7183                 return FALSE;
7185         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7188 static enum input_status
7189 read_prompt_handler(void *data, char *buf, int c)
7191         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7194 static char *
7195 read_prompt(const char *prompt)
7197         return prompt_input(prompt, read_prompt_handler, NULL);
7200 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7202         enum input_status status = INPUT_OK;
7203         int size = 0;
7205         while (items[size].text)
7206                 size++;
7208         while (status == INPUT_OK) {
7209                 const struct menu_item *item = &items[*selected];
7210                 int key;
7211                 int i;
7213                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7214                           prompt, *selected + 1, size);
7215                 if (item->hotkey)
7216                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7217                 wprintw(status_win, "%s", item->text);
7218                 wclrtoeol(status_win);
7220                 key = get_input(COLS - 1);
7221                 switch (key) {
7222                 case KEY_RETURN:
7223                 case KEY_ENTER:
7224                 case '\n':
7225                         status = INPUT_STOP;
7226                         break;
7228                 case KEY_LEFT:
7229                 case KEY_UP:
7230                         *selected = *selected - 1;
7231                         if (*selected < 0)
7232                                 *selected = size - 1;
7233                         break;
7235                 case KEY_RIGHT:
7236                 case KEY_DOWN:
7237                         *selected = (*selected + 1) % size;
7238                         break;
7240                 case KEY_ESC:
7241                         status = INPUT_CANCEL;
7242                         break;
7244                 default:
7245                         for (i = 0; items[i].text; i++)
7246                                 if (items[i].hotkey == key) {
7247                                         *selected = i;
7248                                         status = INPUT_STOP;
7249                                         break;
7250                                 }
7251                 }
7252         }
7254         /* Clear the status window */
7255         status_empty = FALSE;
7256         report("");
7258         return status != INPUT_CANCEL;
7261 /*
7262  * Repository properties
7263  */
7265 static struct ref **refs = NULL;
7266 static size_t refs_size = 0;
7267 static struct ref *refs_head = NULL;
7269 static struct ref_list **ref_lists = NULL;
7270 static size_t ref_lists_size = 0;
7272 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7273 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7274 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7276 static int
7277 compare_refs(const void *ref1_, const void *ref2_)
7279         const struct ref *ref1 = *(const struct ref **)ref1_;
7280         const struct ref *ref2 = *(const struct ref **)ref2_;
7282         if (ref1->tag != ref2->tag)
7283                 return ref2->tag - ref1->tag;
7284         if (ref1->ltag != ref2->ltag)
7285                 return ref2->ltag - ref2->ltag;
7286         if (ref1->head != ref2->head)
7287                 return ref2->head - ref1->head;
7288         if (ref1->tracked != ref2->tracked)
7289                 return ref2->tracked - ref1->tracked;
7290         if (ref1->remote != ref2->remote)
7291                 return ref2->remote - ref1->remote;
7292         return strcmp(ref1->name, ref2->name);
7295 static void
7296 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7298         size_t i;
7300         for (i = 0; i < refs_size; i++)
7301                 if (!visitor(data, refs[i]))
7302                         break;
7305 static struct ref *
7306 get_ref_head()
7308         return refs_head;
7311 static struct ref_list *
7312 get_ref_list(const char *id)
7314         struct ref_list *list;
7315         size_t i;
7317         for (i = 0; i < ref_lists_size; i++)
7318                 if (!strcmp(id, ref_lists[i]->id))
7319                         return ref_lists[i];
7321         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7322                 return NULL;
7323         list = calloc(1, sizeof(*list));
7324         if (!list)
7325                 return NULL;
7327         for (i = 0; i < refs_size; i++) {
7328                 if (!strcmp(id, refs[i]->id) &&
7329                     realloc_refs_list(&list->refs, list->size, 1))
7330                         list->refs[list->size++] = refs[i];
7331         }
7333         if (!list->refs) {
7334                 free(list);
7335                 return NULL;
7336         }
7338         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7339         ref_lists[ref_lists_size++] = list;
7340         return list;
7343 static int
7344 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7346         struct ref *ref = NULL;
7347         bool tag = FALSE;
7348         bool ltag = FALSE;
7349         bool remote = FALSE;
7350         bool tracked = FALSE;
7351         bool head = FALSE;
7352         int from = 0, to = refs_size - 1;
7354         if (!prefixcmp(name, "refs/tags/")) {
7355                 if (!suffixcmp(name, namelen, "^{}")) {
7356                         namelen -= 3;
7357                         name[namelen] = 0;
7358                 } else {
7359                         ltag = TRUE;
7360                 }
7362                 tag = TRUE;
7363                 namelen -= STRING_SIZE("refs/tags/");
7364                 name    += STRING_SIZE("refs/tags/");
7366         } else if (!prefixcmp(name, "refs/remotes/")) {
7367                 remote = TRUE;
7368                 namelen -= STRING_SIZE("refs/remotes/");
7369                 name    += STRING_SIZE("refs/remotes/");
7370                 tracked  = !strcmp(opt_remote, name);
7372         } else if (!prefixcmp(name, "refs/heads/")) {
7373                 namelen -= STRING_SIZE("refs/heads/");
7374                 name    += STRING_SIZE("refs/heads/");
7375                 if (!strncmp(opt_head, name, namelen))
7376                         return OK;
7378         } else if (!strcmp(name, "HEAD")) {
7379                 head     = TRUE;
7380                 if (*opt_head) {
7381                         namelen  = strlen(opt_head);
7382                         name     = opt_head;
7383                 }
7384         }
7386         /* If we are reloading or it's an annotated tag, replace the
7387          * previous SHA1 with the resolved commit id; relies on the fact
7388          * git-ls-remote lists the commit id of an annotated tag right
7389          * before the commit id it points to. */
7390         while (from <= to) {
7391                 size_t pos = (to + from) / 2;
7392                 int cmp = strcmp(name, refs[pos]->name);
7394                 if (!cmp) {
7395                         ref = refs[pos];
7396                         break;
7397                 }
7399                 if (cmp < 0)
7400                         to = pos - 1;
7401                 else
7402                         from = pos + 1;
7403         }
7405         if (!ref) {
7406                 if (!realloc_refs(&refs, refs_size, 1))
7407                         return ERR;
7408                 ref = calloc(1, sizeof(*ref) + namelen);
7409                 if (!ref)
7410                         return ERR;
7411                 memmove(refs + from + 1, refs + from,
7412                         (refs_size - from) * sizeof(*refs));
7413                 refs[from] = ref;
7414                 strncpy(ref->name, name, namelen);
7415                 refs_size++;
7416         }
7418         ref->head = head;
7419         ref->tag = tag;
7420         ref->ltag = ltag;
7421         ref->remote = remote;
7422         ref->tracked = tracked;
7423         string_copy_rev(ref->id, id);
7425         if (head)
7426                 refs_head = ref;
7427         return OK;
7430 static int
7431 load_refs(void)
7433         const char *head_argv[] = {
7434                 "git", "symbolic-ref", "HEAD", NULL
7435         };
7436         static const char *ls_remote_argv[SIZEOF_ARG] = {
7437                 "git", "ls-remote", opt_git_dir, NULL
7438         };
7439         static bool init = FALSE;
7440         size_t i;
7442         if (!init) {
7443                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7444                         die("TIG_LS_REMOTE contains too many arguments");
7445                 init = TRUE;
7446         }
7448         if (!*opt_git_dir)
7449                 return OK;
7451         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7452             !prefixcmp(opt_head, "refs/heads/")) {
7453                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7455                 memmove(opt_head, offset, strlen(offset) + 1);
7456         }
7458         refs_head = NULL;
7459         for (i = 0; i < refs_size; i++)
7460                 refs[i]->id[0] = 0;
7462         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7463                 return ERR;
7465         /* Update the ref lists to reflect changes. */
7466         for (i = 0; i < ref_lists_size; i++) {
7467                 struct ref_list *list = ref_lists[i];
7468                 size_t old, new;
7470                 for (old = new = 0; old < list->size; old++)
7471                         if (!strcmp(list->id, list->refs[old]->id))
7472                                 list->refs[new++] = list->refs[old];
7473                 list->size = new;
7474         }
7476         return OK;
7479 static void
7480 set_remote_branch(const char *name, const char *value, size_t valuelen)
7482         if (!strcmp(name, ".remote")) {
7483                 string_ncopy(opt_remote, value, valuelen);
7485         } else if (*opt_remote && !strcmp(name, ".merge")) {
7486                 size_t from = strlen(opt_remote);
7488                 if (!prefixcmp(value, "refs/heads/"))
7489                         value += STRING_SIZE("refs/heads/");
7491                 if (!string_format_from(opt_remote, &from, "/%s", value))
7492                         opt_remote[0] = 0;
7493         }
7496 static void
7497 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7499         const char *argv[SIZEOF_ARG] = { name, "=" };
7500         int argc = 1 + (cmd == option_set_command);
7501         int error = ERR;
7503         if (!argv_from_string(argv, &argc, value))
7504                 config_msg = "Too many option arguments";
7505         else
7506                 error = cmd(argc, argv);
7508         if (error == ERR)
7509                 warn("Option 'tig.%s': %s", name, config_msg);
7512 static bool
7513 set_environment_variable(const char *name, const char *value)
7515         size_t len = strlen(name) + 1 + strlen(value) + 1;
7516         char *env = malloc(len);
7518         if (env &&
7519             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7520             putenv(env) == 0)
7521                 return TRUE;
7522         free(env);
7523         return FALSE;
7526 static void
7527 set_work_tree(const char *value)
7529         char cwd[SIZEOF_STR];
7531         if (!getcwd(cwd, sizeof(cwd)))
7532                 die("Failed to get cwd path: %s", strerror(errno));
7533         if (chdir(opt_git_dir) < 0)
7534                 die("Failed to chdir(%s): %s", strerror(errno));
7535         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7536                 die("Failed to get git path: %s", strerror(errno));
7537         if (chdir(cwd) < 0)
7538                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7539         if (chdir(value) < 0)
7540                 die("Failed to chdir(%s): %s", value, strerror(errno));
7541         if (!getcwd(cwd, sizeof(cwd)))
7542                 die("Failed to get cwd path: %s", strerror(errno));
7543         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7544                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7545         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7546                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7547         opt_is_inside_work_tree = TRUE;
7550 static int
7551 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7553         if (!strcmp(name, "i18n.commitencoding"))
7554                 string_ncopy(opt_encoding, value, valuelen);
7556         else if (!strcmp(name, "core.editor"))
7557                 string_ncopy(opt_editor, value, valuelen);
7559         else if (!strcmp(name, "core.worktree"))
7560                 set_work_tree(value);
7562         else if (!prefixcmp(name, "tig.color."))
7563                 set_repo_config_option(name + 10, value, option_color_command);
7565         else if (!prefixcmp(name, "tig.bind."))
7566                 set_repo_config_option(name + 9, value, option_bind_command);
7568         else if (!prefixcmp(name, "tig."))
7569                 set_repo_config_option(name + 4, value, option_set_command);
7571         else if (*opt_head && !prefixcmp(name, "branch.") &&
7572                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7573                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7575         return OK;
7578 static int
7579 load_git_config(void)
7581         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7583         return io_run_load(config_list_argv, "=", read_repo_config_option);
7586 static int
7587 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7589         if (!opt_git_dir[0]) {
7590                 string_ncopy(opt_git_dir, name, namelen);
7592         } else if (opt_is_inside_work_tree == -1) {
7593                 /* This can be 3 different values depending on the
7594                  * version of git being used. If git-rev-parse does not
7595                  * understand --is-inside-work-tree it will simply echo
7596                  * the option else either "true" or "false" is printed.
7597                  * Default to true for the unknown case. */
7598                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7600         } else if (*name == '.') {
7601                 string_ncopy(opt_cdup, name, namelen);
7603         } else {
7604                 string_ncopy(opt_prefix, name, namelen);
7605         }
7607         return OK;
7610 static int
7611 load_repo_info(void)
7613         const char *rev_parse_argv[] = {
7614                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7615                         "--show-cdup", "--show-prefix", NULL
7616         };
7618         return io_run_load(rev_parse_argv, "=", read_repo_info);
7622 /*
7623  * Main
7624  */
7626 static const char usage[] =
7627 "tig " TIG_VERSION " (" __DATE__ ")\n"
7628 "\n"
7629 "Usage: tig        [options] [revs] [--] [paths]\n"
7630 "   or: tig show   [options] [revs] [--] [paths]\n"
7631 "   or: tig blame  [rev] path\n"
7632 "   or: tig status\n"
7633 "   or: tig <      [git command output]\n"
7634 "\n"
7635 "Options:\n"
7636 "  -v, --version   Show version and exit\n"
7637 "  -h, --help      Show help message and exit";
7639 static void __NORETURN
7640 quit(int sig)
7642         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7643         if (cursed)
7644                 endwin();
7645         exit(0);
7648 static void __NORETURN
7649 die(const char *err, ...)
7651         va_list args;
7653         endwin();
7655         va_start(args, err);
7656         fputs("tig: ", stderr);
7657         vfprintf(stderr, err, args);
7658         fputs("\n", stderr);
7659         va_end(args);
7661         exit(1);
7664 static void
7665 warn(const char *msg, ...)
7667         va_list args;
7669         va_start(args, msg);
7670         fputs("tig warning: ", stderr);
7671         vfprintf(stderr, msg, args);
7672         fputs("\n", stderr);
7673         va_end(args);
7676 static const char ***filter_args;
7678 static int
7679 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7681         return argv_append(filter_args, name) ? OK : ERR;
7684 static void
7685 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7687         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7688         const char **all_argv = NULL;
7690         filter_args = args;
7691         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7692             !argv_append_array(&all_argv, argv) ||
7693             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7694                 die("Failed to split arguments");
7695         argv_free(all_argv);
7696         free(all_argv);
7699 static void
7700 filter_options(const char *argv[])
7702         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7703         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7704         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7707 static enum request
7708 parse_options(int argc, const char *argv[])
7710         enum request request = REQ_VIEW_MAIN;
7711         const char *subcommand;
7712         bool seen_dashdash = FALSE;
7713         const char **filter_argv = NULL;
7714         int i;
7716         if (!isatty(STDIN_FILENO)) {
7717                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7718                 return REQ_VIEW_PAGER;
7719         }
7721         if (argc <= 1)
7722                 return REQ_VIEW_MAIN;
7724         subcommand = argv[1];
7725         if (!strcmp(subcommand, "status")) {
7726                 if (argc > 2)
7727                         warn("ignoring arguments after `%s'", subcommand);
7728                 return REQ_VIEW_STATUS;
7730         } else if (!strcmp(subcommand, "blame")) {
7731                 if (argc <= 2 || argc > 4)
7732                         die("invalid number of options to blame\n\n%s", usage);
7734                 i = 2;
7735                 if (argc == 4) {
7736                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7737                         i++;
7738                 }
7740                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7741                 return REQ_VIEW_BLAME;
7743         } else if (!strcmp(subcommand, "show")) {
7744                 request = REQ_VIEW_DIFF;
7746         } else {
7747                 subcommand = NULL;
7748         }
7750         for (i = 1 + !!subcommand; i < argc; i++) {
7751                 const char *opt = argv[i];
7753                 if (seen_dashdash) {
7754                         argv_append(&opt_file_args, opt);
7755                         continue;
7757                 } else if (!strcmp(opt, "--")) {
7758                         seen_dashdash = TRUE;
7759                         continue;
7761                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7762                         printf("tig version %s\n", TIG_VERSION);
7763                         quit(0);
7765                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7766                         printf("%s\n", usage);
7767                         quit(0);
7769                 } else if (!strcmp(opt, "--all")) {
7770                         argv_append(&opt_rev_args, opt);
7771                         continue;
7772                 }
7774                 if (!argv_append(&filter_argv, opt))
7775                         die("command too long");
7776         }
7778         if (filter_argv)
7779                 filter_options(filter_argv);
7781         return request;
7784 int
7785 main(int argc, const char *argv[])
7787         const char *codeset = "UTF-8";
7788         enum request request = parse_options(argc, argv);
7789         struct view *view;
7790         size_t i;
7792         signal(SIGINT, quit);
7793         signal(SIGPIPE, SIG_IGN);
7795         if (setlocale(LC_ALL, "")) {
7796                 codeset = nl_langinfo(CODESET);
7797         }
7799         if (load_repo_info() == ERR)
7800                 die("Failed to load repo info.");
7802         if (load_options() == ERR)
7803                 die("Failed to load user config.");
7805         if (load_git_config() == ERR)
7806                 die("Failed to load repo config.");
7808         /* Require a git repository unless when running in pager mode. */
7809         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7810                 die("Not a git repository");
7812         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7813                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7814                 if (opt_iconv_in == ICONV_NONE)
7815                         die("Failed to initialize character set conversion");
7816         }
7818         if (codeset && strcmp(codeset, "UTF-8")) {
7819                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7820                 if (opt_iconv_out == ICONV_NONE)
7821                         die("Failed to initialize character set conversion");
7822         }
7824         if (load_refs() == ERR)
7825                 die("Failed to load refs.");
7827         foreach_view (view, i) {
7828                 if (getenv(view->cmd_env))
7829                         warn("Use of the %s environment variable is deprecated,"
7830                              " use options or TIG_DIFF_ARGS instead",
7831                              view->cmd_env);
7832                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7833                         die("Too many arguments in the `%s` environment variable",
7834                             view->cmd_env);
7835         }
7837         init_display();
7839         while (view_driver(display[current_view], request)) {
7840                 int key = get_input(0);
7842                 view = display[current_view];
7843                 request = get_keybinding(view->keymap, key);
7845                 /* Some low-level request handling. This keeps access to
7846                  * status_win restricted. */
7847                 switch (request) {
7848                 case REQ_NONE:
7849                         report("Unknown key, press %s for help",
7850                                get_key(view->keymap, REQ_VIEW_HELP));
7851                         break;
7852                 case REQ_PROMPT:
7853                 {
7854                         char *cmd = read_prompt(":");
7856                         if (cmd && isdigit(*cmd)) {
7857                                 int lineno = view->lineno + 1;
7859                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7860                                         select_view_line(view, lineno - 1);
7861                                         report("");
7862                                 } else {
7863                                         report("Unable to parse '%s' as a line number", cmd);
7864                                 }
7866                         } else if (cmd) {
7867                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7868                                 const char *argv[SIZEOF_ARG] = { "git" };
7869                                 int argc = 1;
7871                                 /* When running random commands, initially show the
7872                                  * command in the title. However, it maybe later be
7873                                  * overwritten if a commit line is selected. */
7874                                 string_ncopy(next->ref, cmd, strlen(cmd));
7876                                 if (!argv_from_string(argv, &argc, cmd)) {
7877                                         report("Too many arguments");
7878                                 } else if (!prepare_update(next, argv, NULL)) {
7879                                         report("Failed to format command");
7880                                 } else {
7881                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7882                                 }
7883                         }
7885                         request = REQ_NONE;
7886                         break;
7887                 }
7888                 case REQ_SEARCH:
7889                 case REQ_SEARCH_BACK:
7890                 {
7891                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7892                         char *search = read_prompt(prompt);
7894                         if (search)
7895                                 string_ncopy(opt_search, search, strlen(search));
7896                         else if (*opt_search)
7897                                 request = request == REQ_SEARCH ?
7898                                         REQ_FIND_NEXT :
7899                                         REQ_FIND_PREV;
7900                         else
7901                                 request = REQ_NONE;
7902                         break;
7903                 }
7904                 default:
7905                         break;
7906                 }
7907         }
7909         quit(0);
7911         return 0;