Code

io: set io->error when syscalls fail and remove calls to report and die
[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, ...);
71 static size_t utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size);
72 static inline unsigned char utf8_char_length(const char *string, const char *end);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
78 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x)  (sizeof(x) - 1)
81 #define SIZEOF_STR      1024    /* Default string size. */
82 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG      32      /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT   'I'
89 #define REVGRAPH_MERGE  'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND  '^'
94 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT   (-1)
99 #define ICONV_NONE      ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST     /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
106 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
109 #define ID_COLS         8
110 #define AUTHOR_COLS     19
112 #define MIN_VIEW_HEIGHT 4
114 #define NULL_ID         "0000000000000000000000000000000000000000"
116 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
118 /* Some ASCII-shorthands fitted into the ncurses namespace. */
119 #define KEY_TAB         '\t'
120 #define KEY_RETURN      '\r'
121 #define KEY_ESC         27
124 struct ref {
125         char id[SIZEOF_REV];    /* Commit SHA1 ID */
126         unsigned int head:1;    /* Is it the current HEAD? */
127         unsigned int tag:1;     /* Is it a tag? */
128         unsigned int ltag:1;    /* If so, is the tag local? */
129         unsigned int remote:1;  /* Is it a remote ref? */
130         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
131         char name[1];           /* Ref name; tag or head names are shortened. */
132 };
134 struct ref_list {
135         char id[SIZEOF_REV];    /* Commit SHA1 ID */
136         size_t size;            /* Number of refs. */
137         struct ref **refs;      /* References for this ID. */
138 };
140 static struct ref *get_ref_head();
141 static struct ref_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146         FORMAT_ALL,             /* Perform replacement in all arguments. */
147         FORMAT_DASH,            /* Perform replacement up until "--". */
148         FORMAT_NONE             /* No replacement should be performed. */
149 };
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154         INPUT_OK,
155         INPUT_SKIP,
156         INPUT_STOP,
157         INPUT_CANCEL
158 };
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166         int hotkey;
167         const char *text;
168         void *data;
169 };
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
173 /*
174  * Allocation helpers ... Entering macro hell to never be seen again.
175  */
177 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
178 static type *                                                                   \
179 name(type **mem, size_t size, size_t increase)                                  \
180 {                                                                               \
181         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
182         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183         type *tmp = *mem;                                                       \
184                                                                                 \
185         if (mem == NULL || num_chunks != num_chunks_new) {                      \
186                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187                 if (tmp)                                                        \
188                         *mem = tmp;                                             \
189         }                                                                       \
190                                                                                 \
191         return tmp;                                                             \
194 /*
195  * String helpers
196  */
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
201         if (srclen > dstlen - 1)
202                 srclen = dstlen - 1;
204         strncpy(dst, src, srclen);
205         dst[srclen] = 0;
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214         string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
225         size_t size, pos;
227         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228                 if (src[pos] == '\t') {
229                         size_t expanded = tabsize - (size % tabsize);
231                         if (expanded + size >= dstlen - 1)
232                                 expanded = dstlen - size - 1;
233                         memcpy(dst + size, "        ", expanded);
234                         size += expanded;
235                 } else {
236                         dst[size++] = src[pos];
237                 }
238         }
240         dst[size] = 0;
243 static char *
244 chomp_string(char *name)
246         int namelen;
248         while (isspace(*name))
249                 name++;
251         namelen = strlen(name) - 1;
252         while (namelen > 0 && isspace(name[namelen]))
253                 name[namelen--] = 0;
255         return name;
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
261         va_list args;
262         size_t pos = bufpos ? *bufpos : 0;
264         va_start(args, fmt);
265         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266         va_end(args);
268         if (bufpos)
269                 *bufpos = pos;
271         return pos >= bufsize ? FALSE : TRUE;
274 #define string_format(buf, fmt, args...) \
275         string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278         string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
283         size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287         /* Diff-Header == DIFF_HEADER */
288         for (i = 0; i < len; i++) {
289                 if (toupper(str1[i]) == toupper(str2[i]))
290                         continue;
292                 if (string_enum_sep(str1[i]) &&
293                     string_enum_sep(str2[i]))
294                         continue;
296                 return str1[i] - str2[i];
297         }
299         return 0;
302 #define enum_equals(entry, str, len) \
303         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
305 struct enum_map {
306         const char *name;
307         int namelen;
308         int value;
309 };
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
313 static char *
314 enum_map_name(const char *name, size_t namelen)
316         static char buf[SIZEOF_STR];
317         int bufpos;
319         for (bufpos = 0; bufpos <= namelen; bufpos++) {
320                 buf[bufpos] = tolower(name[bufpos]);
321                 if (buf[bufpos] == '_')
322                         buf[bufpos] = '-';
323         }
325         buf[bufpos] = 0;
326         return buf;
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
331 static bool
332 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
334         size_t namelen = strlen(name);
335         int i;
337         for (i = 0; i < map_size; i++)
338                 if (enum_equals(map[i], name, namelen)) {
339                         *value = map[i].value;
340                         return TRUE;
341                 }
343         return FALSE;
346 #define map_enum(attr, map, name) \
347         map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350         strncmp(str1, str2, STRING_SIZE(str2))
352 static inline int
353 suffixcmp(const char *str, int slen, const char *suffix)
355         size_t len = slen >= 0 ? slen : strlen(str);
356         size_t suffixlen = strlen(suffix);
358         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
362 #define DATE_INFO \
363         DATE_(NO), \
364         DATE_(DEFAULT), \
365         DATE_(RELATIVE), \
366         DATE_(SHORT)
368 enum date {
369 #define DATE_(name) DATE_##name
370         DATE_INFO
371 #undef  DATE_
372 };
374 static const struct enum_map date_map[] = {
375 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
376         DATE_INFO
377 #undef  DATE_
378 };
380 struct time {
381         time_t sec;
382         int tz;
383 };
385 static inline int timecmp(const struct time *t1, const struct time *t2)
387         return t1->sec - t2->sec;
390 static const char *
391 mkdate(const struct time *time, enum date date)
393         static char buf[DATE_COLS + 1];
394         static const struct enum_map reldate[] = {
395                 { "second", 1,                  60 * 2 },
396                 { "minute", 60,                 60 * 60 * 2 },
397                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
398                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
399                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
400                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
401         };
402         struct tm tm;
404         if (!date || !time || !time->sec)
405                 return "";
407         if (date == DATE_RELATIVE) {
408                 struct timeval now;
409                 time_t date = time->sec + time->tz;
410                 time_t seconds;
411                 int i;
413                 gettimeofday(&now, NULL);
414                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
415                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
416                         if (seconds >= reldate[i].value)
417                                 continue;
419                         seconds /= reldate[i].namelen;
420                         if (!string_format(buf, "%ld %s%s %s",
421                                            seconds, reldate[i].name,
422                                            seconds > 1 ? "s" : "",
423                                            now.tv_sec >= date ? "ago" : "ahead"))
424                                 break;
425                         return buf;
426                 }
427         }
429         gmtime_r(&time->sec, &tm);
430         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
434 #define AUTHOR_VALUES \
435         AUTHOR_(NO), \
436         AUTHOR_(FULL), \
437         AUTHOR_(ABBREVIATED)
439 enum author {
440 #define AUTHOR_(name) AUTHOR_##name
441         AUTHOR_VALUES,
442 #undef  AUTHOR_
443         AUTHOR_DEFAULT = AUTHOR_FULL
444 };
446 static const struct enum_map author_map[] = {
447 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
448         AUTHOR_VALUES
449 #undef  AUTHOR_
450 };
452 static const char *
453 get_author_initials(const char *author)
455         static char initials[AUTHOR_COLS * 6 + 1];
456         size_t pos = 0;
457         const char *end = strchr(author, '\0');
459 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
461         memset(initials, 0, sizeof(initials));
462         while (author < end) {
463                 unsigned char bytes;
464                 size_t i;
466                 while (is_initial_sep(*author))
467                         author++;
469                 bytes = utf8_char_length(author, end);
470                 if (bytes < sizeof(initials) - 1 - pos) {
471                         while (bytes--) {
472                                 initials[pos++] = *author++;
473                         }
474                 }
476                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
477                         if (i < sizeof(initials) - 1)
478                                 initials[i++] = *author;
479                 }
481                 initials[i++] = 0;
482         }
484         return initials;
488 static bool
489 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
491         int valuelen;
493         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
494                 bool advance = cmd[valuelen] != 0;
496                 cmd[valuelen] = 0;
497                 argv[(*argc)++] = chomp_string(cmd);
498                 cmd = chomp_string(cmd + valuelen + advance);
499         }
501         if (*argc < SIZEOF_ARG)
502                 argv[*argc] = NULL;
503         return *argc < SIZEOF_ARG;
506 static void
507 argv_from_env(const char **argv, const char *name)
509         char *env = argv ? getenv(name) : NULL;
510         int argc = 0;
512         if (env && *env)
513                 env = strdup(env);
514         if (env && !argv_from_string(argv, &argc, env))
515                 die("Too many arguments in the `%s` environment variable", name);
519 /*
520  * Executing external commands.
521  */
523 enum io_type {
524         IO_FD,                  /* File descriptor based IO. */
525         IO_BG,                  /* Execute command in the background. */
526         IO_FG,                  /* Execute command with same std{in,out,err}. */
527         IO_RD,                  /* Read only fork+exec IO. */
528         IO_WR,                  /* Write only fork+exec IO. */
529         IO_AP,                  /* Append fork+exec output to file. */
530 };
532 struct io {
533         enum io_type type;      /* The requested type of pipe. */
534         const char *dir;        /* Directory from which to execute. */
535         pid_t pid;              /* PID of spawned process. */
536         int pipe;               /* Pipe end for reading or writing. */
537         int error;              /* Error status. */
538         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
539         char *buf;              /* Read buffer. */
540         size_t bufalloc;        /* Allocated buffer size. */
541         size_t bufsize;         /* Buffer content size. */
542         char *bufpos;           /* Current buffer position. */
543         unsigned int eof:1;     /* Has end of file been reached. */
544 };
546 static void
547 io_reset(struct io *io)
549         io->pipe = -1;
550         io->pid = 0;
551         io->buf = io->bufpos = NULL;
552         io->bufalloc = io->bufsize = 0;
553         io->error = 0;
554         io->eof = 0;
557 static void
558 io_init(struct io *io, const char *dir, enum io_type type)
560         io_reset(io);
561         io->type = type;
562         io->dir = dir;
565 static bool
566 io_init_rd(struct io *io, const char *argv[], const char *dir,
567                 enum format_flags flags)
569         io_init(io, dir, IO_RD);
570         return format_argv(io->argv, argv, flags);
573 static bool
574 io_open(struct io *io, const char *fmt, ...)
576         char name[SIZEOF_STR] = "";
577         bool fits;
578         va_list args;
580         io_init(io, NULL, IO_FD);
582         va_start(args, fmt);
583         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
584         va_end(args);
586         if (!fits) {
587                 io->error = ENAMETOOLONG;
588                 return FALSE;
589         }
590         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
591         if (io->pipe == -1)
592                 io->error = errno;
593         return io->pipe != -1;
596 static bool
597 io_kill(struct io *io)
599         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
602 static bool
603 io_done(struct io *io)
605         pid_t pid = io->pid;
607         if (io->pipe != -1)
608                 close(io->pipe);
609         free(io->buf);
610         io_reset(io);
612         while (pid > 0) {
613                 int status;
614                 pid_t waiting = waitpid(pid, &status, 0);
616                 if (waiting < 0) {
617                         if (errno == EINTR)
618                                 continue;
619                         io->error = errno;
620                         return FALSE;
621                 }
623                 return waiting == pid &&
624                        !WIFSIGNALED(status) &&
625                        WIFEXITED(status) &&
626                        !WEXITSTATUS(status);
627         }
629         return TRUE;
632 static bool
633 io_start(struct io *io)
635         int pipefds[2] = { -1, -1 };
637         if (io->type == IO_FD)
638                 return TRUE;
640         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
641                 io->error = errno;
642                 return FALSE;
643         } else if (io->type == IO_AP) {
644                 pipefds[1] = io->pipe;
645         }
647         if ((io->pid = fork())) {
648                 if (io->pid == -1)
649                         io->error = errno;
650                 if (pipefds[!(io->type == IO_WR)] != -1)
651                         close(pipefds[!(io->type == IO_WR)]);
652                 if (io->pid != -1) {
653                         io->pipe = pipefds[!!(io->type == IO_WR)];
654                         return TRUE;
655                 }
657         } else {
658                 if (io->type != IO_FG) {
659                         int devnull = open("/dev/null", O_RDWR);
660                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
661                         int writefd = (io->type == IO_RD || io->type == IO_AP)
662                                                         ? pipefds[1] : devnull;
664                         dup2(readfd,  STDIN_FILENO);
665                         dup2(writefd, STDOUT_FILENO);
666                         dup2(devnull, STDERR_FILENO);
668                         close(devnull);
669                         if (pipefds[0] != -1)
670                                 close(pipefds[0]);
671                         if (pipefds[1] != -1)
672                                 close(pipefds[1]);
673                 }
675                 if (io->dir && *io->dir && chdir(io->dir) == -1)
676                         exit(errno);
678                 execvp(io->argv[0], (char *const*) io->argv);
679                 exit(errno);
680         }
682         if (pipefds[!!(io->type == IO_WR)] != -1)
683                 close(pipefds[!!(io->type == IO_WR)]);
684         return FALSE;
687 static bool
688 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
690         io_init(io, dir, type);
691         if (!format_argv(io->argv, argv, FORMAT_NONE))
692                 return FALSE;
693         return io_start(io);
696 static int
697 io_complete(struct io *io)
699         return io_start(io) && io_done(io);
702 static int
703 io_run_bg(const char **argv)
705         struct io io = {};
707         io_init(&io, NULL, IO_BG);
708         if (!format_argv(io.argv, argv, FORMAT_NONE))
709                 return FALSE;
710         return io_complete(&io);
713 static bool
714 io_run_fg(const char **argv, const char *dir)
716         struct io io = {};
718         io_init(&io, dir, IO_FG);
719         if (!format_argv(io.argv, argv, FORMAT_NONE))
720                 return FALSE;
721         return io_complete(&io);
724 static bool
725 io_run_append(const char **argv, enum format_flags flags, int fd)
727         struct io io = {};
729         io_init(&io, NULL, IO_AP);
730         io.pipe = fd;
731         if (format_argv(io.argv, argv, flags))
732                 return io_complete(&io);
733         close(fd);
734         return FALSE;
737 static bool
738 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
740         return io_init_rd(io, argv, dir, flags) && io_start(io);
743 static bool
744 io_eof(struct io *io)
746         return io->eof;
749 static int
750 io_error(struct io *io)
752         return io->error;
755 static char *
756 io_strerror(struct io *io)
758         return strerror(io->error);
761 static bool
762 io_can_read(struct io *io)
764         struct timeval tv = { 0, 500 };
765         fd_set fds;
767         FD_ZERO(&fds);
768         FD_SET(io->pipe, &fds);
770         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
773 static ssize_t
774 io_read(struct io *io, void *buf, size_t bufsize)
776         do {
777                 ssize_t readsize = read(io->pipe, buf, bufsize);
779                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
780                         continue;
781                 else if (readsize == -1)
782                         io->error = errno;
783                 else if (readsize == 0)
784                         io->eof = 1;
785                 return readsize;
786         } while (1);
789 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
791 static char *
792 io_get(struct io *io, int c, bool can_read)
794         char *eol;
795         ssize_t readsize;
797         while (TRUE) {
798                 if (io->bufsize > 0) {
799                         eol = memchr(io->bufpos, c, io->bufsize);
800                         if (eol) {
801                                 char *line = io->bufpos;
803                                 *eol = 0;
804                                 io->bufpos = eol + 1;
805                                 io->bufsize -= io->bufpos - line;
806                                 return line;
807                         }
808                 }
810                 if (io_eof(io)) {
811                         if (io->bufsize) {
812                                 io->bufpos[io->bufsize] = 0;
813                                 io->bufsize = 0;
814                                 return io->bufpos;
815                         }
816                         return NULL;
817                 }
819                 if (!can_read)
820                         return NULL;
822                 if (io->bufsize > 0 && io->bufpos > io->buf)
823                         memmove(io->buf, io->bufpos, io->bufsize);
825                 if (io->bufalloc == io->bufsize) {
826                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
827                                 return NULL;
828                         io->bufalloc += BUFSIZ;
829                 }
831                 io->bufpos = io->buf;
832                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
833                 if (io_error(io))
834                         return NULL;
835                 io->bufsize += readsize;
836         }
839 static bool
840 io_write(struct io *io, const void *buf, size_t bufsize)
842         size_t written = 0;
844         while (!io_error(io) && written < bufsize) {
845                 ssize_t size;
847                 size = write(io->pipe, buf + written, bufsize - written);
848                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
849                         continue;
850                 else if (size == -1)
851                         io->error = errno;
852                 else
853                         written += size;
854         }
856         return written == bufsize;
859 static bool
860 io_read_buf(struct io *io, char buf[], size_t bufsize)
862         char *result = io_get(io, '\n', TRUE);
864         if (result) {
865                 result = chomp_string(result);
866                 string_ncopy_do(buf, bufsize, result, strlen(result));
867         }
869         return io_done(io) && result;
872 static bool
873 io_run_buf(const char **argv, char buf[], size_t bufsize)
875         struct io io = {};
877         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
878             && io_read_buf(&io, buf, bufsize);
881 static int
882 io_load(struct io *io, const char *separators,
883         int (*read_property)(char *, size_t, char *, size_t))
885         char *name;
886         int state = OK;
888         if (!io_start(io))
889                 return ERR;
891         while (state == OK && (name = io_get(io, '\n', TRUE))) {
892                 char *value;
893                 size_t namelen;
894                 size_t valuelen;
896                 name = chomp_string(name);
897                 namelen = strcspn(name, separators);
899                 if (name[namelen]) {
900                         name[namelen] = 0;
901                         value = chomp_string(name + namelen + 1);
902                         valuelen = strlen(value);
904                 } else {
905                         value = "";
906                         valuelen = 0;
907                 }
909                 state = read_property(name, namelen, value, valuelen);
910         }
912         if (state != ERR && io_error(io))
913                 state = ERR;
914         io_done(io);
916         return state;
919 static int
920 io_run_load(const char **argv, const char *separators,
921             int (*read_property)(char *, size_t, char *, size_t))
923         struct io io = {};
925         return io_init_rd(&io, argv, NULL, FORMAT_NONE)
926                 ? io_load(&io, separators, read_property) : ERR;
930 /*
931  * User requests
932  */
934 #define REQ_INFO \
935         /* XXX: Keep the view request first and in sync with views[]. */ \
936         REQ_GROUP("View switching") \
937         REQ_(VIEW_MAIN,         "Show main view"), \
938         REQ_(VIEW_DIFF,         "Show diff view"), \
939         REQ_(VIEW_LOG,          "Show log view"), \
940         REQ_(VIEW_TREE,         "Show tree view"), \
941         REQ_(VIEW_BLOB,         "Show blob view"), \
942         REQ_(VIEW_BLAME,        "Show blame view"), \
943         REQ_(VIEW_BRANCH,       "Show branch view"), \
944         REQ_(VIEW_HELP,         "Show help page"), \
945         REQ_(VIEW_PAGER,        "Show pager view"), \
946         REQ_(VIEW_STATUS,       "Show status view"), \
947         REQ_(VIEW_STAGE,        "Show stage view"), \
948         \
949         REQ_GROUP("View manipulation") \
950         REQ_(ENTER,             "Enter current line and scroll"), \
951         REQ_(NEXT,              "Move to next"), \
952         REQ_(PREVIOUS,          "Move to previous"), \
953         REQ_(PARENT,            "Move to parent"), \
954         REQ_(VIEW_NEXT,         "Move focus to next view"), \
955         REQ_(REFRESH,           "Reload and refresh"), \
956         REQ_(MAXIMIZE,          "Maximize the current view"), \
957         REQ_(VIEW_CLOSE,        "Close the current view"), \
958         REQ_(QUIT,              "Close all views and quit"), \
959         \
960         REQ_GROUP("View specific requests") \
961         REQ_(STATUS_UPDATE,     "Update file status"), \
962         REQ_(STATUS_REVERT,     "Revert file changes"), \
963         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
964         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
965         \
966         REQ_GROUP("Cursor navigation") \
967         REQ_(MOVE_UP,           "Move cursor one line up"), \
968         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
969         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
970         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
971         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
972         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
973         \
974         REQ_GROUP("Scrolling") \
975         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
976         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
977         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
978         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
979         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
980         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
981         \
982         REQ_GROUP("Searching") \
983         REQ_(SEARCH,            "Search the view"), \
984         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
985         REQ_(FIND_NEXT,         "Find next search match"), \
986         REQ_(FIND_PREV,         "Find previous search match"), \
987         \
988         REQ_GROUP("Option manipulation") \
989         REQ_(OPTIONS,           "Open option menu"), \
990         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
991         REQ_(TOGGLE_DATE,       "Toggle date display"), \
992         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
993         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
994         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
995         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
996         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
997         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
998         \
999         REQ_GROUP("Misc") \
1000         REQ_(PROMPT,            "Bring up the prompt"), \
1001         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1002         REQ_(SHOW_VERSION,      "Show version information"), \
1003         REQ_(STOP_LOADING,      "Stop all loading views"), \
1004         REQ_(EDIT,              "Open in editor"), \
1005         REQ_(NONE,              "Do nothing")
1008 /* User action requests. */
1009 enum request {
1010 #define REQ_GROUP(help)
1011 #define REQ_(req, help) REQ_##req
1013         /* Offset all requests to avoid conflicts with ncurses getch values. */
1014         REQ_OFFSET = KEY_MAX + 1,
1015         REQ_INFO
1017 #undef  REQ_GROUP
1018 #undef  REQ_
1019 };
1021 struct request_info {
1022         enum request request;
1023         const char *name;
1024         int namelen;
1025         const char *help;
1026 };
1028 static const struct request_info req_info[] = {
1029 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1030 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1031         REQ_INFO
1032 #undef  REQ_GROUP
1033 #undef  REQ_
1034 };
1036 static enum request
1037 get_request(const char *name)
1039         int namelen = strlen(name);
1040         int i;
1042         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1043                 if (enum_equals(req_info[i], name, namelen))
1044                         return req_info[i].request;
1046         return REQ_NONE;
1050 /*
1051  * Options
1052  */
1054 /* Option and state variables. */
1055 static enum date opt_date               = DATE_DEFAULT;
1056 static enum author opt_author           = AUTHOR_DEFAULT;
1057 static bool opt_line_number             = FALSE;
1058 static bool opt_line_graphics           = TRUE;
1059 static bool opt_rev_graph               = FALSE;
1060 static bool opt_show_refs               = TRUE;
1061 static int opt_num_interval             = 5;
1062 static double opt_hscroll               = 0.50;
1063 static double opt_scale_split_view      = 2.0 / 3.0;
1064 static int opt_tab_size                 = 8;
1065 static int opt_author_cols              = AUTHOR_COLS;
1066 static char opt_path[SIZEOF_STR]        = "";
1067 static char opt_file[SIZEOF_STR]        = "";
1068 static char opt_ref[SIZEOF_REF]         = "";
1069 static char opt_head[SIZEOF_REF]        = "";
1070 static char opt_remote[SIZEOF_REF]      = "";
1071 static char opt_encoding[20]            = "UTF-8";
1072 static iconv_t opt_iconv_in             = ICONV_NONE;
1073 static iconv_t opt_iconv_out            = ICONV_NONE;
1074 static char opt_search[SIZEOF_STR]      = "";
1075 static char opt_cdup[SIZEOF_STR]        = "";
1076 static char opt_prefix[SIZEOF_STR]      = "";
1077 static char opt_git_dir[SIZEOF_STR]     = "";
1078 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1079 static char opt_editor[SIZEOF_STR]      = "";
1080 static FILE *opt_tty                    = NULL;
1082 #define is_initial_commit()     (!get_ref_head())
1083 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1086 /*
1087  * Line-oriented content detection.
1088  */
1090 #define LINE_INFO \
1091 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1092 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1093 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1094 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1095 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1096 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1097 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1098 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1099 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1100 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1101 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1102 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1103 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1104 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1105 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1106 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1107 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1108 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1109 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1110 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1111 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1112 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1113 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1114 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1115 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1116 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1117 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1118 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1119 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1120 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1121 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1122 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1123 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1124 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1125 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1126 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1127 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1128 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1129 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1130 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1131 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1132 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1133 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1134 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1135 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1136 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1137 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1138 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1139 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1140 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1141 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1142 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1143 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1144 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1145 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1146 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1147 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1149 enum line_type {
1150 #define LINE(type, line, fg, bg, attr) \
1151         LINE_##type
1152         LINE_INFO,
1153         LINE_NONE
1154 #undef  LINE
1155 };
1157 struct line_info {
1158         const char *name;       /* Option name. */
1159         int namelen;            /* Size of option name. */
1160         const char *line;       /* The start of line to match. */
1161         int linelen;            /* Size of string to match. */
1162         int fg, bg, attr;       /* Color and text attributes for the lines. */
1163 };
1165 static struct line_info line_info[] = {
1166 #define LINE(type, line, fg, bg, attr) \
1167         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1168         LINE_INFO
1169 #undef  LINE
1170 };
1172 static enum line_type
1173 get_line_type(const char *line)
1175         int linelen = strlen(line);
1176         enum line_type type;
1178         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1179                 /* Case insensitive search matches Signed-off-by lines better. */
1180                 if (linelen >= line_info[type].linelen &&
1181                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1182                         return type;
1184         return LINE_DEFAULT;
1187 static inline int
1188 get_line_attr(enum line_type type)
1190         assert(type < ARRAY_SIZE(line_info));
1191         return COLOR_PAIR(type) | line_info[type].attr;
1194 static struct line_info *
1195 get_line_info(const char *name)
1197         size_t namelen = strlen(name);
1198         enum line_type type;
1200         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1201                 if (enum_equals(line_info[type], name, namelen))
1202                         return &line_info[type];
1204         return NULL;
1207 static void
1208 init_colors(void)
1210         int default_bg = line_info[LINE_DEFAULT].bg;
1211         int default_fg = line_info[LINE_DEFAULT].fg;
1212         enum line_type type;
1214         start_color();
1216         if (assume_default_colors(default_fg, default_bg) == ERR) {
1217                 default_bg = COLOR_BLACK;
1218                 default_fg = COLOR_WHITE;
1219         }
1221         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1222                 struct line_info *info = &line_info[type];
1223                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1224                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1226                 init_pair(type, fg, bg);
1227         }
1230 struct line {
1231         enum line_type type;
1233         /* State flags */
1234         unsigned int selected:1;
1235         unsigned int dirty:1;
1236         unsigned int cleareol:1;
1237         unsigned int other:16;
1239         void *data;             /* User data */
1240 };
1243 /*
1244  * Keys
1245  */
1247 struct keybinding {
1248         int alias;
1249         enum request request;
1250 };
1252 static const struct keybinding default_keybindings[] = {
1253         /* View switching */
1254         { 'm',          REQ_VIEW_MAIN },
1255         { 'd',          REQ_VIEW_DIFF },
1256         { 'l',          REQ_VIEW_LOG },
1257         { 't',          REQ_VIEW_TREE },
1258         { 'f',          REQ_VIEW_BLOB },
1259         { 'B',          REQ_VIEW_BLAME },
1260         { 'H',          REQ_VIEW_BRANCH },
1261         { 'p',          REQ_VIEW_PAGER },
1262         { 'h',          REQ_VIEW_HELP },
1263         { 'S',          REQ_VIEW_STATUS },
1264         { 'c',          REQ_VIEW_STAGE },
1266         /* View manipulation */
1267         { 'q',          REQ_VIEW_CLOSE },
1268         { KEY_TAB,      REQ_VIEW_NEXT },
1269         { KEY_RETURN,   REQ_ENTER },
1270         { KEY_UP,       REQ_PREVIOUS },
1271         { KEY_DOWN,     REQ_NEXT },
1272         { 'R',          REQ_REFRESH },
1273         { KEY_F(5),     REQ_REFRESH },
1274         { 'O',          REQ_MAXIMIZE },
1276         /* Cursor navigation */
1277         { 'k',          REQ_MOVE_UP },
1278         { 'j',          REQ_MOVE_DOWN },
1279         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1280         { KEY_END,      REQ_MOVE_LAST_LINE },
1281         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1282         { ' ',          REQ_MOVE_PAGE_DOWN },
1283         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1284         { 'b',          REQ_MOVE_PAGE_UP },
1285         { '-',          REQ_MOVE_PAGE_UP },
1287         /* Scrolling */
1288         { KEY_LEFT,     REQ_SCROLL_LEFT },
1289         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1290         { KEY_IC,       REQ_SCROLL_LINE_UP },
1291         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1292         { 'w',          REQ_SCROLL_PAGE_UP },
1293         { 's',          REQ_SCROLL_PAGE_DOWN },
1295         /* Searching */
1296         { '/',          REQ_SEARCH },
1297         { '?',          REQ_SEARCH_BACK },
1298         { 'n',          REQ_FIND_NEXT },
1299         { 'N',          REQ_FIND_PREV },
1301         /* Misc */
1302         { 'Q',          REQ_QUIT },
1303         { 'z',          REQ_STOP_LOADING },
1304         { 'v',          REQ_SHOW_VERSION },
1305         { 'r',          REQ_SCREEN_REDRAW },
1306         { 'o',          REQ_OPTIONS },
1307         { '.',          REQ_TOGGLE_LINENO },
1308         { 'D',          REQ_TOGGLE_DATE },
1309         { 'A',          REQ_TOGGLE_AUTHOR },
1310         { 'g',          REQ_TOGGLE_REV_GRAPH },
1311         { 'F',          REQ_TOGGLE_REFS },
1312         { 'I',          REQ_TOGGLE_SORT_ORDER },
1313         { 'i',          REQ_TOGGLE_SORT_FIELD },
1314         { ':',          REQ_PROMPT },
1315         { 'u',          REQ_STATUS_UPDATE },
1316         { '!',          REQ_STATUS_REVERT },
1317         { 'M',          REQ_STATUS_MERGE },
1318         { '@',          REQ_STAGE_NEXT },
1319         { ',',          REQ_PARENT },
1320         { 'e',          REQ_EDIT },
1321 };
1323 #define KEYMAP_INFO \
1324         KEYMAP_(GENERIC), \
1325         KEYMAP_(MAIN), \
1326         KEYMAP_(DIFF), \
1327         KEYMAP_(LOG), \
1328         KEYMAP_(TREE), \
1329         KEYMAP_(BLOB), \
1330         KEYMAP_(BLAME), \
1331         KEYMAP_(BRANCH), \
1332         KEYMAP_(PAGER), \
1333         KEYMAP_(HELP), \
1334         KEYMAP_(STATUS), \
1335         KEYMAP_(STAGE)
1337 enum keymap {
1338 #define KEYMAP_(name) KEYMAP_##name
1339         KEYMAP_INFO
1340 #undef  KEYMAP_
1341 };
1343 static const struct enum_map keymap_table[] = {
1344 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1345         KEYMAP_INFO
1346 #undef  KEYMAP_
1347 };
1349 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1351 struct keybinding_table {
1352         struct keybinding *data;
1353         size_t size;
1354 };
1356 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1358 static void
1359 add_keybinding(enum keymap keymap, enum request request, int key)
1361         struct keybinding_table *table = &keybindings[keymap];
1363         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1364         if (!table->data)
1365                 die("Failed to allocate keybinding");
1366         table->data[table->size].alias = key;
1367         table->data[table->size++].request = request;
1370 /* Looks for a key binding first in the given map, then in the generic map, and
1371  * lastly in the default keybindings. */
1372 static enum request
1373 get_keybinding(enum keymap keymap, int key)
1375         size_t i;
1377         for (i = 0; i < keybindings[keymap].size; i++)
1378                 if (keybindings[keymap].data[i].alias == key)
1379                         return keybindings[keymap].data[i].request;
1381         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1382                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1383                         return keybindings[KEYMAP_GENERIC].data[i].request;
1385         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1386                 if (default_keybindings[i].alias == key)
1387                         return default_keybindings[i].request;
1389         return (enum request) key;
1393 struct key {
1394         const char *name;
1395         int value;
1396 };
1398 static const struct key key_table[] = {
1399         { "Enter",      KEY_RETURN },
1400         { "Space",      ' ' },
1401         { "Backspace",  KEY_BACKSPACE },
1402         { "Tab",        KEY_TAB },
1403         { "Escape",     KEY_ESC },
1404         { "Left",       KEY_LEFT },
1405         { "Right",      KEY_RIGHT },
1406         { "Up",         KEY_UP },
1407         { "Down",       KEY_DOWN },
1408         { "Insert",     KEY_IC },
1409         { "Delete",     KEY_DC },
1410         { "Hash",       '#' },
1411         { "Home",       KEY_HOME },
1412         { "End",        KEY_END },
1413         { "PageUp",     KEY_PPAGE },
1414         { "PageDown",   KEY_NPAGE },
1415         { "F1",         KEY_F(1) },
1416         { "F2",         KEY_F(2) },
1417         { "F3",         KEY_F(3) },
1418         { "F4",         KEY_F(4) },
1419         { "F5",         KEY_F(5) },
1420         { "F6",         KEY_F(6) },
1421         { "F7",         KEY_F(7) },
1422         { "F8",         KEY_F(8) },
1423         { "F9",         KEY_F(9) },
1424         { "F10",        KEY_F(10) },
1425         { "F11",        KEY_F(11) },
1426         { "F12",        KEY_F(12) },
1427 };
1429 static int
1430 get_key_value(const char *name)
1432         int i;
1434         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1435                 if (!strcasecmp(key_table[i].name, name))
1436                         return key_table[i].value;
1438         if (strlen(name) == 1 && isprint(*name))
1439                 return (int) *name;
1441         return ERR;
1444 static const char *
1445 get_key_name(int key_value)
1447         static char key_char[] = "'X'";
1448         const char *seq = NULL;
1449         int key;
1451         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1452                 if (key_table[key].value == key_value)
1453                         seq = key_table[key].name;
1455         if (seq == NULL &&
1456             key_value < 127 &&
1457             isprint(key_value)) {
1458                 key_char[1] = (char) key_value;
1459                 seq = key_char;
1460         }
1462         return seq ? seq : "(no key)";
1465 static bool
1466 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1468         const char *sep = *pos > 0 ? ", " : "";
1469         const char *keyname = get_key_name(keybinding->alias);
1471         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1474 static bool
1475 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1476                            enum keymap keymap, bool all)
1478         int i;
1480         for (i = 0; i < keybindings[keymap].size; i++) {
1481                 if (keybindings[keymap].data[i].request == request) {
1482                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1483                                 return FALSE;
1484                         if (!all)
1485                                 break;
1486                 }
1487         }
1489         return TRUE;
1492 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1494 static const char *
1495 get_keys(enum keymap keymap, enum request request, bool all)
1497         static char buf[BUFSIZ];
1498         size_t pos = 0;
1499         int i;
1501         buf[pos] = 0;
1503         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1504                 return "Too many keybindings!";
1505         if (pos > 0 && !all)
1506                 return buf;
1508         if (keymap != KEYMAP_GENERIC) {
1509                 /* Only the generic keymap includes the default keybindings when
1510                  * listing all keys. */
1511                 if (all)
1512                         return buf;
1514                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1515                         return "Too many keybindings!";
1516                 if (pos)
1517                         return buf;
1518         }
1520         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1521                 if (default_keybindings[i].request == request) {
1522                         if (!append_key(buf, &pos, &default_keybindings[i]))
1523                                 return "Too many keybindings!";
1524                         if (!all)
1525                                 return buf;
1526                 }
1527         }
1529         return buf;
1532 struct run_request {
1533         enum keymap keymap;
1534         int key;
1535         const char *argv[SIZEOF_ARG];
1536 };
1538 static struct run_request *run_request;
1539 static size_t run_requests;
1541 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1543 static enum request
1544 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1546         struct run_request *req;
1548         if (argc >= ARRAY_SIZE(req->argv) - 1)
1549                 return REQ_NONE;
1551         if (!realloc_run_requests(&run_request, run_requests, 1))
1552                 return REQ_NONE;
1554         req = &run_request[run_requests];
1555         req->keymap = keymap;
1556         req->key = key;
1557         req->argv[0] = NULL;
1559         if (!format_argv(req->argv, argv, FORMAT_NONE))
1560                 return REQ_NONE;
1562         return REQ_NONE + ++run_requests;
1565 static struct run_request *
1566 get_run_request(enum request request)
1568         if (request <= REQ_NONE)
1569                 return NULL;
1570         return &run_request[request - REQ_NONE - 1];
1573 static void
1574 add_builtin_run_requests(void)
1576         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1577         const char *commit[] = { "git", "commit", NULL };
1578         const char *gc[] = { "git", "gc", NULL };
1579         struct {
1580                 enum keymap keymap;
1581                 int key;
1582                 int argc;
1583                 const char **argv;
1584         } reqs[] = {
1585                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1586                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1587                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1588         };
1589         int i;
1591         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1592                 enum request req;
1594                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1595                 if (req != REQ_NONE)
1596                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1597         }
1600 /*
1601  * User config file handling.
1602  */
1604 static int   config_lineno;
1605 static bool  config_errors;
1606 static const char *config_msg;
1608 static const struct enum_map color_map[] = {
1609 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1610         COLOR_MAP(DEFAULT),
1611         COLOR_MAP(BLACK),
1612         COLOR_MAP(BLUE),
1613         COLOR_MAP(CYAN),
1614         COLOR_MAP(GREEN),
1615         COLOR_MAP(MAGENTA),
1616         COLOR_MAP(RED),
1617         COLOR_MAP(WHITE),
1618         COLOR_MAP(YELLOW),
1619 };
1621 static const struct enum_map attr_map[] = {
1622 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1623         ATTR_MAP(NORMAL),
1624         ATTR_MAP(BLINK),
1625         ATTR_MAP(BOLD),
1626         ATTR_MAP(DIM),
1627         ATTR_MAP(REVERSE),
1628         ATTR_MAP(STANDOUT),
1629         ATTR_MAP(UNDERLINE),
1630 };
1632 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1634 static int parse_step(double *opt, const char *arg)
1636         *opt = atoi(arg);
1637         if (!strchr(arg, '%'))
1638                 return OK;
1640         /* "Shift down" so 100% and 1 does not conflict. */
1641         *opt = (*opt - 1) / 100;
1642         if (*opt >= 1.0) {
1643                 *opt = 0.99;
1644                 config_msg = "Step value larger than 100%";
1645                 return ERR;
1646         }
1647         if (*opt < 0.0) {
1648                 *opt = 1;
1649                 config_msg = "Invalid step value";
1650                 return ERR;
1651         }
1652         return OK;
1655 static int
1656 parse_int(int *opt, const char *arg, int min, int max)
1658         int value = atoi(arg);
1660         if (min <= value && value <= max) {
1661                 *opt = value;
1662                 return OK;
1663         }
1665         config_msg = "Integer value out of bound";
1666         return ERR;
1669 static bool
1670 set_color(int *color, const char *name)
1672         if (map_enum(color, color_map, name))
1673                 return TRUE;
1674         if (!prefixcmp(name, "color"))
1675                 return parse_int(color, name + 5, 0, 255) == OK;
1676         return FALSE;
1679 /* Wants: object fgcolor bgcolor [attribute] */
1680 static int
1681 option_color_command(int argc, const char *argv[])
1683         struct line_info *info;
1685         if (argc < 3) {
1686                 config_msg = "Wrong number of arguments given to color command";
1687                 return ERR;
1688         }
1690         info = get_line_info(argv[0]);
1691         if (!info) {
1692                 static const struct enum_map obsolete[] = {
1693                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1694                         ENUM_MAP("main-date",   LINE_DATE),
1695                         ENUM_MAP("main-author", LINE_AUTHOR),
1696                 };
1697                 int index;
1699                 if (!map_enum(&index, obsolete, argv[0])) {
1700                         config_msg = "Unknown color name";
1701                         return ERR;
1702                 }
1703                 info = &line_info[index];
1704         }
1706         if (!set_color(&info->fg, argv[1]) ||
1707             !set_color(&info->bg, argv[2])) {
1708                 config_msg = "Unknown color";
1709                 return ERR;
1710         }
1712         info->attr = 0;
1713         while (argc-- > 3) {
1714                 int attr;
1716                 if (!set_attribute(&attr, argv[argc])) {
1717                         config_msg = "Unknown attribute";
1718                         return ERR;
1719                 }
1720                 info->attr |= attr;
1721         }
1723         return OK;
1726 static int parse_bool(bool *opt, const char *arg)
1728         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1729                 ? TRUE : FALSE;
1730         return OK;
1733 static int parse_enum_do(unsigned int *opt, const char *arg,
1734                          const struct enum_map *map, size_t map_size)
1736         bool is_true;
1738         assert(map_size > 1);
1740         if (map_enum_do(map, map_size, (int *) opt, arg))
1741                 return OK;
1743         if (parse_bool(&is_true, arg) != OK)
1744                 return ERR;
1746         *opt = is_true ? map[1].value : map[0].value;
1747         return OK;
1750 #define parse_enum(opt, arg, map) \
1751         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1753 static int
1754 parse_string(char *opt, const char *arg, size_t optsize)
1756         int arglen = strlen(arg);
1758         switch (arg[0]) {
1759         case '\"':
1760         case '\'':
1761                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1762                         config_msg = "Unmatched quotation";
1763                         return ERR;
1764                 }
1765                 arg += 1; arglen -= 2;
1766         default:
1767                 string_ncopy_do(opt, optsize, arg, arglen);
1768                 return OK;
1769         }
1772 /* Wants: name = value */
1773 static int
1774 option_set_command(int argc, const char *argv[])
1776         if (argc != 3) {
1777                 config_msg = "Wrong number of arguments given to set command";
1778                 return ERR;
1779         }
1781         if (strcmp(argv[1], "=")) {
1782                 config_msg = "No value assigned";
1783                 return ERR;
1784         }
1786         if (!strcmp(argv[0], "show-author"))
1787                 return parse_enum(&opt_author, argv[2], author_map);
1789         if (!strcmp(argv[0], "show-date"))
1790                 return parse_enum(&opt_date, argv[2], date_map);
1792         if (!strcmp(argv[0], "show-rev-graph"))
1793                 return parse_bool(&opt_rev_graph, argv[2]);
1795         if (!strcmp(argv[0], "show-refs"))
1796                 return parse_bool(&opt_show_refs, argv[2]);
1798         if (!strcmp(argv[0], "show-line-numbers"))
1799                 return parse_bool(&opt_line_number, argv[2]);
1801         if (!strcmp(argv[0], "line-graphics"))
1802                 return parse_bool(&opt_line_graphics, argv[2]);
1804         if (!strcmp(argv[0], "line-number-interval"))
1805                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1807         if (!strcmp(argv[0], "author-width"))
1808                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1810         if (!strcmp(argv[0], "horizontal-scroll"))
1811                 return parse_step(&opt_hscroll, argv[2]);
1813         if (!strcmp(argv[0], "split-view-height"))
1814                 return parse_step(&opt_scale_split_view, argv[2]);
1816         if (!strcmp(argv[0], "tab-size"))
1817                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1819         if (!strcmp(argv[0], "commit-encoding"))
1820                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1822         config_msg = "Unknown variable name";
1823         return ERR;
1826 /* Wants: mode request key */
1827 static int
1828 option_bind_command(int argc, const char *argv[])
1830         enum request request;
1831         int keymap = -1;
1832         int key;
1834         if (argc < 3) {
1835                 config_msg = "Wrong number of arguments given to bind command";
1836                 return ERR;
1837         }
1839         if (set_keymap(&keymap, argv[0]) == ERR) {
1840                 config_msg = "Unknown key map";
1841                 return ERR;
1842         }
1844         key = get_key_value(argv[1]);
1845         if (key == ERR) {
1846                 config_msg = "Unknown key";
1847                 return ERR;
1848         }
1850         request = get_request(argv[2]);
1851         if (request == REQ_NONE) {
1852                 static const struct enum_map obsolete[] = {
1853                         ENUM_MAP("cherry-pick",         REQ_NONE),
1854                         ENUM_MAP("screen-resize",       REQ_NONE),
1855                         ENUM_MAP("tree-parent",         REQ_PARENT),
1856                 };
1857                 int alias;
1859                 if (map_enum(&alias, obsolete, argv[2])) {
1860                         if (alias != REQ_NONE)
1861                                 add_keybinding(keymap, alias, key);
1862                         config_msg = "Obsolete request name";
1863                         return ERR;
1864                 }
1865         }
1866         if (request == REQ_NONE && *argv[2]++ == '!')
1867                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1868         if (request == REQ_NONE) {
1869                 config_msg = "Unknown request name";
1870                 return ERR;
1871         }
1873         add_keybinding(keymap, request, key);
1875         return OK;
1878 static int
1879 set_option(const char *opt, char *value)
1881         const char *argv[SIZEOF_ARG];
1882         int argc = 0;
1884         if (!argv_from_string(argv, &argc, value)) {
1885                 config_msg = "Too many option arguments";
1886                 return ERR;
1887         }
1889         if (!strcmp(opt, "color"))
1890                 return option_color_command(argc, argv);
1892         if (!strcmp(opt, "set"))
1893                 return option_set_command(argc, argv);
1895         if (!strcmp(opt, "bind"))
1896                 return option_bind_command(argc, argv);
1898         config_msg = "Unknown option command";
1899         return ERR;
1902 static int
1903 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1905         int status = OK;
1907         config_lineno++;
1908         config_msg = "Internal error";
1910         /* Check for comment markers, since read_properties() will
1911          * only ensure opt and value are split at first " \t". */
1912         optlen = strcspn(opt, "#");
1913         if (optlen == 0)
1914                 return OK;
1916         if (opt[optlen] != 0) {
1917                 config_msg = "No option value";
1918                 status = ERR;
1920         }  else {
1921                 /* Look for comment endings in the value. */
1922                 size_t len = strcspn(value, "#");
1924                 if (len < valuelen) {
1925                         valuelen = len;
1926                         value[valuelen] = 0;
1927                 }
1929                 status = set_option(opt, value);
1930         }
1932         if (status == ERR) {
1933                 warn("Error on line %d, near '%.*s': %s",
1934                      config_lineno, (int) optlen, opt, config_msg);
1935                 config_errors = TRUE;
1936         }
1938         /* Always keep going if errors are encountered. */
1939         return OK;
1942 static void
1943 load_option_file(const char *path)
1945         struct io io = {};
1947         /* It's OK that the file doesn't exist. */
1948         if (!io_open(&io, "%s", path))
1949                 return;
1951         config_lineno = 0;
1952         config_errors = FALSE;
1954         if (io_load(&io, " \t", read_option) == ERR ||
1955             config_errors == TRUE)
1956                 warn("Errors while loading %s.", path);
1959 static int
1960 load_options(void)
1962         const char *home = getenv("HOME");
1963         const char *tigrc_user = getenv("TIGRC_USER");
1964         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1965         char buf[SIZEOF_STR];
1967         add_builtin_run_requests();
1969         if (!tigrc_system)
1970                 tigrc_system = SYSCONFDIR "/tigrc";
1971         load_option_file(tigrc_system);
1973         if (!tigrc_user) {
1974                 if (!home || !string_format(buf, "%s/.tigrc", home))
1975                         return ERR;
1976                 tigrc_user = buf;
1977         }
1978         load_option_file(tigrc_user);
1980         return OK;
1984 /*
1985  * The viewer
1986  */
1988 struct view;
1989 struct view_ops;
1991 /* The display array of active views and the index of the current view. */
1992 static struct view *display[2];
1993 static unsigned int current_view;
1995 #define foreach_displayed_view(view, i) \
1996         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1998 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2000 /* Current head and commit ID */
2001 static char ref_blob[SIZEOF_REF]        = "";
2002 static char ref_commit[SIZEOF_REF]      = "HEAD";
2003 static char ref_head[SIZEOF_REF]        = "HEAD";
2005 struct view {
2006         const char *name;       /* View name */
2007         const char *cmd_env;    /* Command line set via environment */
2008         const char *id;         /* Points to either of ref_{head,commit,blob} */
2010         struct view_ops *ops;   /* View operations */
2012         enum keymap keymap;     /* What keymap does this view have */
2013         bool git_dir;           /* Whether the view requires a git directory. */
2015         char ref[SIZEOF_REF];   /* Hovered commit reference */
2016         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2018         int height, width;      /* The width and height of the main window */
2019         WINDOW *win;            /* The main window */
2020         WINDOW *title;          /* The title window living below the main window */
2022         /* Navigation */
2023         unsigned long offset;   /* Offset of the window top */
2024         unsigned long yoffset;  /* Offset from the window side. */
2025         unsigned long lineno;   /* Current line number */
2026         unsigned long p_offset; /* Previous offset of the window top */
2027         unsigned long p_yoffset;/* Previous offset from the window side */
2028         unsigned long p_lineno; /* Previous current line number */
2029         bool p_restore;         /* Should the previous position be restored. */
2031         /* Searching */
2032         char grep[SIZEOF_STR];  /* Search string */
2033         regex_t *regex;         /* Pre-compiled regexp */
2035         /* If non-NULL, points to the view that opened this view. If this view
2036          * is closed tig will switch back to the parent view. */
2037         struct view *parent;
2039         /* Buffering */
2040         size_t lines;           /* Total number of lines */
2041         struct line *line;      /* Line index */
2042         unsigned int digits;    /* Number of digits in the lines member. */
2044         /* Drawing */
2045         struct line *curline;   /* Line currently being drawn. */
2046         enum line_type curtype; /* Attribute currently used for drawing. */
2047         unsigned long col;      /* Column when drawing. */
2048         bool has_scrolled;      /* View was scrolled. */
2050         /* Loading */
2051         struct io io;
2052         struct io *pipe;
2053         time_t start_time;
2054         time_t update_secs;
2055 };
2057 struct view_ops {
2058         /* What type of content being displayed. Used in the title bar. */
2059         const char *type;
2060         /* Default command arguments. */
2061         const char **argv;
2062         /* Open and reads in all view content. */
2063         bool (*open)(struct view *view);
2064         /* Read one line; updates view->line. */
2065         bool (*read)(struct view *view, char *data);
2066         /* Draw one line; @lineno must be < view->height. */
2067         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2068         /* Depending on view handle a special requests. */
2069         enum request (*request)(struct view *view, enum request request, struct line *line);
2070         /* Search for regexp in a line. */
2071         bool (*grep)(struct view *view, struct line *line);
2072         /* Select line */
2073         void (*select)(struct view *view, struct line *line);
2074         /* Prepare view for loading */
2075         bool (*prepare)(struct view *view);
2076 };
2078 static struct view_ops blame_ops;
2079 static struct view_ops blob_ops;
2080 static struct view_ops diff_ops;
2081 static struct view_ops help_ops;
2082 static struct view_ops log_ops;
2083 static struct view_ops main_ops;
2084 static struct view_ops pager_ops;
2085 static struct view_ops stage_ops;
2086 static struct view_ops status_ops;
2087 static struct view_ops tree_ops;
2088 static struct view_ops branch_ops;
2090 #define VIEW_STR(name, env, ref, ops, map, git) \
2091         { name, #env, ref, ops, map, git }
2093 #define VIEW_(id, name, ops, git, ref) \
2094         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2097 static struct view views[] = {
2098         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2099         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2100         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2101         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2102         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2103         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2104         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2105         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2106         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2107         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2108         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2109 };
2111 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2112 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2114 #define foreach_view(view, i) \
2115         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2117 #define view_is_displayed(view) \
2118         (view == display[0] || view == display[1])
2121 static inline void
2122 set_view_attr(struct view *view, enum line_type type)
2124         if (!view->curline->selected && view->curtype != type) {
2125                 (void) wattrset(view->win, get_line_attr(type));
2126                 wchgat(view->win, -1, 0, type, NULL);
2127                 view->curtype = type;
2128         }
2131 static int
2132 draw_chars(struct view *view, enum line_type type, const char *string,
2133            int max_len, bool use_tilde)
2135         static char out_buffer[BUFSIZ * 2];
2136         int len = 0;
2137         int col = 0;
2138         int trimmed = FALSE;
2139         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2141         if (max_len <= 0)
2142                 return 0;
2144         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2146         set_view_attr(view, type);
2147         if (len > 0) {
2148                 if (opt_iconv_out != ICONV_NONE) {
2149                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2150                         size_t inlen = len + 1;
2152                         char *outbuf = out_buffer;
2153                         size_t outlen = sizeof(out_buffer);
2155                         size_t ret;
2157                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2158                         if (ret != (size_t) -1) {
2159                                 string = out_buffer;
2160                                 len = sizeof(out_buffer) - outlen;
2161                         }
2162                 }
2164                 waddnstr(view->win, string, len);
2165         }
2166         if (trimmed && use_tilde) {
2167                 set_view_attr(view, LINE_DELIMITER);
2168                 waddch(view->win, '~');
2169                 col++;
2170         }
2172         return col;
2175 static int
2176 draw_space(struct view *view, enum line_type type, int max, int spaces)
2178         static char space[] = "                    ";
2179         int col = 0;
2181         spaces = MIN(max, spaces);
2183         while (spaces > 0) {
2184                 int len = MIN(spaces, sizeof(space) - 1);
2186                 col += draw_chars(view, type, space, len, FALSE);
2187                 spaces -= len;
2188         }
2190         return col;
2193 static bool
2194 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2196         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2197         return view->width + view->yoffset <= view->col;
2200 static bool
2201 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2203         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2204         int max = view->width + view->yoffset - view->col;
2205         int i;
2207         if (max < size)
2208                 size = max;
2210         set_view_attr(view, type);
2211         /* Using waddch() instead of waddnstr() ensures that
2212          * they'll be rendered correctly for the cursor line. */
2213         for (i = skip; i < size; i++)
2214                 waddch(view->win, graphic[i]);
2216         view->col += size;
2217         if (size < max && skip <= size)
2218                 waddch(view->win, ' ');
2219         view->col++;
2221         return view->width + view->yoffset <= view->col;
2224 static bool
2225 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2227         int max = MIN(view->width + view->yoffset - view->col, len);
2228         int col;
2230         if (text)
2231                 col = draw_chars(view, type, text, max - 1, trim);
2232         else
2233                 col = draw_space(view, type, max - 1, max - 1);
2235         view->col += col;
2236         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2237         return view->width + view->yoffset <= view->col;
2240 static bool
2241 draw_date(struct view *view, struct time *time)
2243         const char *date = mkdate(time, opt_date);
2244         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2246         return draw_field(view, LINE_DATE, date, cols, FALSE);
2249 static bool
2250 draw_author(struct view *view, const char *author)
2252         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2253         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2255         if (abbreviate && author)
2256                 author = get_author_initials(author);
2258         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2261 static bool
2262 draw_mode(struct view *view, mode_t mode)
2264         const char *str;
2266         if (S_ISDIR(mode))
2267                 str = "drwxr-xr-x";
2268         else if (S_ISLNK(mode))
2269                 str = "lrwxrwxrwx";
2270         else if (S_ISGITLINK(mode))
2271                 str = "m---------";
2272         else if (S_ISREG(mode) && mode & S_IXUSR)
2273                 str = "-rwxr-xr-x";
2274         else if (S_ISREG(mode))
2275                 str = "-rw-r--r--";
2276         else
2277                 str = "----------";
2279         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2282 static bool
2283 draw_lineno(struct view *view, unsigned int lineno)
2285         char number[10];
2286         int digits3 = view->digits < 3 ? 3 : view->digits;
2287         int max = MIN(view->width + view->yoffset - view->col, digits3);
2288         char *text = NULL;
2289         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2291         lineno += view->offset + 1;
2292         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2293                 static char fmt[] = "%1ld";
2295                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2296                 if (string_format(number, fmt, lineno))
2297                         text = number;
2298         }
2299         if (text)
2300                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2301         else
2302                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2303         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2306 static bool
2307 draw_view_line(struct view *view, unsigned int lineno)
2309         struct line *line;
2310         bool selected = (view->offset + lineno == view->lineno);
2312         assert(view_is_displayed(view));
2314         if (view->offset + lineno >= view->lines)
2315                 return FALSE;
2317         line = &view->line[view->offset + lineno];
2319         wmove(view->win, lineno, 0);
2320         if (line->cleareol)
2321                 wclrtoeol(view->win);
2322         view->col = 0;
2323         view->curline = line;
2324         view->curtype = LINE_NONE;
2325         line->selected = FALSE;
2326         line->dirty = line->cleareol = 0;
2328         if (selected) {
2329                 set_view_attr(view, LINE_CURSOR);
2330                 line->selected = TRUE;
2331                 view->ops->select(view, line);
2332         }
2334         return view->ops->draw(view, line, lineno);
2337 static void
2338 redraw_view_dirty(struct view *view)
2340         bool dirty = FALSE;
2341         int lineno;
2343         for (lineno = 0; lineno < view->height; lineno++) {
2344                 if (view->offset + lineno >= view->lines)
2345                         break;
2346                 if (!view->line[view->offset + lineno].dirty)
2347                         continue;
2348                 dirty = TRUE;
2349                 if (!draw_view_line(view, lineno))
2350                         break;
2351         }
2353         if (!dirty)
2354                 return;
2355         wnoutrefresh(view->win);
2358 static void
2359 redraw_view_from(struct view *view, int lineno)
2361         assert(0 <= lineno && lineno < view->height);
2363         for (; lineno < view->height; lineno++) {
2364                 if (!draw_view_line(view, lineno))
2365                         break;
2366         }
2368         wnoutrefresh(view->win);
2371 static void
2372 redraw_view(struct view *view)
2374         werase(view->win);
2375         redraw_view_from(view, 0);
2379 static void
2380 update_view_title(struct view *view)
2382         char buf[SIZEOF_STR];
2383         char state[SIZEOF_STR];
2384         size_t bufpos = 0, statelen = 0;
2386         assert(view_is_displayed(view));
2388         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2389                 unsigned int view_lines = view->offset + view->height;
2390                 unsigned int lines = view->lines
2391                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2392                                    : 0;
2394                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2395                                    view->ops->type,
2396                                    view->lineno + 1,
2397                                    view->lines,
2398                                    lines);
2400         }
2402         if (view->pipe) {
2403                 time_t secs = time(NULL) - view->start_time;
2405                 /* Three git seconds are a long time ... */
2406                 if (secs > 2)
2407                         string_format_from(state, &statelen, " loading %lds", secs);
2408         }
2410         string_format_from(buf, &bufpos, "[%s]", view->name);
2411         if (*view->ref && bufpos < view->width) {
2412                 size_t refsize = strlen(view->ref);
2413                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2415                 if (minsize < view->width)
2416                         refsize = view->width - minsize + 7;
2417                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2418         }
2420         if (statelen && bufpos < view->width) {
2421                 string_format_from(buf, &bufpos, "%s", state);
2422         }
2424         if (view == display[current_view])
2425                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2426         else
2427                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2429         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2430         wclrtoeol(view->title);
2431         wnoutrefresh(view->title);
2434 static int
2435 apply_step(double step, int value)
2437         if (step >= 1)
2438                 return (int) step;
2439         value *= step + 0.01;
2440         return value ? value : 1;
2443 static void
2444 resize_display(void)
2446         int offset, i;
2447         struct view *base = display[0];
2448         struct view *view = display[1] ? display[1] : display[0];
2450         /* Setup window dimensions */
2452         getmaxyx(stdscr, base->height, base->width);
2454         /* Make room for the status window. */
2455         base->height -= 1;
2457         if (view != base) {
2458                 /* Horizontal split. */
2459                 view->width   = base->width;
2460                 view->height  = apply_step(opt_scale_split_view, base->height);
2461                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2462                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2463                 base->height -= view->height;
2465                 /* Make room for the title bar. */
2466                 view->height -= 1;
2467         }
2469         /* Make room for the title bar. */
2470         base->height -= 1;
2472         offset = 0;
2474         foreach_displayed_view (view, i) {
2475                 if (!view->win) {
2476                         view->win = newwin(view->height, 0, offset, 0);
2477                         if (!view->win)
2478                                 die("Failed to create %s view", view->name);
2480                         scrollok(view->win, FALSE);
2482                         view->title = newwin(1, 0, offset + view->height, 0);
2483                         if (!view->title)
2484                                 die("Failed to create title window");
2486                 } else {
2487                         wresize(view->win, view->height, view->width);
2488                         mvwin(view->win,   offset, 0);
2489                         mvwin(view->title, offset + view->height, 0);
2490                 }
2492                 offset += view->height + 1;
2493         }
2496 static void
2497 redraw_display(bool clear)
2499         struct view *view;
2500         int i;
2502         foreach_displayed_view (view, i) {
2503                 if (clear)
2504                         wclear(view->win);
2505                 redraw_view(view);
2506                 update_view_title(view);
2507         }
2510 static void
2511 toggle_enum_option_do(unsigned int *opt, const char *help,
2512                       const struct enum_map *map, size_t size)
2514         *opt = (*opt + 1) % size;
2515         redraw_display(FALSE);
2516         report("Displaying %s %s", enum_name(map[*opt]), help);
2519 #define toggle_enum_option(opt, help, map) \
2520         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2522 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2523 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2525 static void
2526 toggle_view_option(bool *option, const char *help)
2528         *option = !*option;
2529         redraw_display(FALSE);
2530         report("%sabling %s", *option ? "En" : "Dis", help);
2533 static void
2534 open_option_menu(void)
2536         const struct menu_item menu[] = {
2537                 { '.', "line numbers", &opt_line_number },
2538                 { 'D', "date display", &opt_date },
2539                 { 'A', "author display", &opt_author },
2540                 { 'g', "revision graph display", &opt_rev_graph },
2541                 { 'F', "reference display", &opt_show_refs },
2542                 { 0 }
2543         };
2544         int selected = 0;
2546         if (prompt_menu("Toggle option", menu, &selected)) {
2547                 if (menu[selected].data == &opt_date)
2548                         toggle_date();
2549                 else if (menu[selected].data == &opt_author)
2550                         toggle_author();
2551                 else
2552                         toggle_view_option(menu[selected].data, menu[selected].text);
2553         }
2556 static void
2557 maximize_view(struct view *view)
2559         memset(display, 0, sizeof(display));
2560         current_view = 0;
2561         display[current_view] = view;
2562         resize_display();
2563         redraw_display(FALSE);
2564         report("");
2568 /*
2569  * Navigation
2570  */
2572 static bool
2573 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2575         if (lineno >= view->lines)
2576                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2578         if (offset > lineno || offset + view->height <= lineno) {
2579                 unsigned long half = view->height / 2;
2581                 if (lineno > half)
2582                         offset = lineno - half;
2583                 else
2584                         offset = 0;
2585         }
2587         if (offset != view->offset || lineno != view->lineno) {
2588                 view->offset = offset;
2589                 view->lineno = lineno;
2590                 return TRUE;
2591         }
2593         return FALSE;
2596 /* Scrolling backend */
2597 static void
2598 do_scroll_view(struct view *view, int lines)
2600         bool redraw_current_line = FALSE;
2602         /* The rendering expects the new offset. */
2603         view->offset += lines;
2605         assert(0 <= view->offset && view->offset < view->lines);
2606         assert(lines);
2608         /* Move current line into the view. */
2609         if (view->lineno < view->offset) {
2610                 view->lineno = view->offset;
2611                 redraw_current_line = TRUE;
2612         } else if (view->lineno >= view->offset + view->height) {
2613                 view->lineno = view->offset + view->height - 1;
2614                 redraw_current_line = TRUE;
2615         }
2617         assert(view->offset <= view->lineno && view->lineno < view->lines);
2619         /* Redraw the whole screen if scrolling is pointless. */
2620         if (view->height < ABS(lines)) {
2621                 redraw_view(view);
2623         } else {
2624                 int line = lines > 0 ? view->height - lines : 0;
2625                 int end = line + ABS(lines);
2627                 scrollok(view->win, TRUE);
2628                 wscrl(view->win, lines);
2629                 scrollok(view->win, FALSE);
2631                 while (line < end && draw_view_line(view, line))
2632                         line++;
2634                 if (redraw_current_line)
2635                         draw_view_line(view, view->lineno - view->offset);
2636                 wnoutrefresh(view->win);
2637         }
2639         view->has_scrolled = TRUE;
2640         report("");
2643 /* Scroll frontend */
2644 static void
2645 scroll_view(struct view *view, enum request request)
2647         int lines = 1;
2649         assert(view_is_displayed(view));
2651         switch (request) {
2652         case REQ_SCROLL_LEFT:
2653                 if (view->yoffset == 0) {
2654                         report("Cannot scroll beyond the first column");
2655                         return;
2656                 }
2657                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2658                         view->yoffset = 0;
2659                 else
2660                         view->yoffset -= apply_step(opt_hscroll, view->width);
2661                 redraw_view_from(view, 0);
2662                 report("");
2663                 return;
2664         case REQ_SCROLL_RIGHT:
2665                 view->yoffset += apply_step(opt_hscroll, view->width);
2666                 redraw_view(view);
2667                 report("");
2668                 return;
2669         case REQ_SCROLL_PAGE_DOWN:
2670                 lines = view->height;
2671         case REQ_SCROLL_LINE_DOWN:
2672                 if (view->offset + lines > view->lines)
2673                         lines = view->lines - view->offset;
2675                 if (lines == 0 || view->offset + view->height >= view->lines) {
2676                         report("Cannot scroll beyond the last line");
2677                         return;
2678                 }
2679                 break;
2681         case REQ_SCROLL_PAGE_UP:
2682                 lines = view->height;
2683         case REQ_SCROLL_LINE_UP:
2684                 if (lines > view->offset)
2685                         lines = view->offset;
2687                 if (lines == 0) {
2688                         report("Cannot scroll beyond the first line");
2689                         return;
2690                 }
2692                 lines = -lines;
2693                 break;
2695         default:
2696                 die("request %d not handled in switch", request);
2697         }
2699         do_scroll_view(view, lines);
2702 /* Cursor moving */
2703 static void
2704 move_view(struct view *view, enum request request)
2706         int scroll_steps = 0;
2707         int steps;
2709         switch (request) {
2710         case REQ_MOVE_FIRST_LINE:
2711                 steps = -view->lineno;
2712                 break;
2714         case REQ_MOVE_LAST_LINE:
2715                 steps = view->lines - view->lineno - 1;
2716                 break;
2718         case REQ_MOVE_PAGE_UP:
2719                 steps = view->height > view->lineno
2720                       ? -view->lineno : -view->height;
2721                 break;
2723         case REQ_MOVE_PAGE_DOWN:
2724                 steps = view->lineno + view->height >= view->lines
2725                       ? view->lines - view->lineno - 1 : view->height;
2726                 break;
2728         case REQ_MOVE_UP:
2729                 steps = -1;
2730                 break;
2732         case REQ_MOVE_DOWN:
2733                 steps = 1;
2734                 break;
2736         default:
2737                 die("request %d not handled in switch", request);
2738         }
2740         if (steps <= 0 && view->lineno == 0) {
2741                 report("Cannot move beyond the first line");
2742                 return;
2744         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2745                 report("Cannot move beyond the last line");
2746                 return;
2747         }
2749         /* Move the current line */
2750         view->lineno += steps;
2751         assert(0 <= view->lineno && view->lineno < view->lines);
2753         /* Check whether the view needs to be scrolled */
2754         if (view->lineno < view->offset ||
2755             view->lineno >= view->offset + view->height) {
2756                 scroll_steps = steps;
2757                 if (steps < 0 && -steps > view->offset) {
2758                         scroll_steps = -view->offset;
2760                 } else if (steps > 0) {
2761                         if (view->lineno == view->lines - 1 &&
2762                             view->lines > view->height) {
2763                                 scroll_steps = view->lines - view->offset - 1;
2764                                 if (scroll_steps >= view->height)
2765                                         scroll_steps -= view->height - 1;
2766                         }
2767                 }
2768         }
2770         if (!view_is_displayed(view)) {
2771                 view->offset += scroll_steps;
2772                 assert(0 <= view->offset && view->offset < view->lines);
2773                 view->ops->select(view, &view->line[view->lineno]);
2774                 return;
2775         }
2777         /* Repaint the old "current" line if we be scrolling */
2778         if (ABS(steps) < view->height)
2779                 draw_view_line(view, view->lineno - steps - view->offset);
2781         if (scroll_steps) {
2782                 do_scroll_view(view, scroll_steps);
2783                 return;
2784         }
2786         /* Draw the current line */
2787         draw_view_line(view, view->lineno - view->offset);
2789         wnoutrefresh(view->win);
2790         report("");
2794 /*
2795  * Searching
2796  */
2798 static void search_view(struct view *view, enum request request);
2800 static bool
2801 grep_text(struct view *view, const char *text[])
2803         regmatch_t pmatch;
2804         size_t i;
2806         for (i = 0; text[i]; i++)
2807                 if (*text[i] &&
2808                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2809                         return TRUE;
2810         return FALSE;
2813 static void
2814 select_view_line(struct view *view, unsigned long lineno)
2816         unsigned long old_lineno = view->lineno;
2817         unsigned long old_offset = view->offset;
2819         if (goto_view_line(view, view->offset, lineno)) {
2820                 if (view_is_displayed(view)) {
2821                         if (old_offset != view->offset) {
2822                                 redraw_view(view);
2823                         } else {
2824                                 draw_view_line(view, old_lineno - view->offset);
2825                                 draw_view_line(view, view->lineno - view->offset);
2826                                 wnoutrefresh(view->win);
2827                         }
2828                 } else {
2829                         view->ops->select(view, &view->line[view->lineno]);
2830                 }
2831         }
2834 static void
2835 find_next(struct view *view, enum request request)
2837         unsigned long lineno = view->lineno;
2838         int direction;
2840         if (!*view->grep) {
2841                 if (!*opt_search)
2842                         report("No previous search");
2843                 else
2844                         search_view(view, request);
2845                 return;
2846         }
2848         switch (request) {
2849         case REQ_SEARCH:
2850         case REQ_FIND_NEXT:
2851                 direction = 1;
2852                 break;
2854         case REQ_SEARCH_BACK:
2855         case REQ_FIND_PREV:
2856                 direction = -1;
2857                 break;
2859         default:
2860                 return;
2861         }
2863         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2864                 lineno += direction;
2866         /* Note, lineno is unsigned long so will wrap around in which case it
2867          * will become bigger than view->lines. */
2868         for (; lineno < view->lines; lineno += direction) {
2869                 if (view->ops->grep(view, &view->line[lineno])) {
2870                         select_view_line(view, lineno);
2871                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2872                         return;
2873                 }
2874         }
2876         report("No match found for '%s'", view->grep);
2879 static void
2880 search_view(struct view *view, enum request request)
2882         int regex_err;
2884         if (view->regex) {
2885                 regfree(view->regex);
2886                 *view->grep = 0;
2887         } else {
2888                 view->regex = calloc(1, sizeof(*view->regex));
2889                 if (!view->regex)
2890                         return;
2891         }
2893         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2894         if (regex_err != 0) {
2895                 char buf[SIZEOF_STR] = "unknown error";
2897                 regerror(regex_err, view->regex, buf, sizeof(buf));
2898                 report("Search failed: %s", buf);
2899                 return;
2900         }
2902         string_copy(view->grep, opt_search);
2904         find_next(view, request);
2907 /*
2908  * Incremental updating
2909  */
2911 static void
2912 reset_view(struct view *view)
2914         int i;
2916         for (i = 0; i < view->lines; i++)
2917                 free(view->line[i].data);
2918         free(view->line);
2920         view->p_offset = view->offset;
2921         view->p_yoffset = view->yoffset;
2922         view->p_lineno = view->lineno;
2924         view->line = NULL;
2925         view->offset = 0;
2926         view->yoffset = 0;
2927         view->lines  = 0;
2928         view->lineno = 0;
2929         view->vid[0] = 0;
2930         view->update_secs = 0;
2933 static void
2934 free_argv(const char *argv[])
2936         int argc;
2938         for (argc = 0; argv[argc]; argc++)
2939                 free((void *) argv[argc]);
2942 static const char *
2943 format_arg(const char *name)
2945         static struct {
2946                 const char *name;
2947                 size_t namelen;
2948                 const char *value;
2949                 const char *value_if_empty;
2950         } vars[] = {
2951 #define FORMAT_VAR(name, value, value_if_empty) \
2952         { name, STRING_SIZE(name), value, value_if_empty }
2953                 FORMAT_VAR("%(directory)",      opt_path,       ""),
2954                 FORMAT_VAR("%(file)",           opt_file,       ""),
2955                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
2956                 FORMAT_VAR("%(head)",           ref_head,       ""),
2957                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
2958                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
2959         };
2960         int i;
2962         for (i = 0; i < ARRAY_SIZE(vars); i++)
2963                 if (!strncmp(name, vars[i].name, vars[i].namelen))
2964                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2966         return NULL;
2968 static bool
2969 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2971         char buf[SIZEOF_STR];
2972         int argc;
2973         bool noreplace = flags == FORMAT_NONE;
2975         free_argv(dst_argv);
2977         for (argc = 0; src_argv[argc]; argc++) {
2978                 const char *arg = src_argv[argc];
2979                 size_t bufpos = 0;
2981                 while (arg) {
2982                         char *next = strstr(arg, "%(");
2983                         int len = next - arg;
2984                         const char *value;
2986                         if (!next || noreplace) {
2987                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2988                                         noreplace = TRUE;
2989                                 len = strlen(arg);
2990                                 value = "";
2992                         } else {
2993                                 value = format_arg(next);
2995                                 if (!value) {
2996                                         report("Unknown replacement: `%s`", next);
2997                                         return FALSE;
2998                                 }
2999                         }
3001                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3002                                 return FALSE;
3004                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3005                 }
3007                 dst_argv[argc] = strdup(buf);
3008                 if (!dst_argv[argc])
3009                         break;
3010         }
3012         dst_argv[argc] = NULL;
3014         return src_argv[argc] == NULL;
3017 static bool
3018 restore_view_position(struct view *view)
3020         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3021                 return FALSE;
3023         /* Changing the view position cancels the restoring. */
3024         /* FIXME: Changing back to the first line is not detected. */
3025         if (view->offset != 0 || view->lineno != 0) {
3026                 view->p_restore = FALSE;
3027                 return FALSE;
3028         }
3030         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3031             view_is_displayed(view))
3032                 werase(view->win);
3034         view->yoffset = view->p_yoffset;
3035         view->p_restore = FALSE;
3037         return TRUE;
3040 static void
3041 end_update(struct view *view, bool force)
3043         if (!view->pipe)
3044                 return;
3045         while (!view->ops->read(view, NULL))
3046                 if (!force)
3047                         return;
3048         if (force)
3049                 io_kill(view->pipe);
3050         io_done(view->pipe);
3051         view->pipe = NULL;
3054 static void
3055 setup_update(struct view *view, const char *vid)
3057         reset_view(view);
3058         string_copy_rev(view->vid, vid);
3059         view->pipe = &view->io;
3060         view->start_time = time(NULL);
3063 static bool
3064 prepare_update(struct view *view, const char *argv[], const char *dir,
3065                enum format_flags flags)
3067         if (view->pipe)
3068                 end_update(view, TRUE);
3069         return io_init_rd(&view->io, argv, dir, flags);
3072 static bool
3073 prepare_update_file(struct view *view, const char *name)
3075         if (view->pipe)
3076                 end_update(view, TRUE);
3077         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3080 static bool
3081 begin_update(struct view *view, bool refresh)
3083         if (view->pipe)
3084                 end_update(view, TRUE);
3086         if (!refresh) {
3087                 if (view->ops->prepare) {
3088                         if (!view->ops->prepare(view))
3089                                 return FALSE;
3090                 } else if (!io_init_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3091                         return FALSE;
3092                 }
3094                 /* Put the current ref_* value to the view title ref
3095                  * member. This is needed by the blob view. Most other
3096                  * views sets it automatically after loading because the
3097                  * first line is a commit line. */
3098                 string_copy_rev(view->ref, view->id);
3099         }
3101         if (!io_start(&view->io))
3102                 return FALSE;
3104         setup_update(view, view->id);
3106         return TRUE;
3109 static bool
3110 update_view(struct view *view)
3112         char out_buffer[BUFSIZ * 2];
3113         char *line;
3114         /* Clear the view and redraw everything since the tree sorting
3115          * might have rearranged things. */
3116         bool redraw = view->lines == 0;
3117         bool can_read = TRUE;
3119         if (!view->pipe)
3120                 return TRUE;
3122         if (!io_can_read(view->pipe)) {
3123                 if (view->lines == 0 && view_is_displayed(view)) {
3124                         time_t secs = time(NULL) - view->start_time;
3126                         if (secs > 1 && secs > view->update_secs) {
3127                                 if (view->update_secs == 0)
3128                                         redraw_view(view);
3129                                 update_view_title(view);
3130                                 view->update_secs = secs;
3131                         }
3132                 }
3133                 return TRUE;
3134         }
3136         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3137                 if (opt_iconv_in != ICONV_NONE) {
3138                         ICONV_CONST char *inbuf = line;
3139                         size_t inlen = strlen(line) + 1;
3141                         char *outbuf = out_buffer;
3142                         size_t outlen = sizeof(out_buffer);
3144                         size_t ret;
3146                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3147                         if (ret != (size_t) -1)
3148                                 line = out_buffer;
3149                 }
3151                 if (!view->ops->read(view, line)) {
3152                         report("Allocation failure");
3153                         end_update(view, TRUE);
3154                         return FALSE;
3155                 }
3156         }
3158         {
3159                 unsigned long lines = view->lines;
3160                 int digits;
3162                 for (digits = 0; lines; digits++)
3163                         lines /= 10;
3165                 /* Keep the displayed view in sync with line number scaling. */
3166                 if (digits != view->digits) {
3167                         view->digits = digits;
3168                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3169                                 redraw = TRUE;
3170                 }
3171         }
3173         if (io_error(view->pipe)) {
3174                 report("Failed to read: %s", io_strerror(view->pipe));
3175                 end_update(view, TRUE);
3177         } else if (io_eof(view->pipe)) {
3178                 report("");
3179                 end_update(view, FALSE);
3180         }
3182         if (restore_view_position(view))
3183                 redraw = TRUE;
3185         if (!view_is_displayed(view))
3186                 return TRUE;
3188         if (redraw)
3189                 redraw_view_from(view, 0);
3190         else
3191                 redraw_view_dirty(view);
3193         /* Update the title _after_ the redraw so that if the redraw picks up a
3194          * commit reference in view->ref it'll be available here. */
3195         update_view_title(view);
3196         return TRUE;
3199 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3201 static struct line *
3202 add_line_data(struct view *view, void *data, enum line_type type)
3204         struct line *line;
3206         if (!realloc_lines(&view->line, view->lines, 1))
3207                 return NULL;
3209         line = &view->line[view->lines++];
3210         memset(line, 0, sizeof(*line));
3211         line->type = type;
3212         line->data = data;
3213         line->dirty = 1;
3215         return line;
3218 static struct line *
3219 add_line_text(struct view *view, const char *text, enum line_type type)
3221         char *data = text ? strdup(text) : NULL;
3223         return data ? add_line_data(view, data, type) : NULL;
3226 static struct line *
3227 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3229         char buf[SIZEOF_STR];
3230         va_list args;
3232         va_start(args, fmt);
3233         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3234                 buf[0] = 0;
3235         va_end(args);
3237         return buf[0] ? add_line_text(view, buf, type) : NULL;
3240 /*
3241  * View opening
3242  */
3244 enum open_flags {
3245         OPEN_DEFAULT = 0,       /* Use default view switching. */
3246         OPEN_SPLIT = 1,         /* Split current view. */
3247         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3248         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3249         OPEN_PREPARED = 32,     /* Open already prepared command. */
3250 };
3252 static void
3253 open_view(struct view *prev, enum request request, enum open_flags flags)
3255         bool split = !!(flags & OPEN_SPLIT);
3256         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3257         bool nomaximize = !!(flags & OPEN_REFRESH);
3258         struct view *view = VIEW(request);
3259         int nviews = displayed_views();
3260         struct view *base_view = display[0];
3262         if (view == prev && nviews == 1 && !reload) {
3263                 report("Already in %s view", view->name);
3264                 return;
3265         }
3267         if (view->git_dir && !opt_git_dir[0]) {
3268                 report("The %s view is disabled in pager view", view->name);
3269                 return;
3270         }
3272         if (split) {
3273                 display[1] = view;
3274                 current_view = 1;
3275         } else if (!nomaximize) {
3276                 /* Maximize the current view. */
3277                 memset(display, 0, sizeof(display));
3278                 current_view = 0;
3279                 display[current_view] = view;
3280         }
3282         /* No parent signals that this is the first loaded view. */
3283         if (prev && view != prev) {
3284                 view->parent = prev;
3285         }
3287         /* Resize the view when switching between split- and full-screen,
3288          * or when switching between two different full-screen views. */
3289         if (nviews != displayed_views() ||
3290             (nviews == 1 && base_view != display[0]))
3291                 resize_display();
3293         if (view->ops->open) {
3294                 if (view->pipe)
3295                         end_update(view, TRUE);
3296                 if (!view->ops->open(view)) {
3297                         report("Failed to load %s view", view->name);
3298                         return;
3299                 }
3300                 restore_view_position(view);
3302         } else if ((reload || strcmp(view->vid, view->id)) &&
3303                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3304                 report("Failed to load %s view", view->name);
3305                 return;
3306         }
3308         if (split && prev->lineno - prev->offset >= prev->height) {
3309                 /* Take the title line into account. */
3310                 int lines = prev->lineno - prev->offset - prev->height + 1;
3312                 /* Scroll the view that was split if the current line is
3313                  * outside the new limited view. */
3314                 do_scroll_view(prev, lines);
3315         }
3317         if (prev && view != prev && split && view_is_displayed(prev)) {
3318                 /* "Blur" the previous view. */
3319                 update_view_title(prev);
3320         }
3322         if (view->pipe && view->lines == 0) {
3323                 /* Clear the old view and let the incremental updating refill
3324                  * the screen. */
3325                 werase(view->win);
3326                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3327                 report("");
3328         } else if (view_is_displayed(view)) {
3329                 redraw_view(view);
3330                 report("");
3331         }
3334 static void
3335 open_external_viewer(const char *argv[], const char *dir)
3337         def_prog_mode();           /* save current tty modes */
3338         endwin();                  /* restore original tty modes */
3339         io_run_fg(argv, dir);
3340         fprintf(stderr, "Press Enter to continue");
3341         getc(opt_tty);
3342         reset_prog_mode();
3343         redraw_display(TRUE);
3346 static void
3347 open_mergetool(const char *file)
3349         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3351         open_external_viewer(mergetool_argv, opt_cdup);
3354 static void
3355 open_editor(const char *file)
3357         const char *editor_argv[] = { "vi", file, NULL };
3358         const char *editor;
3360         editor = getenv("GIT_EDITOR");
3361         if (!editor && *opt_editor)
3362                 editor = opt_editor;
3363         if (!editor)
3364                 editor = getenv("VISUAL");
3365         if (!editor)
3366                 editor = getenv("EDITOR");
3367         if (!editor)
3368                 editor = "vi";
3370         editor_argv[0] = editor;
3371         open_external_viewer(editor_argv, opt_cdup);
3374 static void
3375 open_run_request(enum request request)
3377         struct run_request *req = get_run_request(request);
3378         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3380         if (!req) {
3381                 report("Unknown run request");
3382                 return;
3383         }
3385         if (format_argv(argv, req->argv, FORMAT_ALL))
3386                 open_external_viewer(argv, NULL);
3387         free_argv(argv);
3390 /*
3391  * User request switch noodle
3392  */
3394 static int
3395 view_driver(struct view *view, enum request request)
3397         int i;
3399         if (request == REQ_NONE)
3400                 return TRUE;
3402         if (request > REQ_NONE) {
3403                 open_run_request(request);
3404                 /* FIXME: When all views can refresh always do this. */
3405                 if (view == VIEW(REQ_VIEW_STATUS) ||
3406                     view == VIEW(REQ_VIEW_MAIN) ||
3407                     view == VIEW(REQ_VIEW_LOG) ||
3408                     view == VIEW(REQ_VIEW_BRANCH) ||
3409                     view == VIEW(REQ_VIEW_STAGE))
3410                         request = REQ_REFRESH;
3411                 else
3412                         return TRUE;
3413         }
3415         if (view && view->lines) {
3416                 request = view->ops->request(view, request, &view->line[view->lineno]);
3417                 if (request == REQ_NONE)
3418                         return TRUE;
3419         }
3421         switch (request) {
3422         case REQ_MOVE_UP:
3423         case REQ_MOVE_DOWN:
3424         case REQ_MOVE_PAGE_UP:
3425         case REQ_MOVE_PAGE_DOWN:
3426         case REQ_MOVE_FIRST_LINE:
3427         case REQ_MOVE_LAST_LINE:
3428                 move_view(view, request);
3429                 break;
3431         case REQ_SCROLL_LEFT:
3432         case REQ_SCROLL_RIGHT:
3433         case REQ_SCROLL_LINE_DOWN:
3434         case REQ_SCROLL_LINE_UP:
3435         case REQ_SCROLL_PAGE_DOWN:
3436         case REQ_SCROLL_PAGE_UP:
3437                 scroll_view(view, request);
3438                 break;
3440         case REQ_VIEW_BLAME:
3441                 if (!opt_file[0]) {
3442                         report("No file chosen, press %s to open tree view",
3443                                get_key(view->keymap, REQ_VIEW_TREE));
3444                         break;
3445                 }
3446                 open_view(view, request, OPEN_DEFAULT);
3447                 break;
3449         case REQ_VIEW_BLOB:
3450                 if (!ref_blob[0]) {
3451                         report("No file chosen, press %s to open tree view",
3452                                get_key(view->keymap, REQ_VIEW_TREE));
3453                         break;
3454                 }
3455                 open_view(view, request, OPEN_DEFAULT);
3456                 break;
3458         case REQ_VIEW_PAGER:
3459                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3460                         report("No pager content, press %s to run command from prompt",
3461                                get_key(view->keymap, REQ_PROMPT));
3462                         break;
3463                 }
3464                 open_view(view, request, OPEN_DEFAULT);
3465                 break;
3467         case REQ_VIEW_STAGE:
3468                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3469                         report("No stage content, press %s to open the status view and choose file",
3470                                get_key(view->keymap, REQ_VIEW_STATUS));
3471                         break;
3472                 }
3473                 open_view(view, request, OPEN_DEFAULT);
3474                 break;
3476         case REQ_VIEW_STATUS:
3477                 if (opt_is_inside_work_tree == FALSE) {
3478                         report("The status view requires a working tree");
3479                         break;
3480                 }
3481                 open_view(view, request, OPEN_DEFAULT);
3482                 break;
3484         case REQ_VIEW_MAIN:
3485         case REQ_VIEW_DIFF:
3486         case REQ_VIEW_LOG:
3487         case REQ_VIEW_TREE:
3488         case REQ_VIEW_HELP:
3489         case REQ_VIEW_BRANCH:
3490                 open_view(view, request, OPEN_DEFAULT);
3491                 break;
3493         case REQ_NEXT:
3494         case REQ_PREVIOUS:
3495                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3497                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3498                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3499                    (view == VIEW(REQ_VIEW_DIFF) &&
3500                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3501                    (view == VIEW(REQ_VIEW_STAGE) &&
3502                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3503                    (view == VIEW(REQ_VIEW_BLOB) &&
3504                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3505                    (view == VIEW(REQ_VIEW_MAIN) &&
3506                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3507                         int line;
3509                         view = view->parent;
3510                         line = view->lineno;
3511                         move_view(view, request);
3512                         if (view_is_displayed(view))
3513                                 update_view_title(view);
3514                         if (line != view->lineno)
3515                                 view->ops->request(view, REQ_ENTER,
3516                                                    &view->line[view->lineno]);
3518                 } else {
3519                         move_view(view, request);
3520                 }
3521                 break;
3523         case REQ_VIEW_NEXT:
3524         {
3525                 int nviews = displayed_views();
3526                 int next_view = (current_view + 1) % nviews;
3528                 if (next_view == current_view) {
3529                         report("Only one view is displayed");
3530                         break;
3531                 }
3533                 current_view = next_view;
3534                 /* Blur out the title of the previous view. */
3535                 update_view_title(view);
3536                 report("");
3537                 break;
3538         }
3539         case REQ_REFRESH:
3540                 report("Refreshing is not yet supported for the %s view", view->name);
3541                 break;
3543         case REQ_MAXIMIZE:
3544                 if (displayed_views() == 2)
3545                         maximize_view(view);
3546                 break;
3548         case REQ_OPTIONS:
3549                 open_option_menu();
3550                 break;
3552         case REQ_TOGGLE_LINENO:
3553                 toggle_view_option(&opt_line_number, "line numbers");
3554                 break;
3556         case REQ_TOGGLE_DATE:
3557                 toggle_date();
3558                 break;
3560         case REQ_TOGGLE_AUTHOR:
3561                 toggle_author();
3562                 break;
3564         case REQ_TOGGLE_REV_GRAPH:
3565                 toggle_view_option(&opt_rev_graph, "revision graph display");
3566                 break;
3568         case REQ_TOGGLE_REFS:
3569                 toggle_view_option(&opt_show_refs, "reference display");
3570                 break;
3572         case REQ_TOGGLE_SORT_FIELD:
3573         case REQ_TOGGLE_SORT_ORDER:
3574                 report("Sorting is not yet supported for the %s view", view->name);
3575                 break;
3577         case REQ_SEARCH:
3578         case REQ_SEARCH_BACK:
3579                 search_view(view, request);
3580                 break;
3582         case REQ_FIND_NEXT:
3583         case REQ_FIND_PREV:
3584                 find_next(view, request);
3585                 break;
3587         case REQ_STOP_LOADING:
3588                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3589                         view = &views[i];
3590                         if (view->pipe)
3591                                 report("Stopped loading the %s view", view->name),
3592                         end_update(view, TRUE);
3593                 }
3594                 break;
3596         case REQ_SHOW_VERSION:
3597                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3598                 return TRUE;
3600         case REQ_SCREEN_REDRAW:
3601                 redraw_display(TRUE);
3602                 break;
3604         case REQ_EDIT:
3605                 report("Nothing to edit");
3606                 break;
3608         case REQ_ENTER:
3609                 report("Nothing to enter");
3610                 break;
3612         case REQ_VIEW_CLOSE:
3613                 /* XXX: Mark closed views by letting view->parent point to the
3614                  * view itself. Parents to closed view should never be
3615                  * followed. */
3616                 if (view->parent &&
3617                     view->parent->parent != view->parent) {
3618                         maximize_view(view->parent);
3619                         view->parent = view;
3620                         break;
3621                 }
3622                 /* Fall-through */
3623         case REQ_QUIT:
3624                 return FALSE;
3626         default:
3627                 report("Unknown key, press %s for help",
3628                        get_key(view->keymap, REQ_VIEW_HELP));
3629                 return TRUE;
3630         }
3632         return TRUE;
3636 /*
3637  * View backend utilities
3638  */
3640 enum sort_field {
3641         ORDERBY_NAME,
3642         ORDERBY_DATE,
3643         ORDERBY_AUTHOR,
3644 };
3646 struct sort_state {
3647         const enum sort_field *fields;
3648         size_t size, current;
3649         bool reverse;
3650 };
3652 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3653 #define get_sort_field(state) ((state).fields[(state).current])
3654 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3656 static void
3657 sort_view(struct view *view, enum request request, struct sort_state *state,
3658           int (*compare)(const void *, const void *))
3660         switch (request) {
3661         case REQ_TOGGLE_SORT_FIELD:
3662                 state->current = (state->current + 1) % state->size;
3663                 break;
3665         case REQ_TOGGLE_SORT_ORDER:
3666                 state->reverse = !state->reverse;
3667                 break;
3668         default:
3669                 die("Not a sort request");
3670         }
3672         qsort(view->line, view->lines, sizeof(*view->line), compare);
3673         redraw_view(view);
3676 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3678 /* Small author cache to reduce memory consumption. It uses binary
3679  * search to lookup or find place to position new entries. No entries
3680  * are ever freed. */
3681 static const char *
3682 get_author(const char *name)
3684         static const char **authors;
3685         static size_t authors_size;
3686         int from = 0, to = authors_size - 1;
3688         while (from <= to) {
3689                 size_t pos = (to + from) / 2;
3690                 int cmp = strcmp(name, authors[pos]);
3692                 if (!cmp)
3693                         return authors[pos];
3695                 if (cmp < 0)
3696                         to = pos - 1;
3697                 else
3698                         from = pos + 1;
3699         }
3701         if (!realloc_authors(&authors, authors_size, 1))
3702                 return NULL;
3703         name = strdup(name);
3704         if (!name)
3705                 return NULL;
3707         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3708         authors[from] = name;
3709         authors_size++;
3711         return name;
3714 static void
3715 parse_timesec(struct time *time, const char *sec)
3717         time->sec = (time_t) atol(sec);
3720 static void
3721 parse_timezone(struct time *time, const char *zone)
3723         long tz;
3725         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3726         tz += ('0' - zone[2]) * 60 * 60;
3727         tz += ('0' - zone[3]) * 60;
3728         tz += ('0' - zone[4]);
3730         if (zone[0] == '-')
3731                 tz = -tz;
3733         time->tz = tz;
3734         time->sec -= tz;
3737 /* Parse author lines where the name may be empty:
3738  *      author  <email@address.tld> 1138474660 +0100
3739  */
3740 static void
3741 parse_author_line(char *ident, const char **author, struct time *time)
3743         char *nameend = strchr(ident, '<');
3744         char *emailend = strchr(ident, '>');
3746         if (nameend && emailend)
3747                 *nameend = *emailend = 0;
3748         ident = chomp_string(ident);
3749         if (!*ident) {
3750                 if (nameend)
3751                         ident = chomp_string(nameend + 1);
3752                 if (!*ident)
3753                         ident = "Unknown";
3754         }
3756         *author = get_author(ident);
3758         /* Parse epoch and timezone */
3759         if (emailend && emailend[1] == ' ') {
3760                 char *secs = emailend + 2;
3761                 char *zone = strchr(secs, ' ');
3763                 parse_timesec(time, secs);
3765                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3766                         parse_timezone(time, zone + 1);
3767         }
3770 static bool
3771 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3773         char rev[SIZEOF_REV];
3774         const char *revlist_argv[] = {
3775                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3776         };
3777         struct menu_item *items;
3778         char text[SIZEOF_STR];
3779         bool ok = TRUE;
3780         int i;
3782         items = calloc(*parents + 1, sizeof(*items));
3783         if (!items)
3784                 return FALSE;
3786         for (i = 0; i < *parents; i++) {
3787                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3788                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3789                     !(items[i].text = strdup(text))) {
3790                         ok = FALSE;
3791                         break;
3792                 }
3793         }
3795         if (ok) {
3796                 *parents = 0;
3797                 ok = prompt_menu("Select parent", items, parents);
3798         }
3799         for (i = 0; items[i].text; i++)
3800                 free((char *) items[i].text);
3801         free(items);
3802         return ok;
3805 static bool
3806 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3808         char buf[SIZEOF_STR * 4];
3809         const char *revlist_argv[] = {
3810                 "git", "log", "--no-color", "-1",
3811                         "--pretty=format:%P", id, "--", path, NULL
3812         };
3813         int parents;
3815         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3816             (parents = strlen(buf) / 40) < 0) {
3817                 report("Failed to get parent information");
3818                 return FALSE;
3820         } else if (parents == 0) {
3821                 if (path)
3822                         report("Path '%s' does not exist in the parent", path);
3823                 else
3824                         report("The selected commit has no parents");
3825                 return FALSE;
3826         }
3828         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3829                 return FALSE;
3831         string_copy_rev(rev, &buf[41 * parents]);
3832         return TRUE;
3835 /*
3836  * Pager backend
3837  */
3839 static bool
3840 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3842         char text[SIZEOF_STR];
3844         if (opt_line_number && draw_lineno(view, lineno))
3845                 return TRUE;
3847         string_expand(text, sizeof(text), line->data, opt_tab_size);
3848         draw_text(view, line->type, text, TRUE);
3849         return TRUE;
3852 static bool
3853 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3855         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3856         char ref[SIZEOF_STR];
3858         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3859                 return TRUE;
3861         /* This is the only fatal call, since it can "corrupt" the buffer. */
3862         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3863                 return FALSE;
3865         return TRUE;
3868 static void
3869 add_pager_refs(struct view *view, struct line *line)
3871         char buf[SIZEOF_STR];
3872         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3873         struct ref_list *list;
3874         size_t bufpos = 0, i;
3875         const char *sep = "Refs: ";
3876         bool is_tag = FALSE;
3878         assert(line->type == LINE_COMMIT);
3880         list = get_ref_list(commit_id);
3881         if (!list) {
3882                 if (view == VIEW(REQ_VIEW_DIFF))
3883                         goto try_add_describe_ref;
3884                 return;
3885         }
3887         for (i = 0; i < list->size; i++) {
3888                 struct ref *ref = list->refs[i];
3889                 const char *fmt = ref->tag    ? "%s[%s]" :
3890                                   ref->remote ? "%s<%s>" : "%s%s";
3892                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3893                         return;
3894                 sep = ", ";
3895                 if (ref->tag)
3896                         is_tag = TRUE;
3897         }
3899         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3900 try_add_describe_ref:
3901                 /* Add <tag>-g<commit_id> "fake" reference. */
3902                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3903                         return;
3904         }
3906         if (bufpos == 0)
3907                 return;
3909         add_line_text(view, buf, LINE_PP_REFS);
3912 static bool
3913 pager_read(struct view *view, char *data)
3915         struct line *line;
3917         if (!data)
3918                 return TRUE;
3920         line = add_line_text(view, data, get_line_type(data));
3921         if (!line)
3922                 return FALSE;
3924         if (line->type == LINE_COMMIT &&
3925             (view == VIEW(REQ_VIEW_DIFF) ||
3926              view == VIEW(REQ_VIEW_LOG)))
3927                 add_pager_refs(view, line);
3929         return TRUE;
3932 static enum request
3933 pager_request(struct view *view, enum request request, struct line *line)
3935         int split = 0;
3937         if (request != REQ_ENTER)
3938                 return request;
3940         if (line->type == LINE_COMMIT &&
3941            (view == VIEW(REQ_VIEW_LOG) ||
3942             view == VIEW(REQ_VIEW_PAGER))) {
3943                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3944                 split = 1;
3945         }
3947         /* Always scroll the view even if it was split. That way
3948          * you can use Enter to scroll through the log view and
3949          * split open each commit diff. */
3950         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3952         /* FIXME: A minor workaround. Scrolling the view will call report("")
3953          * but if we are scrolling a non-current view this won't properly
3954          * update the view title. */
3955         if (split)
3956                 update_view_title(view);
3958         return REQ_NONE;
3961 static bool
3962 pager_grep(struct view *view, struct line *line)
3964         const char *text[] = { line->data, NULL };
3966         return grep_text(view, text);
3969 static void
3970 pager_select(struct view *view, struct line *line)
3972         if (line->type == LINE_COMMIT) {
3973                 char *text = (char *)line->data + STRING_SIZE("commit ");
3975                 if (view != VIEW(REQ_VIEW_PAGER))
3976                         string_copy_rev(view->ref, text);
3977                 string_copy_rev(ref_commit, text);
3978         }
3981 static struct view_ops pager_ops = {
3982         "line",
3983         NULL,
3984         NULL,
3985         pager_read,
3986         pager_draw,
3987         pager_request,
3988         pager_grep,
3989         pager_select,
3990 };
3992 static const char *log_argv[SIZEOF_ARG] = {
3993         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3994 };
3996 static enum request
3997 log_request(struct view *view, enum request request, struct line *line)
3999         switch (request) {
4000         case REQ_REFRESH:
4001                 load_refs();
4002                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4003                 return REQ_NONE;
4004         default:
4005                 return pager_request(view, request, line);
4006         }
4009 static struct view_ops log_ops = {
4010         "line",
4011         log_argv,
4012         NULL,
4013         pager_read,
4014         pager_draw,
4015         log_request,
4016         pager_grep,
4017         pager_select,
4018 };
4020 static const char *diff_argv[SIZEOF_ARG] = {
4021         "git", "show", "--pretty=fuller", "--no-color", "--root",
4022                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4023 };
4025 static struct view_ops diff_ops = {
4026         "line",
4027         diff_argv,
4028         NULL,
4029         pager_read,
4030         pager_draw,
4031         pager_request,
4032         pager_grep,
4033         pager_select,
4034 };
4036 /*
4037  * Help backend
4038  */
4040 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4042 static bool
4043 help_open_keymap_title(struct view *view, enum keymap keymap)
4045         struct line *line;
4047         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4048                                help_keymap_hidden[keymap] ? '+' : '-',
4049                                enum_name(keymap_table[keymap]));
4050         if (line)
4051                 line->other = keymap;
4053         return help_keymap_hidden[keymap];
4056 static void
4057 help_open_keymap(struct view *view, enum keymap keymap)
4059         const char *group = NULL;
4060         char buf[SIZEOF_STR];
4061         size_t bufpos;
4062         bool add_title = TRUE;
4063         int i;
4065         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4066                 const char *key = NULL;
4068                 if (req_info[i].request == REQ_NONE)
4069                         continue;
4071                 if (!req_info[i].request) {
4072                         group = req_info[i].help;
4073                         continue;
4074                 }
4076                 key = get_keys(keymap, req_info[i].request, TRUE);
4077                 if (!key || !*key)
4078                         continue;
4080                 if (add_title && help_open_keymap_title(view, keymap))
4081                         return;
4082                 add_title = FALSE;
4084                 if (group) {
4085                         add_line_text(view, group, LINE_HELP_GROUP);
4086                         group = NULL;
4087                 }
4089                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4090                                 enum_name(req_info[i]), req_info[i].help);
4091         }
4093         group = "External commands:";
4095         for (i = 0; i < run_requests; i++) {
4096                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4097                 const char *key;
4098                 int argc;
4100                 if (!req || req->keymap != keymap)
4101                         continue;
4103                 key = get_key_name(req->key);
4104                 if (!*key)
4105                         key = "(no key defined)";
4107                 if (add_title && help_open_keymap_title(view, keymap))
4108                         return;
4109                 if (group) {
4110                         add_line_text(view, group, LINE_HELP_GROUP);
4111                         group = NULL;
4112                 }
4114                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4115                         if (!string_format_from(buf, &bufpos, "%s%s",
4116                                                 argc ? " " : "", req->argv[argc]))
4117                                 return;
4119                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4120         }
4123 static bool
4124 help_open(struct view *view)
4126         enum keymap keymap;
4128         reset_view(view);
4129         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4130         add_line_text(view, "", LINE_DEFAULT);
4132         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4133                 help_open_keymap(view, keymap);
4135         return TRUE;
4138 static enum request
4139 help_request(struct view *view, enum request request, struct line *line)
4141         switch (request) {
4142         case REQ_ENTER:
4143                 if (line->type == LINE_HELP_KEYMAP) {
4144                         help_keymap_hidden[line->other] =
4145                                 !help_keymap_hidden[line->other];
4146                         view->p_restore = TRUE;
4147                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4148                 }
4150                 return REQ_NONE;
4151         default:
4152                 return pager_request(view, request, line);
4153         }
4156 static struct view_ops help_ops = {
4157         "line",
4158         NULL,
4159         help_open,
4160         NULL,
4161         pager_draw,
4162         help_request,
4163         pager_grep,
4164         pager_select,
4165 };
4168 /*
4169  * Tree backend
4170  */
4172 struct tree_stack_entry {
4173         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4174         unsigned long lineno;           /* Line number to restore */
4175         char *name;                     /* Position of name in opt_path */
4176 };
4178 /* The top of the path stack. */
4179 static struct tree_stack_entry *tree_stack = NULL;
4180 unsigned long tree_lineno = 0;
4182 static void
4183 pop_tree_stack_entry(void)
4185         struct tree_stack_entry *entry = tree_stack;
4187         tree_lineno = entry->lineno;
4188         entry->name[0] = 0;
4189         tree_stack = entry->prev;
4190         free(entry);
4193 static void
4194 push_tree_stack_entry(const char *name, unsigned long lineno)
4196         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4197         size_t pathlen = strlen(opt_path);
4199         if (!entry)
4200                 return;
4202         entry->prev = tree_stack;
4203         entry->name = opt_path + pathlen;
4204         tree_stack = entry;
4206         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4207                 pop_tree_stack_entry();
4208                 return;
4209         }
4211         /* Move the current line to the first tree entry. */
4212         tree_lineno = 1;
4213         entry->lineno = lineno;
4216 /* Parse output from git-ls-tree(1):
4217  *
4218  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4219  */
4221 #define SIZEOF_TREE_ATTR \
4222         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4224 #define SIZEOF_TREE_MODE \
4225         STRING_SIZE("100644 ")
4227 #define TREE_ID_OFFSET \
4228         STRING_SIZE("100644 blob ")
4230 struct tree_entry {
4231         char id[SIZEOF_REV];
4232         mode_t mode;
4233         struct time time;               /* Date from the author ident. */
4234         const char *author;             /* Author of the commit. */
4235         char name[1];
4236 };
4238 static const char *
4239 tree_path(const struct line *line)
4241         return ((struct tree_entry *) line->data)->name;
4244 static int
4245 tree_compare_entry(const struct line *line1, const struct line *line2)
4247         if (line1->type != line2->type)
4248                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4249         return strcmp(tree_path(line1), tree_path(line2));
4252 static const enum sort_field tree_sort_fields[] = {
4253         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4254 };
4255 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4257 static int
4258 tree_compare(const void *l1, const void *l2)
4260         const struct line *line1 = (const struct line *) l1;
4261         const struct line *line2 = (const struct line *) l2;
4262         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4263         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4265         if (line1->type == LINE_TREE_HEAD)
4266                 return -1;
4267         if (line2->type == LINE_TREE_HEAD)
4268                 return 1;
4270         switch (get_sort_field(tree_sort_state)) {
4271         case ORDERBY_DATE:
4272                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4274         case ORDERBY_AUTHOR:
4275                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4277         case ORDERBY_NAME:
4278         default:
4279                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4280         }
4284 static struct line *
4285 tree_entry(struct view *view, enum line_type type, const char *path,
4286            const char *mode, const char *id)
4288         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4289         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4291         if (!entry || !line) {
4292                 free(entry);
4293                 return NULL;
4294         }
4296         strncpy(entry->name, path, strlen(path));
4297         if (mode)
4298                 entry->mode = strtoul(mode, NULL, 8);
4299         if (id)
4300                 string_copy_rev(entry->id, id);
4302         return line;
4305 static bool
4306 tree_read_date(struct view *view, char *text, bool *read_date)
4308         static const char *author_name;
4309         static struct time author_time;
4311         if (!text && *read_date) {
4312                 *read_date = FALSE;
4313                 return TRUE;
4315         } else if (!text) {
4316                 char *path = *opt_path ? opt_path : ".";
4317                 /* Find next entry to process */
4318                 const char *log_file[] = {
4319                         "git", "log", "--no-color", "--pretty=raw",
4320                                 "--cc", "--raw", view->id, "--", path, NULL
4321                 };
4322                 struct io io = {};
4324                 if (!view->lines) {
4325                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4326                         report("Tree is empty");
4327                         return TRUE;
4328                 }
4330                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4331                         report("Failed to load tree data");
4332                         return TRUE;
4333                 }
4335                 io_done(view->pipe);
4336                 view->io = io;
4337                 *read_date = TRUE;
4338                 return FALSE;
4340         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4341                 parse_author_line(text + STRING_SIZE("author "),
4342                                   &author_name, &author_time);
4344         } else if (*text == ':') {
4345                 char *pos;
4346                 size_t annotated = 1;
4347                 size_t i;
4349                 pos = strchr(text, '\t');
4350                 if (!pos)
4351                         return TRUE;
4352                 text = pos + 1;
4353                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4354                         text += strlen(opt_path);
4355                 pos = strchr(text, '/');
4356                 if (pos)
4357                         *pos = 0;
4359                 for (i = 1; i < view->lines; i++) {
4360                         struct line *line = &view->line[i];
4361                         struct tree_entry *entry = line->data;
4363                         annotated += !!entry->author;
4364                         if (entry->author || strcmp(entry->name, text))
4365                                 continue;
4367                         entry->author = author_name;
4368                         entry->time = author_time;
4369                         line->dirty = 1;
4370                         break;
4371                 }
4373                 if (annotated == view->lines)
4374                         io_kill(view->pipe);
4375         }
4376         return TRUE;
4379 static bool
4380 tree_read(struct view *view, char *text)
4382         static bool read_date = FALSE;
4383         struct tree_entry *data;
4384         struct line *entry, *line;
4385         enum line_type type;
4386         size_t textlen = text ? strlen(text) : 0;
4387         char *path = text + SIZEOF_TREE_ATTR;
4389         if (read_date || !text)
4390                 return tree_read_date(view, text, &read_date);
4392         if (textlen <= SIZEOF_TREE_ATTR)
4393                 return FALSE;
4394         if (view->lines == 0 &&
4395             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4396                 return FALSE;
4398         /* Strip the path part ... */
4399         if (*opt_path) {
4400                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4401                 size_t striplen = strlen(opt_path);
4403                 if (pathlen > striplen)
4404                         memmove(path, path + striplen,
4405                                 pathlen - striplen + 1);
4407                 /* Insert "link" to parent directory. */
4408                 if (view->lines == 1 &&
4409                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4410                         return FALSE;
4411         }
4413         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4414         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4415         if (!entry)
4416                 return FALSE;
4417         data = entry->data;
4419         /* Skip "Directory ..." and ".." line. */
4420         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4421                 if (tree_compare_entry(line, entry) <= 0)
4422                         continue;
4424                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4426                 line->data = data;
4427                 line->type = type;
4428                 for (; line <= entry; line++)
4429                         line->dirty = line->cleareol = 1;
4430                 return TRUE;
4431         }
4433         if (tree_lineno > view->lineno) {
4434                 view->lineno = tree_lineno;
4435                 tree_lineno = 0;
4436         }
4438         return TRUE;
4441 static bool
4442 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4444         struct tree_entry *entry = line->data;
4446         if (line->type == LINE_TREE_HEAD) {
4447                 if (draw_text(view, line->type, "Directory path /", TRUE))
4448                         return TRUE;
4449         } else {
4450                 if (draw_mode(view, entry->mode))
4451                         return TRUE;
4453                 if (opt_author && draw_author(view, entry->author))
4454                         return TRUE;
4456                 if (opt_date && draw_date(view, &entry->time))
4457                         return TRUE;
4458         }
4459         if (draw_text(view, line->type, entry->name, TRUE))
4460                 return TRUE;
4461         return TRUE;
4464 static void
4465 open_blob_editor()
4467         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4468         int fd = mkstemp(file);
4470         if (fd == -1)
4471                 report("Failed to create temporary file");
4472         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4473                 report("Failed to save blob data to file");
4474         else
4475                 open_editor(file);
4476         if (fd != -1)
4477                 unlink(file);
4480 static enum request
4481 tree_request(struct view *view, enum request request, struct line *line)
4483         enum open_flags flags;
4485         switch (request) {
4486         case REQ_VIEW_BLAME:
4487                 if (line->type != LINE_TREE_FILE) {
4488                         report("Blame only supported for files");
4489                         return REQ_NONE;
4490                 }
4492                 string_copy(opt_ref, view->vid);
4493                 return request;
4495         case REQ_EDIT:
4496                 if (line->type != LINE_TREE_FILE) {
4497                         report("Edit only supported for files");
4498                 } else if (!is_head_commit(view->vid)) {
4499                         open_blob_editor();
4500                 } else {
4501                         open_editor(opt_file);
4502                 }
4503                 return REQ_NONE;
4505         case REQ_TOGGLE_SORT_FIELD:
4506         case REQ_TOGGLE_SORT_ORDER:
4507                 sort_view(view, request, &tree_sort_state, tree_compare);
4508                 return REQ_NONE;
4510         case REQ_PARENT:
4511                 if (!*opt_path) {
4512                         /* quit view if at top of tree */
4513                         return REQ_VIEW_CLOSE;
4514                 }
4515                 /* fake 'cd  ..' */
4516                 line = &view->line[1];
4517                 break;
4519         case REQ_ENTER:
4520                 break;
4522         default:
4523                 return request;
4524         }
4526         /* Cleanup the stack if the tree view is at a different tree. */
4527         while (!*opt_path && tree_stack)
4528                 pop_tree_stack_entry();
4530         switch (line->type) {
4531         case LINE_TREE_DIR:
4532                 /* Depending on whether it is a subdirectory or parent link
4533                  * mangle the path buffer. */
4534                 if (line == &view->line[1] && *opt_path) {
4535                         pop_tree_stack_entry();
4537                 } else {
4538                         const char *basename = tree_path(line);
4540                         push_tree_stack_entry(basename, view->lineno);
4541                 }
4543                 /* Trees and subtrees share the same ID, so they are not not
4544                  * unique like blobs. */
4545                 flags = OPEN_RELOAD;
4546                 request = REQ_VIEW_TREE;
4547                 break;
4549         case LINE_TREE_FILE:
4550                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4551                 request = REQ_VIEW_BLOB;
4552                 break;
4554         default:
4555                 return REQ_NONE;
4556         }
4558         open_view(view, request, flags);
4559         if (request == REQ_VIEW_TREE)
4560                 view->lineno = tree_lineno;
4562         return REQ_NONE;
4565 static bool
4566 tree_grep(struct view *view, struct line *line)
4568         struct tree_entry *entry = line->data;
4569         const char *text[] = {
4570                 entry->name,
4571                 opt_author ? entry->author : "",
4572                 mkdate(&entry->time, opt_date),
4573                 NULL
4574         };
4576         return grep_text(view, text);
4579 static void
4580 tree_select(struct view *view, struct line *line)
4582         struct tree_entry *entry = line->data;
4584         if (line->type == LINE_TREE_FILE) {
4585                 string_copy_rev(ref_blob, entry->id);
4586                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4588         } else if (line->type != LINE_TREE_DIR) {
4589                 return;
4590         }
4592         string_copy_rev(view->ref, entry->id);
4595 static bool
4596 tree_prepare(struct view *view)
4598         if (view->lines == 0 && opt_prefix[0]) {
4599                 char *pos = opt_prefix;
4601                 while (pos && *pos) {
4602                         char *end = strchr(pos, '/');
4604                         if (end)
4605                                 *end = 0;
4606                         push_tree_stack_entry(pos, 0);
4607                         pos = end;
4608                         if (end) {
4609                                 *end = '/';
4610                                 pos++;
4611                         }
4612                 }
4614         } else if (strcmp(view->vid, view->id)) {
4615                 opt_path[0] = 0;
4616         }
4618         return io_init_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4621 static const char *tree_argv[SIZEOF_ARG] = {
4622         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4623 };
4625 static struct view_ops tree_ops = {
4626         "file",
4627         tree_argv,
4628         NULL,
4629         tree_read,
4630         tree_draw,
4631         tree_request,
4632         tree_grep,
4633         tree_select,
4634         tree_prepare,
4635 };
4637 static bool
4638 blob_read(struct view *view, char *line)
4640         if (!line)
4641                 return TRUE;
4642         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4645 static enum request
4646 blob_request(struct view *view, enum request request, struct line *line)
4648         switch (request) {
4649         case REQ_EDIT:
4650                 open_blob_editor();
4651                 return REQ_NONE;
4652         default:
4653                 return pager_request(view, request, line);
4654         }
4657 static const char *blob_argv[SIZEOF_ARG] = {
4658         "git", "cat-file", "blob", "%(blob)", NULL
4659 };
4661 static struct view_ops blob_ops = {
4662         "line",
4663         blob_argv,
4664         NULL,
4665         blob_read,
4666         pager_draw,
4667         blob_request,
4668         pager_grep,
4669         pager_select,
4670 };
4672 /*
4673  * Blame backend
4674  *
4675  * Loading the blame view is a two phase job:
4676  *
4677  *  1. File content is read either using opt_file from the
4678  *     filesystem or using git-cat-file.
4679  *  2. Then blame information is incrementally added by
4680  *     reading output from git-blame.
4681  */
4683 static const char *blame_head_argv[] = {
4684         "git", "blame", "--incremental", "--", "%(file)", NULL
4685 };
4687 static const char *blame_ref_argv[] = {
4688         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4689 };
4691 static const char *blame_cat_file_argv[] = {
4692         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4693 };
4695 struct blame_commit {
4696         char id[SIZEOF_REV];            /* SHA1 ID. */
4697         char title[128];                /* First line of the commit message. */
4698         const char *author;             /* Author of the commit. */
4699         struct time time;               /* Date from the author ident. */
4700         char filename[128];             /* Name of file. */
4701         bool has_previous;              /* Was a "previous" line detected. */
4702 };
4704 struct blame {
4705         struct blame_commit *commit;
4706         unsigned long lineno;
4707         char text[1];
4708 };
4710 static bool
4711 blame_open(struct view *view)
4713         char path[SIZEOF_STR];
4715         if (!view->parent && *opt_prefix) {
4716                 string_copy(path, opt_file);
4717                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4718                         return FALSE;
4719         }
4721         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4722                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4723                         return FALSE;
4724         }
4726         setup_update(view, opt_file);
4727         string_format(view->ref, "%s ...", opt_file);
4729         return TRUE;
4732 static struct blame_commit *
4733 get_blame_commit(struct view *view, const char *id)
4735         size_t i;
4737         for (i = 0; i < view->lines; i++) {
4738                 struct blame *blame = view->line[i].data;
4740                 if (!blame->commit)
4741                         continue;
4743                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4744                         return blame->commit;
4745         }
4747         {
4748                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4750                 if (commit)
4751                         string_ncopy(commit->id, id, SIZEOF_REV);
4752                 return commit;
4753         }
4756 static bool
4757 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4759         const char *pos = *posref;
4761         *posref = NULL;
4762         pos = strchr(pos + 1, ' ');
4763         if (!pos || !isdigit(pos[1]))
4764                 return FALSE;
4765         *number = atoi(pos + 1);
4766         if (*number < min || *number > max)
4767                 return FALSE;
4769         *posref = pos;
4770         return TRUE;
4773 static struct blame_commit *
4774 parse_blame_commit(struct view *view, const char *text, int *blamed)
4776         struct blame_commit *commit;
4777         struct blame *blame;
4778         const char *pos = text + SIZEOF_REV - 2;
4779         size_t orig_lineno = 0;
4780         size_t lineno;
4781         size_t group;
4783         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4784                 return NULL;
4786         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4787             !parse_number(&pos, &lineno, 1, view->lines) ||
4788             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4789                 return NULL;
4791         commit = get_blame_commit(view, text);
4792         if (!commit)
4793                 return NULL;
4795         *blamed += group;
4796         while (group--) {
4797                 struct line *line = &view->line[lineno + group - 1];
4799                 blame = line->data;
4800                 blame->commit = commit;
4801                 blame->lineno = orig_lineno + group - 1;
4802                 line->dirty = 1;
4803         }
4805         return commit;
4808 static bool
4809 blame_read_file(struct view *view, const char *line, bool *read_file)
4811         if (!line) {
4812                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4813                 struct io io = {};
4815                 if (view->lines == 0 && !view->parent)
4816                         die("No blame exist for %s", view->vid);
4818                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4819                         report("Failed to load blame data");
4820                         return TRUE;
4821                 }
4823                 io_done(view->pipe);
4824                 view->io = io;
4825                 *read_file = FALSE;
4826                 return FALSE;
4828         } else {
4829                 size_t linelen = strlen(line);
4830                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4832                 if (!blame)
4833                         return FALSE;
4835                 blame->commit = NULL;
4836                 strncpy(blame->text, line, linelen);
4837                 blame->text[linelen] = 0;
4838                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4839         }
4842 static bool
4843 match_blame_header(const char *name, char **line)
4845         size_t namelen = strlen(name);
4846         bool matched = !strncmp(name, *line, namelen);
4848         if (matched)
4849                 *line += namelen;
4851         return matched;
4854 static bool
4855 blame_read(struct view *view, char *line)
4857         static struct blame_commit *commit = NULL;
4858         static int blamed = 0;
4859         static bool read_file = TRUE;
4861         if (read_file)
4862                 return blame_read_file(view, line, &read_file);
4864         if (!line) {
4865                 /* Reset all! */
4866                 commit = NULL;
4867                 blamed = 0;
4868                 read_file = TRUE;
4869                 string_format(view->ref, "%s", view->vid);
4870                 if (view_is_displayed(view)) {
4871                         update_view_title(view);
4872                         redraw_view_from(view, 0);
4873                 }
4874                 return TRUE;
4875         }
4877         if (!commit) {
4878                 commit = parse_blame_commit(view, line, &blamed);
4879                 string_format(view->ref, "%s %2d%%", view->vid,
4880                               view->lines ? blamed * 100 / view->lines : 0);
4882         } else if (match_blame_header("author ", &line)) {
4883                 commit->author = get_author(line);
4885         } else if (match_blame_header("author-time ", &line)) {
4886                 parse_timesec(&commit->time, line);
4888         } else if (match_blame_header("author-tz ", &line)) {
4889                 parse_timezone(&commit->time, line);
4891         } else if (match_blame_header("summary ", &line)) {
4892                 string_ncopy(commit->title, line, strlen(line));
4894         } else if (match_blame_header("previous ", &line)) {
4895                 commit->has_previous = TRUE;
4897         } else if (match_blame_header("filename ", &line)) {
4898                 string_ncopy(commit->filename, line, strlen(line));
4899                 commit = NULL;
4900         }
4902         return TRUE;
4905 static bool
4906 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4908         struct blame *blame = line->data;
4909         struct time *time = NULL;
4910         const char *id = NULL, *author = NULL;
4911         char text[SIZEOF_STR];
4913         if (blame->commit && *blame->commit->filename) {
4914                 id = blame->commit->id;
4915                 author = blame->commit->author;
4916                 time = &blame->commit->time;
4917         }
4919         if (opt_date && draw_date(view, time))
4920                 return TRUE;
4922         if (opt_author && draw_author(view, author))
4923                 return TRUE;
4925         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4926                 return TRUE;
4928         if (draw_lineno(view, lineno))
4929                 return TRUE;
4931         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4932         draw_text(view, LINE_DEFAULT, text, TRUE);
4933         return TRUE;
4936 static bool
4937 check_blame_commit(struct blame *blame, bool check_null_id)
4939         if (!blame->commit)
4940                 report("Commit data not loaded yet");
4941         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4942                 report("No commit exist for the selected line");
4943         else
4944                 return TRUE;
4945         return FALSE;
4948 static void
4949 setup_blame_parent_line(struct view *view, struct blame *blame)
4951         const char *diff_tree_argv[] = {
4952                 "git", "diff-tree", "-U0", blame->commit->id,
4953                         "--", blame->commit->filename, NULL
4954         };
4955         struct io io = {};
4956         int parent_lineno = -1;
4957         int blamed_lineno = -1;
4958         char *line;
4960         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
4961                 return;
4963         while ((line = io_get(&io, '\n', TRUE))) {
4964                 if (*line == '@') {
4965                         char *pos = strchr(line, '+');
4967                         parent_lineno = atoi(line + 4);
4968                         if (pos)
4969                                 blamed_lineno = atoi(pos + 1);
4971                 } else if (*line == '+' && parent_lineno != -1) {
4972                         if (blame->lineno == blamed_lineno - 1 &&
4973                             !strcmp(blame->text, line + 1)) {
4974                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4975                                 break;
4976                         }
4977                         blamed_lineno++;
4978                 }
4979         }
4981         io_done(&io);
4984 static enum request
4985 blame_request(struct view *view, enum request request, struct line *line)
4987         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4988         struct blame *blame = line->data;
4990         switch (request) {
4991         case REQ_VIEW_BLAME:
4992                 if (check_blame_commit(blame, TRUE)) {
4993                         string_copy(opt_ref, blame->commit->id);
4994                         string_copy(opt_file, blame->commit->filename);
4995                         if (blame->lineno)
4996                                 view->lineno = blame->lineno;
4997                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4998                 }
4999                 break;
5001         case REQ_PARENT:
5002                 if (check_blame_commit(blame, TRUE) &&
5003                     select_commit_parent(blame->commit->id, opt_ref,
5004                                          blame->commit->filename)) {
5005                         string_copy(opt_file, blame->commit->filename);
5006                         setup_blame_parent_line(view, blame);
5007                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5008                 }
5009                 break;
5011         case REQ_ENTER:
5012                 if (!check_blame_commit(blame, FALSE))
5013                         break;
5015                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5016                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5017                         break;
5019                 if (!strcmp(blame->commit->id, NULL_ID)) {
5020                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5021                         const char *diff_index_argv[] = {
5022                                 "git", "diff-index", "--root", "--patch-with-stat",
5023                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5024                         };
5026                         if (!blame->commit->has_previous) {
5027                                 diff_index_argv[1] = "diff";
5028                                 diff_index_argv[2] = "--no-color";
5029                                 diff_index_argv[6] = "--";
5030                                 diff_index_argv[7] = "/dev/null";
5031                         }
5033                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5034                                 report("Failed to allocate diff command");
5035                                 break;
5036                         }
5037                         flags |= OPEN_PREPARED;
5038                 }
5040                 open_view(view, REQ_VIEW_DIFF, flags);
5041                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5042                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5043                 break;
5045         default:
5046                 return request;
5047         }
5049         return REQ_NONE;
5052 static bool
5053 blame_grep(struct view *view, struct line *line)
5055         struct blame *blame = line->data;
5056         struct blame_commit *commit = blame->commit;
5057         const char *text[] = {
5058                 blame->text,
5059                 commit ? commit->title : "",
5060                 commit ? commit->id : "",
5061                 commit && opt_author ? commit->author : "",
5062                 commit ? mkdate(&commit->time, opt_date) : "",
5063                 NULL
5064         };
5066         return grep_text(view, text);
5069 static void
5070 blame_select(struct view *view, struct line *line)
5072         struct blame *blame = line->data;
5073         struct blame_commit *commit = blame->commit;
5075         if (!commit)
5076                 return;
5078         if (!strcmp(commit->id, NULL_ID))
5079                 string_ncopy(ref_commit, "HEAD", 4);
5080         else
5081                 string_copy_rev(ref_commit, commit->id);
5084 static struct view_ops blame_ops = {
5085         "line",
5086         NULL,
5087         blame_open,
5088         blame_read,
5089         blame_draw,
5090         blame_request,
5091         blame_grep,
5092         blame_select,
5093 };
5095 /*
5096  * Branch backend
5097  */
5099 struct branch {
5100         const char *author;             /* Author of the last commit. */
5101         struct time time;               /* Date of the last activity. */
5102         const struct ref *ref;          /* Name and commit ID information. */
5103 };
5105 static const struct ref branch_all;
5107 static const enum sort_field branch_sort_fields[] = {
5108         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5109 };
5110 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5112 static int
5113 branch_compare(const void *l1, const void *l2)
5115         const struct branch *branch1 = ((const struct line *) l1)->data;
5116         const struct branch *branch2 = ((const struct line *) l2)->data;
5118         switch (get_sort_field(branch_sort_state)) {
5119         case ORDERBY_DATE:
5120                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5122         case ORDERBY_AUTHOR:
5123                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5125         case ORDERBY_NAME:
5126         default:
5127                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5128         }
5131 static bool
5132 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5134         struct branch *branch = line->data;
5135         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5137         if (opt_date && draw_date(view, &branch->time))
5138                 return TRUE;
5140         if (opt_author && draw_author(view, branch->author))
5141                 return TRUE;
5143         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5144         return TRUE;
5147 static enum request
5148 branch_request(struct view *view, enum request request, struct line *line)
5150         struct branch *branch = line->data;
5152         switch (request) {
5153         case REQ_REFRESH:
5154                 load_refs();
5155                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5156                 return REQ_NONE;
5158         case REQ_TOGGLE_SORT_FIELD:
5159         case REQ_TOGGLE_SORT_ORDER:
5160                 sort_view(view, request, &branch_sort_state, branch_compare);
5161                 return REQ_NONE;
5163         case REQ_ENTER:
5164                 if (branch->ref == &branch_all) {
5165                         const char *all_branches_argv[] = {
5166                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5167                                       "--topo-order", "--all", NULL
5168                         };
5169                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5171                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5172                                 report("Failed to load view of all branches");
5173                                 return REQ_NONE;
5174                         }
5175                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5176                 } else {
5177                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5178                 }
5179                 return REQ_NONE;
5181         default:
5182                 return request;
5183         }
5186 static bool
5187 branch_read(struct view *view, char *line)
5189         static char id[SIZEOF_REV];
5190         struct branch *reference;
5191         size_t i;
5193         if (!line)
5194                 return TRUE;
5196         switch (get_line_type(line)) {
5197         case LINE_COMMIT:
5198                 string_copy_rev(id, line + STRING_SIZE("commit "));
5199                 return TRUE;
5201         case LINE_AUTHOR:
5202                 for (i = 0, reference = NULL; i < view->lines; i++) {
5203                         struct branch *branch = view->line[i].data;
5205                         if (strcmp(branch->ref->id, id))
5206                                 continue;
5208                         view->line[i].dirty = TRUE;
5209                         if (reference) {
5210                                 branch->author = reference->author;
5211                                 branch->time = reference->time;
5212                                 continue;
5213                         }
5215                         parse_author_line(line + STRING_SIZE("author "),
5216                                           &branch->author, &branch->time);
5217                         reference = branch;
5218                 }
5219                 return TRUE;
5221         default:
5222                 return TRUE;
5223         }
5227 static bool
5228 branch_open_visitor(void *data, const struct ref *ref)
5230         struct view *view = data;
5231         struct branch *branch;
5233         if (ref->tag || ref->ltag || ref->remote)
5234                 return TRUE;
5236         branch = calloc(1, sizeof(*branch));
5237         if (!branch)
5238                 return FALSE;
5240         branch->ref = ref;
5241         return !!add_line_data(view, branch, LINE_DEFAULT);
5244 static bool
5245 branch_open(struct view *view)
5247         const char *branch_log[] = {
5248                 "git", "log", "--no-color", "--pretty=raw",
5249                         "--simplify-by-decoration", "--all", NULL
5250         };
5252         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5253                 report("Failed to load branch data");
5254                 return TRUE;
5255         }
5257         setup_update(view, view->id);
5258         branch_open_visitor(view, &branch_all);
5259         foreach_ref(branch_open_visitor, view);
5260         view->p_restore = TRUE;
5262         return TRUE;
5265 static bool
5266 branch_grep(struct view *view, struct line *line)
5268         struct branch *branch = line->data;
5269         const char *text[] = {
5270                 branch->ref->name,
5271                 branch->author,
5272                 NULL
5273         };
5275         return grep_text(view, text);
5278 static void
5279 branch_select(struct view *view, struct line *line)
5281         struct branch *branch = line->data;
5283         string_copy_rev(view->ref, branch->ref->id);
5284         string_copy_rev(ref_commit, branch->ref->id);
5285         string_copy_rev(ref_head, branch->ref->id);
5288 static struct view_ops branch_ops = {
5289         "branch",
5290         NULL,
5291         branch_open,
5292         branch_read,
5293         branch_draw,
5294         branch_request,
5295         branch_grep,
5296         branch_select,
5297 };
5299 /*
5300  * Status backend
5301  */
5303 struct status {
5304         char status;
5305         struct {
5306                 mode_t mode;
5307                 char rev[SIZEOF_REV];
5308                 char name[SIZEOF_STR];
5309         } old;
5310         struct {
5311                 mode_t mode;
5312                 char rev[SIZEOF_REV];
5313                 char name[SIZEOF_STR];
5314         } new;
5315 };
5317 static char status_onbranch[SIZEOF_STR];
5318 static struct status stage_status;
5319 static enum line_type stage_line_type;
5320 static size_t stage_chunks;
5321 static int *stage_chunk;
5323 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5325 /* This should work even for the "On branch" line. */
5326 static inline bool
5327 status_has_none(struct view *view, struct line *line)
5329         return line < view->line + view->lines && !line[1].data;
5332 /* Get fields from the diff line:
5333  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5334  */
5335 static inline bool
5336 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5338         const char *old_mode = buf +  1;
5339         const char *new_mode = buf +  8;
5340         const char *old_rev  = buf + 15;
5341         const char *new_rev  = buf + 56;
5342         const char *status   = buf + 97;
5344         if (bufsize < 98 ||
5345             old_mode[-1] != ':' ||
5346             new_mode[-1] != ' ' ||
5347             old_rev[-1]  != ' ' ||
5348             new_rev[-1]  != ' ' ||
5349             status[-1]   != ' ')
5350                 return FALSE;
5352         file->status = *status;
5354         string_copy_rev(file->old.rev, old_rev);
5355         string_copy_rev(file->new.rev, new_rev);
5357         file->old.mode = strtoul(old_mode, NULL, 8);
5358         file->new.mode = strtoul(new_mode, NULL, 8);
5360         file->old.name[0] = file->new.name[0] = 0;
5362         return TRUE;
5365 static bool
5366 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5368         struct status *unmerged = NULL;
5369         char *buf;
5370         struct io io = {};
5372         if (!io_run(&io, argv, opt_cdup, IO_RD))
5373                 return FALSE;
5375         add_line_data(view, NULL, type);
5377         while ((buf = io_get(&io, 0, TRUE))) {
5378                 struct status *file = unmerged;
5380                 if (!file) {
5381                         file = calloc(1, sizeof(*file));
5382                         if (!file || !add_line_data(view, file, type))
5383                                 goto error_out;
5384                 }
5386                 /* Parse diff info part. */
5387                 if (status) {
5388                         file->status = status;
5389                         if (status == 'A')
5390                                 string_copy(file->old.rev, NULL_ID);
5392                 } else if (!file->status || file == unmerged) {
5393                         if (!status_get_diff(file, buf, strlen(buf)))
5394                                 goto error_out;
5396                         buf = io_get(&io, 0, TRUE);
5397                         if (!buf)
5398                                 break;
5400                         /* Collapse all modified entries that follow an
5401                          * associated unmerged entry. */
5402                         if (unmerged == file) {
5403                                 unmerged->status = 'U';
5404                                 unmerged = NULL;
5405                         } else if (file->status == 'U') {
5406                                 unmerged = file;
5407                         }
5408                 }
5410                 /* Grab the old name for rename/copy. */
5411                 if (!*file->old.name &&
5412                     (file->status == 'R' || file->status == 'C')) {
5413                         string_ncopy(file->old.name, buf, strlen(buf));
5415                         buf = io_get(&io, 0, TRUE);
5416                         if (!buf)
5417                                 break;
5418                 }
5420                 /* git-ls-files just delivers a NUL separated list of
5421                  * file names similar to the second half of the
5422                  * git-diff-* output. */
5423                 string_ncopy(file->new.name, buf, strlen(buf));
5424                 if (!*file->old.name)
5425                         string_copy(file->old.name, file->new.name);
5426                 file = NULL;
5427         }
5429         if (io_error(&io)) {
5430 error_out:
5431                 io_done(&io);
5432                 return FALSE;
5433         }
5435         if (!view->line[view->lines - 1].data)
5436                 add_line_data(view, NULL, LINE_STAT_NONE);
5438         io_done(&io);
5439         return TRUE;
5442 /* Don't show unmerged entries in the staged section. */
5443 static const char *status_diff_index_argv[] = {
5444         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5445                              "--cached", "-M", "HEAD", NULL
5446 };
5448 static const char *status_diff_files_argv[] = {
5449         "git", "diff-files", "-z", NULL
5450 };
5452 static const char *status_list_other_argv[] = {
5453         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5454 };
5456 static const char *status_list_no_head_argv[] = {
5457         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5458 };
5460 static const char *update_index_argv[] = {
5461         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5462 };
5464 /* Restore the previous line number to stay in the context or select a
5465  * line with something that can be updated. */
5466 static void
5467 status_restore(struct view *view)
5469         if (view->p_lineno >= view->lines)
5470                 view->p_lineno = view->lines - 1;
5471         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5472                 view->p_lineno++;
5473         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5474                 view->p_lineno--;
5476         /* If the above fails, always skip the "On branch" line. */
5477         if (view->p_lineno < view->lines)
5478                 view->lineno = view->p_lineno;
5479         else
5480                 view->lineno = 1;
5482         if (view->lineno < view->offset)
5483                 view->offset = view->lineno;
5484         else if (view->offset + view->height <= view->lineno)
5485                 view->offset = view->lineno - view->height + 1;
5487         view->p_restore = FALSE;
5490 static void
5491 status_update_onbranch(void)
5493         static const char *paths[][2] = {
5494                 { "rebase-apply/rebasing",      "Rebasing" },
5495                 { "rebase-apply/applying",      "Applying mailbox" },
5496                 { "rebase-apply/",              "Rebasing mailbox" },
5497                 { "rebase-merge/interactive",   "Interactive rebase" },
5498                 { "rebase-merge/",              "Rebase merge" },
5499                 { "MERGE_HEAD",                 "Merging" },
5500                 { "BISECT_LOG",                 "Bisecting" },
5501                 { "HEAD",                       "On branch" },
5502         };
5503         char buf[SIZEOF_STR];
5504         struct stat stat;
5505         int i;
5507         if (is_initial_commit()) {
5508                 string_copy(status_onbranch, "Initial commit");
5509                 return;
5510         }
5512         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5513                 char *head = opt_head;
5515                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5516                     lstat(buf, &stat) < 0)
5517                         continue;
5519                 if (!*opt_head) {
5520                         struct io io = {};
5522                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5523                             io_read_buf(&io, buf, sizeof(buf))) {
5524                                 head = buf;
5525                                 if (!prefixcmp(head, "refs/heads/"))
5526                                         head += STRING_SIZE("refs/heads/");
5527                         }
5528                 }
5530                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5531                         string_copy(status_onbranch, opt_head);
5532                 return;
5533         }
5535         string_copy(status_onbranch, "Not currently on any branch");
5538 /* First parse staged info using git-diff-index(1), then parse unstaged
5539  * info using git-diff-files(1), and finally untracked files using
5540  * git-ls-files(1). */
5541 static bool
5542 status_open(struct view *view)
5544         reset_view(view);
5546         add_line_data(view, NULL, LINE_STAT_HEAD);
5547         status_update_onbranch();
5549         io_run_bg(update_index_argv);
5551         if (is_initial_commit()) {
5552                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5553                         return FALSE;
5554         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5555                 return FALSE;
5556         }
5558         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5559             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5560                 return FALSE;
5562         /* Restore the exact position or use the specialized restore
5563          * mode? */
5564         if (!view->p_restore)
5565                 status_restore(view);
5566         return TRUE;
5569 static bool
5570 status_draw(struct view *view, struct line *line, unsigned int lineno)
5572         struct status *status = line->data;
5573         enum line_type type;
5574         const char *text;
5576         if (!status) {
5577                 switch (line->type) {
5578                 case LINE_STAT_STAGED:
5579                         type = LINE_STAT_SECTION;
5580                         text = "Changes to be committed:";
5581                         break;
5583                 case LINE_STAT_UNSTAGED:
5584                         type = LINE_STAT_SECTION;
5585                         text = "Changed but not updated:";
5586                         break;
5588                 case LINE_STAT_UNTRACKED:
5589                         type = LINE_STAT_SECTION;
5590                         text = "Untracked files:";
5591                         break;
5593                 case LINE_STAT_NONE:
5594                         type = LINE_DEFAULT;
5595                         text = "  (no files)";
5596                         break;
5598                 case LINE_STAT_HEAD:
5599                         type = LINE_STAT_HEAD;
5600                         text = status_onbranch;
5601                         break;
5603                 default:
5604                         return FALSE;
5605                 }
5606         } else {
5607                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5609                 buf[0] = status->status;
5610                 if (draw_text(view, line->type, buf, TRUE))
5611                         return TRUE;
5612                 type = LINE_DEFAULT;
5613                 text = status->new.name;
5614         }
5616         draw_text(view, type, text, TRUE);
5617         return TRUE;
5620 static enum request
5621 status_load_error(struct view *view, struct view *stage, const char *path)
5623         if (displayed_views() == 2 || display[current_view] != view)
5624                 maximize_view(view);
5625         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5626         return REQ_NONE;
5629 static enum request
5630 status_enter(struct view *view, struct line *line)
5632         struct status *status = line->data;
5633         const char *oldpath = status ? status->old.name : NULL;
5634         /* Diffs for unmerged entries are empty when passing the new
5635          * path, so leave it empty. */
5636         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5637         const char *info;
5638         enum open_flags split;
5639         struct view *stage = VIEW(REQ_VIEW_STAGE);
5641         if (line->type == LINE_STAT_NONE ||
5642             (!status && line[1].type == LINE_STAT_NONE)) {
5643                 report("No file to diff");
5644                 return REQ_NONE;
5645         }
5647         switch (line->type) {
5648         case LINE_STAT_STAGED:
5649                 if (is_initial_commit()) {
5650                         const char *no_head_diff_argv[] = {
5651                                 "git", "diff", "--no-color", "--patch-with-stat",
5652                                         "--", "/dev/null", newpath, NULL
5653                         };
5655                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5656                                 return status_load_error(view, stage, newpath);
5657                 } else {
5658                         const char *index_show_argv[] = {
5659                                 "git", "diff-index", "--root", "--patch-with-stat",
5660                                         "-C", "-M", "--cached", "HEAD", "--",
5661                                         oldpath, newpath, NULL
5662                         };
5664                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5665                                 return status_load_error(view, stage, newpath);
5666                 }
5668                 if (status)
5669                         info = "Staged changes to %s";
5670                 else
5671                         info = "Staged changes";
5672                 break;
5674         case LINE_STAT_UNSTAGED:
5675         {
5676                 const char *files_show_argv[] = {
5677                         "git", "diff-files", "--root", "--patch-with-stat",
5678                                 "-C", "-M", "--", oldpath, newpath, NULL
5679                 };
5681                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5682                         return status_load_error(view, stage, newpath);
5683                 if (status)
5684                         info = "Unstaged changes to %s";
5685                 else
5686                         info = "Unstaged changes";
5687                 break;
5688         }
5689         case LINE_STAT_UNTRACKED:
5690                 if (!newpath) {
5691                         report("No file to show");
5692                         return REQ_NONE;
5693                 }
5695                 if (!suffixcmp(status->new.name, -1, "/")) {
5696                         report("Cannot display a directory");
5697                         return REQ_NONE;
5698                 }
5700                 if (!prepare_update_file(stage, newpath))
5701                         return status_load_error(view, stage, newpath);
5702                 info = "Untracked file %s";
5703                 break;
5705         case LINE_STAT_HEAD:
5706                 return REQ_NONE;
5708         default:
5709                 die("line type %d not handled in switch", line->type);
5710         }
5712         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5713         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5714         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5715                 if (status) {
5716                         stage_status = *status;
5717                 } else {
5718                         memset(&stage_status, 0, sizeof(stage_status));
5719                 }
5721                 stage_line_type = line->type;
5722                 stage_chunks = 0;
5723                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5724         }
5726         return REQ_NONE;
5729 static bool
5730 status_exists(struct status *status, enum line_type type)
5732         struct view *view = VIEW(REQ_VIEW_STATUS);
5733         unsigned long lineno;
5735         for (lineno = 0; lineno < view->lines; lineno++) {
5736                 struct line *line = &view->line[lineno];
5737                 struct status *pos = line->data;
5739                 if (line->type != type)
5740                         continue;
5741                 if (!pos && (!status || !status->status) && line[1].data) {
5742                         select_view_line(view, lineno);
5743                         return TRUE;
5744                 }
5745                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5746                         select_view_line(view, lineno);
5747                         return TRUE;
5748                 }
5749         }
5751         return FALSE;
5755 static bool
5756 status_update_prepare(struct io *io, enum line_type type)
5758         const char *staged_argv[] = {
5759                 "git", "update-index", "-z", "--index-info", NULL
5760         };
5761         const char *others_argv[] = {
5762                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5763         };
5765         switch (type) {
5766         case LINE_STAT_STAGED:
5767                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5769         case LINE_STAT_UNSTAGED:
5770         case LINE_STAT_UNTRACKED:
5771                 return io_run(io, others_argv, opt_cdup, IO_WR);
5773         default:
5774                 die("line type %d not handled in switch", type);
5775                 return FALSE;
5776         }
5779 static bool
5780 status_update_write(struct io *io, struct status *status, enum line_type type)
5782         char buf[SIZEOF_STR];
5783         size_t bufsize = 0;
5785         switch (type) {
5786         case LINE_STAT_STAGED:
5787                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5788                                         status->old.mode,
5789                                         status->old.rev,
5790                                         status->old.name, 0))
5791                         return FALSE;
5792                 break;
5794         case LINE_STAT_UNSTAGED:
5795         case LINE_STAT_UNTRACKED:
5796                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5797                         return FALSE;
5798                 break;
5800         default:
5801                 die("line type %d not handled in switch", type);
5802         }
5804         return io_write(io, buf, bufsize);
5807 static bool
5808 status_update_file(struct status *status, enum line_type type)
5810         struct io io = {};
5811         bool result;
5813         if (!status_update_prepare(&io, type))
5814                 return FALSE;
5816         result = status_update_write(&io, status, type);
5817         return io_done(&io) && result;
5820 static bool
5821 status_update_files(struct view *view, struct line *line)
5823         char buf[sizeof(view->ref)];
5824         struct io io = {};
5825         bool result = TRUE;
5826         struct line *pos = view->line + view->lines;
5827         int files = 0;
5828         int file, done;
5829         int cursor_y = -1, cursor_x = -1;
5831         if (!status_update_prepare(&io, line->type))
5832                 return FALSE;
5834         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5835                 files++;
5837         string_copy(buf, view->ref);
5838         getsyx(cursor_y, cursor_x);
5839         for (file = 0, done = 5; result && file < files; line++, file++) {
5840                 int almost_done = file * 100 / files;
5842                 if (almost_done > done) {
5843                         done = almost_done;
5844                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5845                                       file, files, done);
5846                         update_view_title(view);
5847                         setsyx(cursor_y, cursor_x);
5848                         doupdate();
5849                 }
5850                 result = status_update_write(&io, line->data, line->type);
5851         }
5852         string_copy(view->ref, buf);
5854         return io_done(&io) && result;
5857 static bool
5858 status_update(struct view *view)
5860         struct line *line = &view->line[view->lineno];
5862         assert(view->lines);
5864         if (!line->data) {
5865                 /* This should work even for the "On branch" line. */
5866                 if (line < view->line + view->lines && !line[1].data) {
5867                         report("Nothing to update");
5868                         return FALSE;
5869                 }
5871                 if (!status_update_files(view, line + 1)) {
5872                         report("Failed to update file status");
5873                         return FALSE;
5874                 }
5876         } else if (!status_update_file(line->data, line->type)) {
5877                 report("Failed to update file status");
5878                 return FALSE;
5879         }
5881         return TRUE;
5884 static bool
5885 status_revert(struct status *status, enum line_type type, bool has_none)
5887         if (!status || type != LINE_STAT_UNSTAGED) {
5888                 if (type == LINE_STAT_STAGED) {
5889                         report("Cannot revert changes to staged files");
5890                 } else if (type == LINE_STAT_UNTRACKED) {
5891                         report("Cannot revert changes to untracked files");
5892                 } else if (has_none) {
5893                         report("Nothing to revert");
5894                 } else {
5895                         report("Cannot revert changes to multiple files");
5896                 }
5898         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5899                 char mode[10] = "100644";
5900                 const char *reset_argv[] = {
5901                         "git", "update-index", "--cacheinfo", mode,
5902                                 status->old.rev, status->old.name, NULL
5903                 };
5904                 const char *checkout_argv[] = {
5905                         "git", "checkout", "--", status->old.name, NULL
5906                 };
5908                 if (status->status == 'U') {
5909                         string_format(mode, "%5o", status->old.mode);
5911                         if (status->old.mode == 0 && status->new.mode == 0) {
5912                                 reset_argv[2] = "--force-remove";
5913                                 reset_argv[3] = status->old.name;
5914                                 reset_argv[4] = NULL;
5915                         }
5917                         if (!io_run_fg(reset_argv, opt_cdup))
5918                                 return FALSE;
5919                         if (status->old.mode == 0 && status->new.mode == 0)
5920                                 return TRUE;
5921                 }
5923                 return io_run_fg(checkout_argv, opt_cdup);
5924         }
5926         return FALSE;
5929 static enum request
5930 status_request(struct view *view, enum request request, struct line *line)
5932         struct status *status = line->data;
5934         switch (request) {
5935         case REQ_STATUS_UPDATE:
5936                 if (!status_update(view))
5937                         return REQ_NONE;
5938                 break;
5940         case REQ_STATUS_REVERT:
5941                 if (!status_revert(status, line->type, status_has_none(view, line)))
5942                         return REQ_NONE;
5943                 break;
5945         case REQ_STATUS_MERGE:
5946                 if (!status || status->status != 'U') {
5947                         report("Merging only possible for files with unmerged status ('U').");
5948                         return REQ_NONE;
5949                 }
5950                 open_mergetool(status->new.name);
5951                 break;
5953         case REQ_EDIT:
5954                 if (!status)
5955                         return request;
5956                 if (status->status == 'D') {
5957                         report("File has been deleted.");
5958                         return REQ_NONE;
5959                 }
5961                 open_editor(status->new.name);
5962                 break;
5964         case REQ_VIEW_BLAME:
5965                 if (status)
5966                         opt_ref[0] = 0;
5967                 return request;
5969         case REQ_ENTER:
5970                 /* After returning the status view has been split to
5971                  * show the stage view. No further reloading is
5972                  * necessary. */
5973                 return status_enter(view, line);
5975         case REQ_REFRESH:
5976                 /* Simply reload the view. */
5977                 break;
5979         default:
5980                 return request;
5981         }
5983         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5985         return REQ_NONE;
5988 static void
5989 status_select(struct view *view, struct line *line)
5991         struct status *status = line->data;
5992         char file[SIZEOF_STR] = "all files";
5993         const char *text;
5994         const char *key;
5996         if (status && !string_format(file, "'%s'", status->new.name))
5997                 return;
5999         if (!status && line[1].type == LINE_STAT_NONE)
6000                 line++;
6002         switch (line->type) {
6003         case LINE_STAT_STAGED:
6004                 text = "Press %s to unstage %s for commit";
6005                 break;
6007         case LINE_STAT_UNSTAGED:
6008                 text = "Press %s to stage %s for commit";
6009                 break;
6011         case LINE_STAT_UNTRACKED:
6012                 text = "Press %s to stage %s for addition";
6013                 break;
6015         case LINE_STAT_HEAD:
6016         case LINE_STAT_NONE:
6017                 text = "Nothing to update";
6018                 break;
6020         default:
6021                 die("line type %d not handled in switch", line->type);
6022         }
6024         if (status && status->status == 'U') {
6025                 text = "Press %s to resolve conflict in %s";
6026                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6028         } else {
6029                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6030         }
6032         string_format(view->ref, text, key, file);
6033         if (status)
6034                 string_copy(opt_file, status->new.name);
6037 static bool
6038 status_grep(struct view *view, struct line *line)
6040         struct status *status = line->data;
6042         if (status) {
6043                 const char buf[2] = { status->status, 0 };
6044                 const char *text[] = { status->new.name, buf, NULL };
6046                 return grep_text(view, text);
6047         }
6049         return FALSE;
6052 static struct view_ops status_ops = {
6053         "file",
6054         NULL,
6055         status_open,
6056         NULL,
6057         status_draw,
6058         status_request,
6059         status_grep,
6060         status_select,
6061 };
6064 static bool
6065 stage_diff_write(struct io *io, struct line *line, struct line *end)
6067         while (line < end) {
6068                 if (!io_write(io, line->data, strlen(line->data)) ||
6069                     !io_write(io, "\n", 1))
6070                         return FALSE;
6071                 line++;
6072                 if (line->type == LINE_DIFF_CHUNK ||
6073                     line->type == LINE_DIFF_HEADER)
6074                         break;
6075         }
6077         return TRUE;
6080 static struct line *
6081 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6083         for (; view->line < line; line--)
6084                 if (line->type == type)
6085                         return line;
6087         return NULL;
6090 static bool
6091 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6093         const char *apply_argv[SIZEOF_ARG] = {
6094                 "git", "apply", "--whitespace=nowarn", NULL
6095         };
6096         struct line *diff_hdr;
6097         struct io io = {};
6098         int argc = 3;
6100         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6101         if (!diff_hdr)
6102                 return FALSE;
6104         if (!revert)
6105                 apply_argv[argc++] = "--cached";
6106         if (revert || stage_line_type == LINE_STAT_STAGED)
6107                 apply_argv[argc++] = "-R";
6108         apply_argv[argc++] = "-";
6109         apply_argv[argc++] = NULL;
6110         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6111                 return FALSE;
6113         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6114             !stage_diff_write(&io, chunk, view->line + view->lines))
6115                 chunk = NULL;
6117         io_done(&io);
6118         io_run_bg(update_index_argv);
6120         return chunk ? TRUE : FALSE;
6123 static bool
6124 stage_update(struct view *view, struct line *line)
6126         struct line *chunk = NULL;
6128         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6129                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6131         if (chunk) {
6132                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6133                         report("Failed to apply chunk");
6134                         return FALSE;
6135                 }
6137         } else if (!stage_status.status) {
6138                 view = VIEW(REQ_VIEW_STATUS);
6140                 for (line = view->line; line < view->line + view->lines; line++)
6141                         if (line->type == stage_line_type)
6142                                 break;
6144                 if (!status_update_files(view, line + 1)) {
6145                         report("Failed to update files");
6146                         return FALSE;
6147                 }
6149         } else if (!status_update_file(&stage_status, stage_line_type)) {
6150                 report("Failed to update file");
6151                 return FALSE;
6152         }
6154         return TRUE;
6157 static bool
6158 stage_revert(struct view *view, struct line *line)
6160         struct line *chunk = NULL;
6162         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6163                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6165         if (chunk) {
6166                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6167                         return FALSE;
6169                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6170                         report("Failed to revert chunk");
6171                         return FALSE;
6172                 }
6173                 return TRUE;
6175         } else {
6176                 return status_revert(stage_status.status ? &stage_status : NULL,
6177                                      stage_line_type, FALSE);
6178         }
6182 static void
6183 stage_next(struct view *view, struct line *line)
6185         int i;
6187         if (!stage_chunks) {
6188                 for (line = view->line; line < view->line + view->lines; line++) {
6189                         if (line->type != LINE_DIFF_CHUNK)
6190                                 continue;
6192                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6193                                 report("Allocation failure");
6194                                 return;
6195                         }
6197                         stage_chunk[stage_chunks++] = line - view->line;
6198                 }
6199         }
6201         for (i = 0; i < stage_chunks; i++) {
6202                 if (stage_chunk[i] > view->lineno) {
6203                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6204                         report("Chunk %d of %d", i + 1, stage_chunks);
6205                         return;
6206                 }
6207         }
6209         report("No next chunk found");
6212 static enum request
6213 stage_request(struct view *view, enum request request, struct line *line)
6215         switch (request) {
6216         case REQ_STATUS_UPDATE:
6217                 if (!stage_update(view, line))
6218                         return REQ_NONE;
6219                 break;
6221         case REQ_STATUS_REVERT:
6222                 if (!stage_revert(view, line))
6223                         return REQ_NONE;
6224                 break;
6226         case REQ_STAGE_NEXT:
6227                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6228                         report("File is untracked; press %s to add",
6229                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6230                         return REQ_NONE;
6231                 }
6232                 stage_next(view, line);
6233                 return REQ_NONE;
6235         case REQ_EDIT:
6236                 if (!stage_status.new.name[0])
6237                         return request;
6238                 if (stage_status.status == 'D') {
6239                         report("File has been deleted.");
6240                         return REQ_NONE;
6241                 }
6243                 open_editor(stage_status.new.name);
6244                 break;
6246         case REQ_REFRESH:
6247                 /* Reload everything ... */
6248                 break;
6250         case REQ_VIEW_BLAME:
6251                 if (stage_status.new.name[0]) {
6252                         string_copy(opt_file, stage_status.new.name);
6253                         opt_ref[0] = 0;
6254                 }
6255                 return request;
6257         case REQ_ENTER:
6258                 return pager_request(view, request, line);
6260         default:
6261                 return request;
6262         }
6264         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6265         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6267         /* Check whether the staged entry still exists, and close the
6268          * stage view if it doesn't. */
6269         if (!status_exists(&stage_status, stage_line_type)) {
6270                 status_restore(VIEW(REQ_VIEW_STATUS));
6271                 return REQ_VIEW_CLOSE;
6272         }
6274         if (stage_line_type == LINE_STAT_UNTRACKED) {
6275                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6276                         report("Cannot display a directory");
6277                         return REQ_NONE;
6278                 }
6280                 if (!prepare_update_file(view, stage_status.new.name)) {
6281                         report("Failed to open file: %s", strerror(errno));
6282                         return REQ_NONE;
6283                 }
6284         }
6285         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6287         return REQ_NONE;
6290 static struct view_ops stage_ops = {
6291         "line",
6292         NULL,
6293         NULL,
6294         pager_read,
6295         pager_draw,
6296         stage_request,
6297         pager_grep,
6298         pager_select,
6299 };
6302 /*
6303  * Revision graph
6304  */
6306 struct commit {
6307         char id[SIZEOF_REV];            /* SHA1 ID. */
6308         char title[128];                /* First line of the commit message. */
6309         const char *author;             /* Author of the commit. */
6310         struct time time;               /* Date from the author ident. */
6311         struct ref_list *refs;          /* Repository references. */
6312         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6313         size_t graph_size;              /* The width of the graph array. */
6314         bool has_parents;               /* Rewritten --parents seen. */
6315 };
6317 /* Size of rev graph with no  "padding" columns */
6318 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6320 struct rev_graph {
6321         struct rev_graph *prev, *next, *parents;
6322         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6323         size_t size;
6324         struct commit *commit;
6325         size_t pos;
6326         unsigned int boundary:1;
6327 };
6329 /* Parents of the commit being visualized. */
6330 static struct rev_graph graph_parents[4];
6332 /* The current stack of revisions on the graph. */
6333 static struct rev_graph graph_stacks[4] = {
6334         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6335         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6336         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6337         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6338 };
6340 static inline bool
6341 graph_parent_is_merge(struct rev_graph *graph)
6343         return graph->parents->size > 1;
6346 static inline void
6347 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6349         struct commit *commit = graph->commit;
6351         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6352                 commit->graph[commit->graph_size++] = symbol;
6355 static void
6356 clear_rev_graph(struct rev_graph *graph)
6358         graph->boundary = 0;
6359         graph->size = graph->pos = 0;
6360         graph->commit = NULL;
6361         memset(graph->parents, 0, sizeof(*graph->parents));
6364 static void
6365 done_rev_graph(struct rev_graph *graph)
6367         if (graph_parent_is_merge(graph) &&
6368             graph->pos < graph->size - 1 &&
6369             graph->next->size == graph->size + graph->parents->size - 1) {
6370                 size_t i = graph->pos + graph->parents->size - 1;
6372                 graph->commit->graph_size = i * 2;
6373                 while (i < graph->next->size - 1) {
6374                         append_to_rev_graph(graph, ' ');
6375                         append_to_rev_graph(graph, '\\');
6376                         i++;
6377                 }
6378         }
6380         clear_rev_graph(graph);
6383 static void
6384 push_rev_graph(struct rev_graph *graph, const char *parent)
6386         int i;
6388         /* "Collapse" duplicate parents lines.
6389          *
6390          * FIXME: This needs to also update update the drawn graph but
6391          * for now it just serves as a method for pruning graph lines. */
6392         for (i = 0; i < graph->size; i++)
6393                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6394                         return;
6396         if (graph->size < SIZEOF_REVITEMS) {
6397                 string_copy_rev(graph->rev[graph->size++], parent);
6398         }
6401 static chtype
6402 get_rev_graph_symbol(struct rev_graph *graph)
6404         chtype symbol;
6406         if (graph->boundary)
6407                 symbol = REVGRAPH_BOUND;
6408         else if (graph->parents->size == 0)
6409                 symbol = REVGRAPH_INIT;
6410         else if (graph_parent_is_merge(graph))
6411                 symbol = REVGRAPH_MERGE;
6412         else if (graph->pos >= graph->size)
6413                 symbol = REVGRAPH_BRANCH;
6414         else
6415                 symbol = REVGRAPH_COMMIT;
6417         return symbol;
6420 static void
6421 draw_rev_graph(struct rev_graph *graph)
6423         struct rev_filler {
6424                 chtype separator, line;
6425         };
6426         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6427         static struct rev_filler fillers[] = {
6428                 { ' ',  '|' },
6429                 { '`',  '.' },
6430                 { '\'', ' ' },
6431                 { '/',  ' ' },
6432         };
6433         chtype symbol = get_rev_graph_symbol(graph);
6434         struct rev_filler *filler;
6435         size_t i;
6437         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6438         filler = &fillers[DEFAULT];
6440         for (i = 0; i < graph->pos; i++) {
6441                 append_to_rev_graph(graph, filler->line);
6442                 if (graph_parent_is_merge(graph->prev) &&
6443                     graph->prev->pos == i)
6444                         filler = &fillers[RSHARP];
6446                 append_to_rev_graph(graph, filler->separator);
6447         }
6449         /* Place the symbol for this revision. */
6450         append_to_rev_graph(graph, symbol);
6452         if (graph->prev->size > graph->size)
6453                 filler = &fillers[RDIAG];
6454         else
6455                 filler = &fillers[DEFAULT];
6457         i++;
6459         for (; i < graph->size; i++) {
6460                 append_to_rev_graph(graph, filler->separator);
6461                 append_to_rev_graph(graph, filler->line);
6462                 if (graph_parent_is_merge(graph->prev) &&
6463                     i < graph->prev->pos + graph->parents->size)
6464                         filler = &fillers[RSHARP];
6465                 if (graph->prev->size > graph->size)
6466                         filler = &fillers[LDIAG];
6467         }
6469         if (graph->prev->size > graph->size) {
6470                 append_to_rev_graph(graph, filler->separator);
6471                 if (filler->line != ' ')
6472                         append_to_rev_graph(graph, filler->line);
6473         }
6476 /* Prepare the next rev graph */
6477 static void
6478 prepare_rev_graph(struct rev_graph *graph)
6480         size_t i;
6482         /* First, traverse all lines of revisions up to the active one. */
6483         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6484                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6485                         break;
6487                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6488         }
6490         /* Interleave the new revision parent(s). */
6491         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6492                 push_rev_graph(graph->next, graph->parents->rev[i]);
6494         /* Lastly, put any remaining revisions. */
6495         for (i = graph->pos + 1; i < graph->size; i++)
6496                 push_rev_graph(graph->next, graph->rev[i]);
6499 static void
6500 update_rev_graph(struct view *view, struct rev_graph *graph)
6502         /* If this is the finalizing update ... */
6503         if (graph->commit)
6504                 prepare_rev_graph(graph);
6506         /* Graph visualization needs a one rev look-ahead,
6507          * so the first update doesn't visualize anything. */
6508         if (!graph->prev->commit)
6509                 return;
6511         if (view->lines > 2)
6512                 view->line[view->lines - 3].dirty = 1;
6513         if (view->lines > 1)
6514                 view->line[view->lines - 2].dirty = 1;
6515         draw_rev_graph(graph->prev);
6516         done_rev_graph(graph->prev->prev);
6520 /*
6521  * Main view backend
6522  */
6524 static const char *main_argv[SIZEOF_ARG] = {
6525         "git", "log", "--no-color", "--pretty=raw", "--parents",
6526                       "--topo-order", "%(head)", NULL
6527 };
6529 static bool
6530 main_draw(struct view *view, struct line *line, unsigned int lineno)
6532         struct commit *commit = line->data;
6534         if (!commit->author)
6535                 return FALSE;
6537         if (opt_date && draw_date(view, &commit->time))
6538                 return TRUE;
6540         if (opt_author && draw_author(view, commit->author))
6541                 return TRUE;
6543         if (opt_rev_graph && commit->graph_size &&
6544             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6545                 return TRUE;
6547         if (opt_show_refs && commit->refs) {
6548                 size_t i;
6550                 for (i = 0; i < commit->refs->size; i++) {
6551                         struct ref *ref = commit->refs->refs[i];
6552                         enum line_type type;
6554                         if (ref->head)
6555                                 type = LINE_MAIN_HEAD;
6556                         else if (ref->ltag)
6557                                 type = LINE_MAIN_LOCAL_TAG;
6558                         else if (ref->tag)
6559                                 type = LINE_MAIN_TAG;
6560                         else if (ref->tracked)
6561                                 type = LINE_MAIN_TRACKED;
6562                         else if (ref->remote)
6563                                 type = LINE_MAIN_REMOTE;
6564                         else
6565                                 type = LINE_MAIN_REF;
6567                         if (draw_text(view, type, "[", TRUE) ||
6568                             draw_text(view, type, ref->name, TRUE) ||
6569                             draw_text(view, type, "]", TRUE))
6570                                 return TRUE;
6572                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6573                                 return TRUE;
6574                 }
6575         }
6577         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6578         return TRUE;
6581 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6582 static bool
6583 main_read(struct view *view, char *line)
6585         static struct rev_graph *graph = graph_stacks;
6586         enum line_type type;
6587         struct commit *commit;
6589         if (!line) {
6590                 int i;
6592                 if (!view->lines && !view->parent)
6593                         die("No revisions match the given arguments.");
6594                 if (view->lines > 0) {
6595                         commit = view->line[view->lines - 1].data;
6596                         view->line[view->lines - 1].dirty = 1;
6597                         if (!commit->author) {
6598                                 view->lines--;
6599                                 free(commit);
6600                                 graph->commit = NULL;
6601                         }
6602                 }
6603                 update_rev_graph(view, graph);
6605                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6606                         clear_rev_graph(&graph_stacks[i]);
6607                 return TRUE;
6608         }
6610         type = get_line_type(line);
6611         if (type == LINE_COMMIT) {
6612                 commit = calloc(1, sizeof(struct commit));
6613                 if (!commit)
6614                         return FALSE;
6616                 line += STRING_SIZE("commit ");
6617                 if (*line == '-') {
6618                         graph->boundary = 1;
6619                         line++;
6620                 }
6622                 string_copy_rev(commit->id, line);
6623                 commit->refs = get_ref_list(commit->id);
6624                 graph->commit = commit;
6625                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6627                 while ((line = strchr(line, ' '))) {
6628                         line++;
6629                         push_rev_graph(graph->parents, line);
6630                         commit->has_parents = TRUE;
6631                 }
6632                 return TRUE;
6633         }
6635         if (!view->lines)
6636                 return TRUE;
6637         commit = view->line[view->lines - 1].data;
6639         switch (type) {
6640         case LINE_PARENT:
6641                 if (commit->has_parents)
6642                         break;
6643                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6644                 break;
6646         case LINE_AUTHOR:
6647                 parse_author_line(line + STRING_SIZE("author "),
6648                                   &commit->author, &commit->time);
6649                 update_rev_graph(view, graph);
6650                 graph = graph->next;
6651                 break;
6653         default:
6654                 /* Fill in the commit title if it has not already been set. */
6655                 if (commit->title[0])
6656                         break;
6658                 /* Require titles to start with a non-space character at the
6659                  * offset used by git log. */
6660                 if (strncmp(line, "    ", 4))
6661                         break;
6662                 line += 4;
6663                 /* Well, if the title starts with a whitespace character,
6664                  * try to be forgiving.  Otherwise we end up with no title. */
6665                 while (isspace(*line))
6666                         line++;
6667                 if (*line == '\0')
6668                         break;
6669                 /* FIXME: More graceful handling of titles; append "..." to
6670                  * shortened titles, etc. */
6672                 string_expand(commit->title, sizeof(commit->title), line, 1);
6673                 view->line[view->lines - 1].dirty = 1;
6674         }
6676         return TRUE;
6679 static enum request
6680 main_request(struct view *view, enum request request, struct line *line)
6682         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6684         switch (request) {
6685         case REQ_ENTER:
6686                 open_view(view, REQ_VIEW_DIFF, flags);
6687                 break;
6688         case REQ_REFRESH:
6689                 load_refs();
6690                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6691                 break;
6692         default:
6693                 return request;
6694         }
6696         return REQ_NONE;
6699 static bool
6700 grep_refs(struct ref_list *list, regex_t *regex)
6702         regmatch_t pmatch;
6703         size_t i;
6705         if (!opt_show_refs || !list)
6706                 return FALSE;
6708         for (i = 0; i < list->size; i++) {
6709                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6710                         return TRUE;
6711         }
6713         return FALSE;
6716 static bool
6717 main_grep(struct view *view, struct line *line)
6719         struct commit *commit = line->data;
6720         const char *text[] = {
6721                 commit->title,
6722                 opt_author ? commit->author : "",
6723                 mkdate(&commit->time, opt_date),
6724                 NULL
6725         };
6727         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6730 static void
6731 main_select(struct view *view, struct line *line)
6733         struct commit *commit = line->data;
6735         string_copy_rev(view->ref, commit->id);
6736         string_copy_rev(ref_commit, view->ref);
6739 static struct view_ops main_ops = {
6740         "commit",
6741         main_argv,
6742         NULL,
6743         main_read,
6744         main_draw,
6745         main_request,
6746         main_grep,
6747         main_select,
6748 };
6751 /*
6752  * Unicode / UTF-8 handling
6753  *
6754  * NOTE: Much of the following code for dealing with Unicode is derived from
6755  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6756  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6757  */
6759 static inline int
6760 unicode_width(unsigned long c, int tab_size)
6762         if (c >= 0x1100 &&
6763            (c <= 0x115f                         /* Hangul Jamo */
6764             || c == 0x2329
6765             || c == 0x232a
6766             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6767                                                 /* CJK ... Yi */
6768             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6769             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6770             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6771             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6772             || (c >= 0xffe0  && c <= 0xffe6)
6773             || (c >= 0x20000 && c <= 0x2fffd)
6774             || (c >= 0x30000 && c <= 0x3fffd)))
6775                 return 2;
6777         if (c == '\t')
6778                 return tab_size;
6780         return 1;
6783 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6784  * Illegal bytes are set one. */
6785 static const unsigned char utf8_bytes[256] = {
6786         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,
6787         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,
6788         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,
6789         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,
6790         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,
6791         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,
6792         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,
6793         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,
6794 };
6796 static inline unsigned char
6797 utf8_char_length(const char *string, const char *end)
6799         int c = *(unsigned char *) string;
6801         return utf8_bytes[c];
6804 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6805 static inline unsigned long
6806 utf8_to_unicode(const char *string, size_t length)
6808         unsigned long unicode;
6810         switch (length) {
6811         case 1:
6812                 unicode  =   string[0];
6813                 break;
6814         case 2:
6815                 unicode  =  (string[0] & 0x1f) << 6;
6816                 unicode +=  (string[1] & 0x3f);
6817                 break;
6818         case 3:
6819                 unicode  =  (string[0] & 0x0f) << 12;
6820                 unicode += ((string[1] & 0x3f) << 6);
6821                 unicode +=  (string[2] & 0x3f);
6822                 break;
6823         case 4:
6824                 unicode  =  (string[0] & 0x0f) << 18;
6825                 unicode += ((string[1] & 0x3f) << 12);
6826                 unicode += ((string[2] & 0x3f) << 6);
6827                 unicode +=  (string[3] & 0x3f);
6828                 break;
6829         case 5:
6830                 unicode  =  (string[0] & 0x0f) << 24;
6831                 unicode += ((string[1] & 0x3f) << 18);
6832                 unicode += ((string[2] & 0x3f) << 12);
6833                 unicode += ((string[3] & 0x3f) << 6);
6834                 unicode +=  (string[4] & 0x3f);
6835                 break;
6836         case 6:
6837                 unicode  =  (string[0] & 0x01) << 30;
6838                 unicode += ((string[1] & 0x3f) << 24);
6839                 unicode += ((string[2] & 0x3f) << 18);
6840                 unicode += ((string[3] & 0x3f) << 12);
6841                 unicode += ((string[4] & 0x3f) << 6);
6842                 unicode +=  (string[5] & 0x3f);
6843                 break;
6844         default:
6845                 die("Invalid Unicode length");
6846         }
6848         /* Invalid characters could return the special 0xfffd value but NUL
6849          * should be just as good. */
6850         return unicode > 0xffff ? 0 : unicode;
6853 /* Calculates how much of string can be shown within the given maximum width
6854  * and sets trimmed parameter to non-zero value if all of string could not be
6855  * shown. If the reserve flag is TRUE, it will reserve at least one
6856  * trailing character, which can be useful when drawing a delimiter.
6857  *
6858  * Returns the number of bytes to output from string to satisfy max_width. */
6859 static size_t
6860 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
6862         const char *string = *start;
6863         const char *end = strchr(string, '\0');
6864         unsigned char last_bytes = 0;
6865         size_t last_ucwidth = 0;
6867         *width = 0;
6868         *trimmed = 0;
6870         while (string < end) {
6871                 unsigned char bytes = utf8_char_length(string, end);
6872                 size_t ucwidth;
6873                 unsigned long unicode;
6875                 if (string + bytes > end)
6876                         break;
6878                 /* Change representation to figure out whether
6879                  * it is a single- or double-width character. */
6881                 unicode = utf8_to_unicode(string, bytes);
6882                 /* FIXME: Graceful handling of invalid Unicode character. */
6883                 if (!unicode)
6884                         break;
6886                 ucwidth = unicode_width(unicode, tab_size);
6887                 if (skip > 0) {
6888                         skip -= ucwidth <= skip ? ucwidth : skip;
6889                         *start += bytes;
6890                 }
6891                 *width  += ucwidth;
6892                 if (*width > max_width) {
6893                         *trimmed = 1;
6894                         *width -= ucwidth;
6895                         if (reserve && *width == max_width) {
6896                                 string -= last_bytes;
6897                                 *width -= last_ucwidth;
6898                         }
6899                         break;
6900                 }
6902                 string  += bytes;
6903                 last_bytes = ucwidth ? bytes : 0;
6904                 last_ucwidth = ucwidth;
6905         }
6907         return string - *start;
6911 /*
6912  * Status management
6913  */
6915 /* Whether or not the curses interface has been initialized. */
6916 static bool cursed = FALSE;
6918 /* Terminal hacks and workarounds. */
6919 static bool use_scroll_redrawwin;
6920 static bool use_scroll_status_wclear;
6922 /* The status window is used for polling keystrokes. */
6923 static WINDOW *status_win;
6925 /* Reading from the prompt? */
6926 static bool input_mode = FALSE;
6928 static bool status_empty = FALSE;
6930 /* Update status and title window. */
6931 static void
6932 report(const char *msg, ...)
6934         struct view *view = display[current_view];
6936         if (input_mode)
6937                 return;
6939         if (!view) {
6940                 char buf[SIZEOF_STR];
6941                 va_list args;
6943                 va_start(args, msg);
6944                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6945                         buf[sizeof(buf) - 1] = 0;
6946                         buf[sizeof(buf) - 2] = '.';
6947                         buf[sizeof(buf) - 3] = '.';
6948                         buf[sizeof(buf) - 4] = '.';
6949                 }
6950                 va_end(args);
6951                 die("%s", buf);
6952         }
6954         if (!status_empty || *msg) {
6955                 va_list args;
6957                 va_start(args, msg);
6959                 wmove(status_win, 0, 0);
6960                 if (view->has_scrolled && use_scroll_status_wclear)
6961                         wclear(status_win);
6962                 if (*msg) {
6963                         vwprintw(status_win, msg, args);
6964                         status_empty = FALSE;
6965                 } else {
6966                         status_empty = TRUE;
6967                 }
6968                 wclrtoeol(status_win);
6969                 wnoutrefresh(status_win);
6971                 va_end(args);
6972         }
6974         update_view_title(view);
6977 static void
6978 init_display(void)
6980         const char *term;
6981         int x, y;
6983         /* Initialize the curses library */
6984         if (isatty(STDIN_FILENO)) {
6985                 cursed = !!initscr();
6986                 opt_tty = stdin;
6987         } else {
6988                 /* Leave stdin and stdout alone when acting as a pager. */
6989                 opt_tty = fopen("/dev/tty", "r+");
6990                 if (!opt_tty)
6991                         die("Failed to open /dev/tty");
6992                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6993         }
6995         if (!cursed)
6996                 die("Failed to initialize curses");
6998         nonl();         /* Disable conversion and detect newlines from input. */
6999         cbreak();       /* Take input chars one at a time, no wait for \n */
7000         noecho();       /* Don't echo input */
7001         leaveok(stdscr, FALSE);
7003         if (has_colors())
7004                 init_colors();
7006         getmaxyx(stdscr, y, x);
7007         status_win = newwin(1, 0, y - 1, 0);
7008         if (!status_win)
7009                 die("Failed to create status window");
7011         /* Enable keyboard mapping */
7012         keypad(status_win, TRUE);
7013         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7015         TABSIZE = opt_tab_size;
7017         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7018         if (term && !strcmp(term, "gnome-terminal")) {
7019                 /* In the gnome-terminal-emulator, the message from
7020                  * scrolling up one line when impossible followed by
7021                  * scrolling down one line causes corruption of the
7022                  * status line. This is fixed by calling wclear. */
7023                 use_scroll_status_wclear = TRUE;
7024                 use_scroll_redrawwin = FALSE;
7026         } else if (term && !strcmp(term, "xrvt-xpm")) {
7027                 /* No problems with full optimizations in xrvt-(unicode)
7028                  * and aterm. */
7029                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7031         } else {
7032                 /* When scrolling in (u)xterm the last line in the
7033                  * scrolling direction will update slowly. */
7034                 use_scroll_redrawwin = TRUE;
7035                 use_scroll_status_wclear = FALSE;
7036         }
7039 static int
7040 get_input(int prompt_position)
7042         struct view *view;
7043         int i, key, cursor_y, cursor_x;
7044         bool loading = FALSE;
7046         if (prompt_position)
7047                 input_mode = TRUE;
7049         while (TRUE) {
7050                 foreach_view (view, i) {
7051                         update_view(view);
7052                         if (view_is_displayed(view) && view->has_scrolled &&
7053                             use_scroll_redrawwin)
7054                                 redrawwin(view->win);
7055                         view->has_scrolled = FALSE;
7056                         if (view->pipe)
7057                                 loading = TRUE;
7058                 }
7060                 /* Update the cursor position. */
7061                 if (prompt_position) {
7062                         getbegyx(status_win, cursor_y, cursor_x);
7063                         cursor_x = prompt_position;
7064                 } else {
7065                         view = display[current_view];
7066                         getbegyx(view->win, cursor_y, cursor_x);
7067                         cursor_x = view->width - 1;
7068                         cursor_y += view->lineno - view->offset;
7069                 }
7070                 setsyx(cursor_y, cursor_x);
7072                 /* Refresh, accept single keystroke of input */
7073                 doupdate();
7074                 nodelay(status_win, loading);
7075                 key = wgetch(status_win);
7077                 /* wgetch() with nodelay() enabled returns ERR when
7078                  * there's no input. */
7079                 if (key == ERR) {
7081                 } else if (key == KEY_RESIZE) {
7082                         int height, width;
7084                         getmaxyx(stdscr, height, width);
7086                         wresize(status_win, 1, width);
7087                         mvwin(status_win, height - 1, 0);
7088                         wnoutrefresh(status_win);
7089                         resize_display();
7090                         redraw_display(TRUE);
7092                 } else {
7093                         input_mode = FALSE;
7094                         return key;
7095                 }
7096         }
7099 static char *
7100 prompt_input(const char *prompt, input_handler handler, void *data)
7102         enum input_status status = INPUT_OK;
7103         static char buf[SIZEOF_STR];
7104         size_t pos = 0;
7106         buf[pos] = 0;
7108         while (status == INPUT_OK || status == INPUT_SKIP) {
7109                 int key;
7111                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7112                 wclrtoeol(status_win);
7114                 key = get_input(pos + 1);
7115                 switch (key) {
7116                 case KEY_RETURN:
7117                 case KEY_ENTER:
7118                 case '\n':
7119                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7120                         break;
7122                 case KEY_BACKSPACE:
7123                         if (pos > 0)
7124                                 buf[--pos] = 0;
7125                         else
7126                                 status = INPUT_CANCEL;
7127                         break;
7129                 case KEY_ESC:
7130                         status = INPUT_CANCEL;
7131                         break;
7133                 default:
7134                         if (pos >= sizeof(buf)) {
7135                                 report("Input string too long");
7136                                 return NULL;
7137                         }
7139                         status = handler(data, buf, key);
7140                         if (status == INPUT_OK)
7141                                 buf[pos++] = (char) key;
7142                 }
7143         }
7145         /* Clear the status window */
7146         status_empty = FALSE;
7147         report("");
7149         if (status == INPUT_CANCEL)
7150                 return NULL;
7152         buf[pos++] = 0;
7154         return buf;
7157 static enum input_status
7158 prompt_yesno_handler(void *data, char *buf, int c)
7160         if (c == 'y' || c == 'Y')
7161                 return INPUT_STOP;
7162         if (c == 'n' || c == 'N')
7163                 return INPUT_CANCEL;
7164         return INPUT_SKIP;
7167 static bool
7168 prompt_yesno(const char *prompt)
7170         char prompt2[SIZEOF_STR];
7172         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7173                 return FALSE;
7175         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7178 static enum input_status
7179 read_prompt_handler(void *data, char *buf, int c)
7181         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7184 static char *
7185 read_prompt(const char *prompt)
7187         return prompt_input(prompt, read_prompt_handler, NULL);
7190 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7192         enum input_status status = INPUT_OK;
7193         int size = 0;
7195         while (items[size].text)
7196                 size++;
7198         while (status == INPUT_OK) {
7199                 const struct menu_item *item = &items[*selected];
7200                 int key;
7201                 int i;
7203                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7204                           prompt, *selected + 1, size);
7205                 if (item->hotkey)
7206                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7207                 wprintw(status_win, "%s", item->text);
7208                 wclrtoeol(status_win);
7210                 key = get_input(COLS - 1);
7211                 switch (key) {
7212                 case KEY_RETURN:
7213                 case KEY_ENTER:
7214                 case '\n':
7215                         status = INPUT_STOP;
7216                         break;
7218                 case KEY_LEFT:
7219                 case KEY_UP:
7220                         *selected = *selected - 1;
7221                         if (*selected < 0)
7222                                 *selected = size - 1;
7223                         break;
7225                 case KEY_RIGHT:
7226                 case KEY_DOWN:
7227                         *selected = (*selected + 1) % size;
7228                         break;
7230                 case KEY_ESC:
7231                         status = INPUT_CANCEL;
7232                         break;
7234                 default:
7235                         for (i = 0; items[i].text; i++)
7236                                 if (items[i].hotkey == key) {
7237                                         *selected = i;
7238                                         status = INPUT_STOP;
7239                                         break;
7240                                 }
7241                 }
7242         }
7244         /* Clear the status window */
7245         status_empty = FALSE;
7246         report("");
7248         return status != INPUT_CANCEL;
7251 /*
7252  * Repository properties
7253  */
7255 static struct ref **refs = NULL;
7256 static size_t refs_size = 0;
7257 static struct ref *refs_head = NULL;
7259 static struct ref_list **ref_lists = NULL;
7260 static size_t ref_lists_size = 0;
7262 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7263 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7264 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7266 static int
7267 compare_refs(const void *ref1_, const void *ref2_)
7269         const struct ref *ref1 = *(const struct ref **)ref1_;
7270         const struct ref *ref2 = *(const struct ref **)ref2_;
7272         if (ref1->tag != ref2->tag)
7273                 return ref2->tag - ref1->tag;
7274         if (ref1->ltag != ref2->ltag)
7275                 return ref2->ltag - ref2->ltag;
7276         if (ref1->head != ref2->head)
7277                 return ref2->head - ref1->head;
7278         if (ref1->tracked != ref2->tracked)
7279                 return ref2->tracked - ref1->tracked;
7280         if (ref1->remote != ref2->remote)
7281                 return ref2->remote - ref1->remote;
7282         return strcmp(ref1->name, ref2->name);
7285 static void
7286 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7288         size_t i;
7290         for (i = 0; i < refs_size; i++)
7291                 if (!visitor(data, refs[i]))
7292                         break;
7295 static struct ref *
7296 get_ref_head()
7298         return refs_head;
7301 static struct ref_list *
7302 get_ref_list(const char *id)
7304         struct ref_list *list;
7305         size_t i;
7307         for (i = 0; i < ref_lists_size; i++)
7308                 if (!strcmp(id, ref_lists[i]->id))
7309                         return ref_lists[i];
7311         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7312                 return NULL;
7313         list = calloc(1, sizeof(*list));
7314         if (!list)
7315                 return NULL;
7317         for (i = 0; i < refs_size; i++) {
7318                 if (!strcmp(id, refs[i]->id) &&
7319                     realloc_refs_list(&list->refs, list->size, 1))
7320                         list->refs[list->size++] = refs[i];
7321         }
7323         if (!list->refs) {
7324                 free(list);
7325                 return NULL;
7326         }
7328         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7329         ref_lists[ref_lists_size++] = list;
7330         return list;
7333 static int
7334 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7336         struct ref *ref = NULL;
7337         bool tag = FALSE;
7338         bool ltag = FALSE;
7339         bool remote = FALSE;
7340         bool tracked = FALSE;
7341         bool head = FALSE;
7342         int from = 0, to = refs_size - 1;
7344         if (!prefixcmp(name, "refs/tags/")) {
7345                 if (!suffixcmp(name, namelen, "^{}")) {
7346                         namelen -= 3;
7347                         name[namelen] = 0;
7348                 } else {
7349                         ltag = TRUE;
7350                 }
7352                 tag = TRUE;
7353                 namelen -= STRING_SIZE("refs/tags/");
7354                 name    += STRING_SIZE("refs/tags/");
7356         } else if (!prefixcmp(name, "refs/remotes/")) {
7357                 remote = TRUE;
7358                 namelen -= STRING_SIZE("refs/remotes/");
7359                 name    += STRING_SIZE("refs/remotes/");
7360                 tracked  = !strcmp(opt_remote, name);
7362         } else if (!prefixcmp(name, "refs/heads/")) {
7363                 namelen -= STRING_SIZE("refs/heads/");
7364                 name    += STRING_SIZE("refs/heads/");
7365                 if (!strncmp(opt_head, name, namelen))
7366                         return OK;
7368         } else if (!strcmp(name, "HEAD")) {
7369                 head     = TRUE;
7370                 if (*opt_head) {
7371                         namelen  = strlen(opt_head);
7372                         name     = opt_head;
7373                 }
7374         }
7376         /* If we are reloading or it's an annotated tag, replace the
7377          * previous SHA1 with the resolved commit id; relies on the fact
7378          * git-ls-remote lists the commit id of an annotated tag right
7379          * before the commit id it points to. */
7380         while (from <= to) {
7381                 size_t pos = (to + from) / 2;
7382                 int cmp = strcmp(name, refs[pos]->name);
7384                 if (!cmp) {
7385                         ref = refs[pos];
7386                         break;
7387                 }
7389                 if (cmp < 0)
7390                         to = pos - 1;
7391                 else
7392                         from = pos + 1;
7393         }
7395         if (!ref) {
7396                 if (!realloc_refs(&refs, refs_size, 1))
7397                         return ERR;
7398                 ref = calloc(1, sizeof(*ref) + namelen);
7399                 if (!ref)
7400                         return ERR;
7401                 memmove(refs + from + 1, refs + from,
7402                         (refs_size - from) * sizeof(*refs));
7403                 refs[from] = ref;
7404                 strncpy(ref->name, name, namelen);
7405                 refs_size++;
7406         }
7408         ref->head = head;
7409         ref->tag = tag;
7410         ref->ltag = ltag;
7411         ref->remote = remote;
7412         ref->tracked = tracked;
7413         string_copy_rev(ref->id, id);
7415         if (head)
7416                 refs_head = ref;
7417         return OK;
7420 static int
7421 load_refs(void)
7423         const char *head_argv[] = {
7424                 "git", "symbolic-ref", "HEAD", NULL
7425         };
7426         static const char *ls_remote_argv[SIZEOF_ARG] = {
7427                 "git", "ls-remote", opt_git_dir, NULL
7428         };
7429         static bool init = FALSE;
7430         size_t i;
7432         if (!init) {
7433                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7434                 init = TRUE;
7435         }
7437         if (!*opt_git_dir)
7438                 return OK;
7440         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7441             !prefixcmp(opt_head, "refs/heads/")) {
7442                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7444                 memmove(opt_head, offset, strlen(offset) + 1);
7445         }
7447         refs_head = NULL;
7448         for (i = 0; i < refs_size; i++)
7449                 refs[i]->id[0] = 0;
7451         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7452                 return ERR;
7454         /* Update the ref lists to reflect changes. */
7455         for (i = 0; i < ref_lists_size; i++) {
7456                 struct ref_list *list = ref_lists[i];
7457                 size_t old, new;
7459                 for (old = new = 0; old < list->size; old++)
7460                         if (!strcmp(list->id, list->refs[old]->id))
7461                                 list->refs[new++] = list->refs[old];
7462                 list->size = new;
7463         }
7465         return OK;
7468 static void
7469 set_remote_branch(const char *name, const char *value, size_t valuelen)
7471         if (!strcmp(name, ".remote")) {
7472                 string_ncopy(opt_remote, value, valuelen);
7474         } else if (*opt_remote && !strcmp(name, ".merge")) {
7475                 size_t from = strlen(opt_remote);
7477                 if (!prefixcmp(value, "refs/heads/"))
7478                         value += STRING_SIZE("refs/heads/");
7480                 if (!string_format_from(opt_remote, &from, "/%s", value))
7481                         opt_remote[0] = 0;
7482         }
7485 static void
7486 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7488         const char *argv[SIZEOF_ARG] = { name, "=" };
7489         int argc = 1 + (cmd == option_set_command);
7490         int error = ERR;
7492         if (!argv_from_string(argv, &argc, value))
7493                 config_msg = "Too many option arguments";
7494         else
7495                 error = cmd(argc, argv);
7497         if (error == ERR)
7498                 warn("Option 'tig.%s': %s", name, config_msg);
7501 static bool
7502 set_environment_variable(const char *name, const char *value)
7504         size_t len = strlen(name) + 1 + strlen(value) + 1;
7505         char *env = malloc(len);
7507         if (env &&
7508             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7509             putenv(env) == 0)
7510                 return TRUE;
7511         free(env);
7512         return FALSE;
7515 static void
7516 set_work_tree(const char *value)
7518         char cwd[SIZEOF_STR];
7520         if (!getcwd(cwd, sizeof(cwd)))
7521                 die("Failed to get cwd path: %s", strerror(errno));
7522         if (chdir(opt_git_dir) < 0)
7523                 die("Failed to chdir(%s): %s", strerror(errno));
7524         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7525                 die("Failed to get git path: %s", strerror(errno));
7526         if (chdir(cwd) < 0)
7527                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7528         if (chdir(value) < 0)
7529                 die("Failed to chdir(%s): %s", value, strerror(errno));
7530         if (!getcwd(cwd, sizeof(cwd)))
7531                 die("Failed to get cwd path: %s", strerror(errno));
7532         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7533                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7534         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7535                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7536         opt_is_inside_work_tree = TRUE;
7539 static int
7540 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7542         if (!strcmp(name, "i18n.commitencoding"))
7543                 string_ncopy(opt_encoding, value, valuelen);
7545         else if (!strcmp(name, "core.editor"))
7546                 string_ncopy(opt_editor, value, valuelen);
7548         else if (!strcmp(name, "core.worktree"))
7549                 set_work_tree(value);
7551         else if (!prefixcmp(name, "tig.color."))
7552                 set_repo_config_option(name + 10, value, option_color_command);
7554         else if (!prefixcmp(name, "tig.bind."))
7555                 set_repo_config_option(name + 9, value, option_bind_command);
7557         else if (!prefixcmp(name, "tig."))
7558                 set_repo_config_option(name + 4, value, option_set_command);
7560         else if (*opt_head && !prefixcmp(name, "branch.") &&
7561                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7562                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7564         return OK;
7567 static int
7568 load_git_config(void)
7570         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7572         return io_run_load(config_list_argv, "=", read_repo_config_option);
7575 static int
7576 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7578         if (!opt_git_dir[0]) {
7579                 string_ncopy(opt_git_dir, name, namelen);
7581         } else if (opt_is_inside_work_tree == -1) {
7582                 /* This can be 3 different values depending on the
7583                  * version of git being used. If git-rev-parse does not
7584                  * understand --is-inside-work-tree it will simply echo
7585                  * the option else either "true" or "false" is printed.
7586                  * Default to true for the unknown case. */
7587                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7589         } else if (*name == '.') {
7590                 string_ncopy(opt_cdup, name, namelen);
7592         } else {
7593                 string_ncopy(opt_prefix, name, namelen);
7594         }
7596         return OK;
7599 static int
7600 load_repo_info(void)
7602         const char *rev_parse_argv[] = {
7603                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7604                         "--show-cdup", "--show-prefix", NULL
7605         };
7607         return io_run_load(rev_parse_argv, "=", read_repo_info);
7611 /*
7612  * Main
7613  */
7615 static const char usage[] =
7616 "tig " TIG_VERSION " (" __DATE__ ")\n"
7617 "\n"
7618 "Usage: tig        [options] [revs] [--] [paths]\n"
7619 "   or: tig show   [options] [revs] [--] [paths]\n"
7620 "   or: tig blame  [rev] path\n"
7621 "   or: tig status\n"
7622 "   or: tig <      [git command output]\n"
7623 "\n"
7624 "Options:\n"
7625 "  -v, --version   Show version and exit\n"
7626 "  -h, --help      Show help message and exit";
7628 static void __NORETURN
7629 quit(int sig)
7631         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7632         if (cursed)
7633                 endwin();
7634         exit(0);
7637 static void __NORETURN
7638 die(const char *err, ...)
7640         va_list args;
7642         endwin();
7644         va_start(args, err);
7645         fputs("tig: ", stderr);
7646         vfprintf(stderr, err, args);
7647         fputs("\n", stderr);
7648         va_end(args);
7650         exit(1);
7653 static void
7654 warn(const char *msg, ...)
7656         va_list args;
7658         va_start(args, msg);
7659         fputs("tig warning: ", stderr);
7660         vfprintf(stderr, msg, args);
7661         fputs("\n", stderr);
7662         va_end(args);
7665 static enum request
7666 parse_options(int argc, const char *argv[])
7668         enum request request = REQ_VIEW_MAIN;
7669         const char *subcommand;
7670         bool seen_dashdash = FALSE;
7671         /* XXX: This is vulnerable to the user overriding options
7672          * required for the main view parser. */
7673         const char *custom_argv[SIZEOF_ARG] = {
7674                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7675                         "--topo-order", NULL
7676         };
7677         int i, j = 6;
7679         if (!isatty(STDIN_FILENO)) {
7680                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7681                 return REQ_VIEW_PAGER;
7682         }
7684         if (argc <= 1)
7685                 return REQ_NONE;
7687         subcommand = argv[1];
7688         if (!strcmp(subcommand, "status")) {
7689                 if (argc > 2)
7690                         warn("ignoring arguments after `%s'", subcommand);
7691                 return REQ_VIEW_STATUS;
7693         } else if (!strcmp(subcommand, "blame")) {
7694                 if (argc <= 2 || argc > 4)
7695                         die("invalid number of options to blame\n\n%s", usage);
7697                 i = 2;
7698                 if (argc == 4) {
7699                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7700                         i++;
7701                 }
7703                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7704                 return REQ_VIEW_BLAME;
7706         } else if (!strcmp(subcommand, "show")) {
7707                 request = REQ_VIEW_DIFF;
7709         } else {
7710                 subcommand = NULL;
7711         }
7713         if (subcommand) {
7714                 custom_argv[1] = subcommand;
7715                 j = 2;
7716         }
7718         for (i = 1 + !!subcommand; i < argc; i++) {
7719                 const char *opt = argv[i];
7721                 if (seen_dashdash || !strcmp(opt, "--")) {
7722                         seen_dashdash = TRUE;
7724                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7725                         printf("tig version %s\n", TIG_VERSION);
7726                         quit(0);
7728                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7729                         printf("%s\n", usage);
7730                         quit(0);
7731                 }
7733                 custom_argv[j++] = opt;
7734                 if (j >= ARRAY_SIZE(custom_argv))
7735                         die("command too long");
7736         }
7738         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7739                 die("Failed to format arguments");
7741         return request;
7744 int
7745 main(int argc, const char *argv[])
7747         const char *codeset = "UTF-8";
7748         enum request request = parse_options(argc, argv);
7749         struct view *view;
7750         size_t i;
7752         signal(SIGINT, quit);
7753         signal(SIGPIPE, SIG_IGN);
7755         if (setlocale(LC_ALL, "")) {
7756                 codeset = nl_langinfo(CODESET);
7757         }
7759         if (load_repo_info() == ERR)
7760                 die("Failed to load repo info.");
7762         if (load_options() == ERR)
7763                 die("Failed to load user config.");
7765         if (load_git_config() == ERR)
7766                 die("Failed to load repo config.");
7768         /* Require a git repository unless when running in pager mode. */
7769         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7770                 die("Not a git repository");
7772         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7773                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7774                 if (opt_iconv_in == ICONV_NONE)
7775                         die("Failed to initialize character set conversion");
7776         }
7778         if (codeset && strcmp(codeset, "UTF-8")) {
7779                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7780                 if (opt_iconv_out == ICONV_NONE)
7781                         die("Failed to initialize character set conversion");
7782         }
7784         if (load_refs() == ERR)
7785                 die("Failed to load refs.");
7787         foreach_view (view, i)
7788                 argv_from_env(view->ops->argv, view->cmd_env);
7790         init_display();
7792         if (request != REQ_NONE)
7793                 open_view(NULL, request, OPEN_PREPARED);
7794         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7796         while (view_driver(display[current_view], request)) {
7797                 int key = get_input(0);
7799                 view = display[current_view];
7800                 request = get_keybinding(view->keymap, key);
7802                 /* Some low-level request handling. This keeps access to
7803                  * status_win restricted. */
7804                 switch (request) {
7805                 case REQ_PROMPT:
7806                 {
7807                         char *cmd = read_prompt(":");
7809                         if (cmd && isdigit(*cmd)) {
7810                                 int lineno = view->lineno + 1;
7812                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7813                                         select_view_line(view, lineno - 1);
7814                                         report("");
7815                                 } else {
7816                                         report("Unable to parse '%s' as a line number", cmd);
7817                                 }
7819                         } else if (cmd) {
7820                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7821                                 const char *argv[SIZEOF_ARG] = { "git" };
7822                                 int argc = 1;
7824                                 /* When running random commands, initially show the
7825                                  * command in the title. However, it maybe later be
7826                                  * overwritten if a commit line is selected. */
7827                                 string_ncopy(next->ref, cmd, strlen(cmd));
7829                                 if (!argv_from_string(argv, &argc, cmd)) {
7830                                         report("Too many arguments");
7831                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7832                                         report("Failed to format command");
7833                                 } else {
7834                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7835                                 }
7836                         }
7838                         request = REQ_NONE;
7839                         break;
7840                 }
7841                 case REQ_SEARCH:
7842                 case REQ_SEARCH_BACK:
7843                 {
7844                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7845                         char *search = read_prompt(prompt);
7847                         if (search)
7848                                 string_ncopy(opt_search, search, strlen(search));
7849                         else if (*opt_search)
7850                                 request = request == REQ_SEARCH ?
7851                                         REQ_FIND_NEXT :
7852                                         REQ_FIND_PREV;
7853                         else
7854                                 request = REQ_NONE;
7855                         break;
7856                 }
7857                 default:
7858                         break;
7859                 }
7860         }
7862         quit(0);
7864         return 0;