Code

Use FALSE marco instead of C++ false value
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 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 void set_nonblocking_input(bool loading);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
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_list *get_ref_list(const char *id);
141 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
142 static int load_refs(void);
144 enum format_flags {
145         FORMAT_ALL,             /* Perform replacement in all arguments. */
146         FORMAT_DASH,            /* Perform replacement up until "--". */
147         FORMAT_NONE             /* No replacement should be performed. */
148 };
150 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
152 enum input_status {
153         INPUT_OK,
154         INPUT_SKIP,
155         INPUT_STOP,
156         INPUT_CANCEL
157 };
159 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
161 static char *prompt_input(const char *prompt, input_handler handler, void *data);
162 static bool prompt_yesno(const char *prompt);
164 struct menu_item {
165         int hotkey;
166         const char *text;
167         void *data;
168 };
170 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
172 /*
173  * Allocation helpers ... Entering macro hell to never be seen again.
174  */
176 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
177 static type *                                                                   \
178 name(type **mem, size_t size, size_t increase)                                  \
179 {                                                                               \
180         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
181         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
182         type *tmp = *mem;                                                       \
183                                                                                 \
184         if (mem == NULL || num_chunks != num_chunks_new) {                      \
185                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
186                 if (tmp)                                                        \
187                         *mem = tmp;                                             \
188         }                                                                       \
189                                                                                 \
190         return tmp;                                                             \
193 /*
194  * String helpers
195  */
197 static inline void
198 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
200         if (srclen > dstlen - 1)
201                 srclen = dstlen - 1;
203         strncpy(dst, src, srclen);
204         dst[srclen] = 0;
207 /* Shorthands for safely copying into a fixed buffer. */
209 #define string_copy(dst, src) \
210         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
212 #define string_ncopy(dst, src, srclen) \
213         string_ncopy_do(dst, sizeof(dst), src, srclen)
215 #define string_copy_rev(dst, src) \
216         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
218 #define string_add(dst, from, src) \
219         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
221 static void
222 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
224         size_t size, pos;
226         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
227                 if (src[pos] == '\t') {
228                         size_t expanded = tabsize - (size % tabsize);
230                         if (expanded + size >= dstlen - 1)
231                                 expanded = dstlen - size - 1;
232                         memcpy(dst + size, "        ", expanded);
233                         size += expanded;
234                 } else {
235                         dst[size++] = src[pos];
236                 }
237         }
239         dst[size] = 0;
242 static char *
243 chomp_string(char *name)
245         int namelen;
247         while (isspace(*name))
248                 name++;
250         namelen = strlen(name) - 1;
251         while (namelen > 0 && isspace(name[namelen]))
252                 name[namelen--] = 0;
254         return name;
257 static bool
258 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
260         va_list args;
261         size_t pos = bufpos ? *bufpos : 0;
263         va_start(args, fmt);
264         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
265         va_end(args);
267         if (bufpos)
268                 *bufpos = pos;
270         return pos >= bufsize ? FALSE : TRUE;
273 #define string_format(buf, fmt, args...) \
274         string_nformat(buf, sizeof(buf), NULL, fmt, args)
276 #define string_format_from(buf, from, fmt, args...) \
277         string_nformat(buf, sizeof(buf), from, fmt, args)
279 static int
280 string_enum_compare(const char *str1, const char *str2, int len)
282         size_t i;
284 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
286         /* Diff-Header == DIFF_HEADER */
287         for (i = 0; i < len; i++) {
288                 if (toupper(str1[i]) == toupper(str2[i]))
289                         continue;
291                 if (string_enum_sep(str1[i]) &&
292                     string_enum_sep(str2[i]))
293                         continue;
295                 return str1[i] - str2[i];
296         }
298         return 0;
301 #define enum_equals(entry, str, len) \
302         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
304 struct enum_map {
305         const char *name;
306         int namelen;
307         int value;
308 };
310 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
312 static char *
313 enum_map_name(const char *name, size_t namelen)
315         static char buf[SIZEOF_STR];
316         int bufpos;
318         for (bufpos = 0; bufpos <= namelen; bufpos++) {
319                 buf[bufpos] = tolower(name[bufpos]);
320                 if (buf[bufpos] == '_')
321                         buf[bufpos] = '-';
322         }
324         buf[bufpos] = 0;
325         return buf;
328 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
330 static bool
331 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
333         size_t namelen = strlen(name);
334         int i;
336         for (i = 0; i < map_size; i++)
337                 if (enum_equals(map[i], name, namelen)) {
338                         *value = map[i].value;
339                         return TRUE;
340                 }
342         return FALSE;
345 #define map_enum(attr, map, name) \
346         map_enum_do(map, ARRAY_SIZE(map), attr, name)
348 #define prefixcmp(str1, str2) \
349         strncmp(str1, str2, STRING_SIZE(str2))
351 static inline int
352 suffixcmp(const char *str, int slen, const char *suffix)
354         size_t len = slen >= 0 ? slen : strlen(str);
355         size_t suffixlen = strlen(suffix);
357         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
361 /*
362  * What value of "tz" was in effect back then at "time" in the
363  * local timezone?
364  */
365 static int local_tzoffset(time_t time)
367         time_t t, t_local;
368         struct tm tm;
369         int offset, eastwest; 
371         t = time;
372         localtime_r(&t, &tm);
373         t_local = mktime(&tm);
375         if (t_local < t) {
376                 eastwest = -1;
377                 offset = t - t_local;
378         } else {
379                 eastwest = 1;
380                 offset = t_local - t;
381         }
382         offset /= 60; /* in minutes */
383         offset = (offset % 60) + ((offset / 60) * 100);
384         return offset * eastwest;
387 #define DATE_INFO \
388         DATE_(NO), \
389         DATE_(DEFAULT), \
390         DATE_(RELATIVE), \
391         DATE_(SHORT)
393 enum date {
394 #define DATE_(name) DATE_##name
395         DATE_INFO
396 #undef  DATE_
397 };
399 static const struct enum_map date_map[] = {
400 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
401         DATE_INFO
402 #undef  DATE_
403 };
405 static const char *
406 string_date(const time_t *time, enum date date)
408         static char buf[DATE_COLS + 1];
409         static const struct enum_map reldate[] = {
410                 { "second", 1,                  60 * 2 },
411                 { "minute", 60,                 60 * 60 * 2 },
412                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
413                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
414                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
415                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
416         };
417         struct tm tm;
419         if (date == DATE_RELATIVE) {
420                 struct timeval now;
421                 time_t date = *time + local_tzoffset(*time);
422                 time_t seconds;
423                 int i;
425                 gettimeofday(&now, NULL);
426                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
427                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
428                         if (seconds >= reldate[i].value)
429                                 continue;
431                         seconds /= reldate[i].namelen;
432                         if (!string_format(buf, "%ld %s%s %s",
433                                            seconds, reldate[i].name,
434                                            seconds > 1 ? "s" : "",
435                                            now.tv_sec >= date ? "ago" : "ahead"))
436                                 break;
437                         return buf;
438                 }
439         }
441         gmtime_r(time, &tm);
442         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
446 #define AUTHOR_VALUES \
447         AUTHOR_(NO), \
448         AUTHOR_(FULL), \
449         AUTHOR_(ABBREVIATED)
451 enum author {
452 #define AUTHOR_(name) AUTHOR_##name
453         AUTHOR_VALUES,
454 #undef  AUTHOR_
455         AUTHOR_DEFAULT = AUTHOR_FULL
456 };
458 static const struct enum_map author_map[] = {
459 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
460         AUTHOR_VALUES
461 #undef  AUTHOR_
462 };
464 /* FIXME: Handle multi-byte and multi-column characters. */
465 static const char *
466 get_author_initials(const char *author, size_t max_columns)
468         static char initials[AUTHOR_COLS];
469         size_t pos;
471 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
473         memset(initials, 0, sizeof(initials));
474         for (pos = 0; *author && pos < sizeof(initials) - 1; author++, pos++) {
475                 while (is_initial_sep(*author))
476                         author++;
477                 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
478                 while (*author && author[1] && !is_initial_sep(author[1]))
479                         author++;
480         }
482         return initials;
486 static bool
487 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
489         int valuelen;
491         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
492                 bool advance = cmd[valuelen] != 0;
494                 cmd[valuelen] = 0;
495                 argv[(*argc)++] = chomp_string(cmd);
496                 cmd = chomp_string(cmd + valuelen + advance);
497         }
499         if (*argc < SIZEOF_ARG)
500                 argv[*argc] = NULL;
501         return *argc < SIZEOF_ARG;
504 static void
505 argv_from_env(const char **argv, const char *name)
507         char *env = argv ? getenv(name) : NULL;
508         int argc = 0;
510         if (env && *env)
511                 env = strdup(env);
512         if (env && !argv_from_string(argv, &argc, env))
513                 die("Too many arguments in the `%s` environment variable", name);
517 /*
518  * Executing external commands.
519  */
521 enum io_type {
522         IO_FD,                  /* File descriptor based IO. */
523         IO_BG,                  /* Execute command in the background. */
524         IO_FG,                  /* Execute command with same std{in,out,err}. */
525         IO_RD,                  /* Read only fork+exec IO. */
526         IO_WR,                  /* Write only fork+exec IO. */
527         IO_AP,                  /* Append fork+exec output to file. */
528 };
530 struct io {
531         enum io_type type;      /* The requested type of pipe. */
532         const char *dir;        /* Directory from which to execute. */
533         pid_t pid;              /* Pipe for reading or writing. */
534         int pipe;               /* Pipe end for reading or writing. */
535         int error;              /* Error status. */
536         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
537         char *buf;              /* Read buffer. */
538         size_t bufalloc;        /* Allocated buffer size. */
539         size_t bufsize;         /* Buffer content size. */
540         char *bufpos;           /* Current buffer position. */
541         unsigned int eof:1;     /* Has end of file been reached. */
542 };
544 static void
545 reset_io(struct io *io)
547         io->pipe = -1;
548         io->pid = 0;
549         io->buf = io->bufpos = NULL;
550         io->bufalloc = io->bufsize = 0;
551         io->error = 0;
552         io->eof = 0;
555 static void
556 init_io(struct io *io, const char *dir, enum io_type type)
558         reset_io(io);
559         io->type = type;
560         io->dir = dir;
563 static bool
564 init_io_rd(struct io *io, const char *argv[], const char *dir,
565                 enum format_flags flags)
567         init_io(io, dir, IO_RD);
568         return format_argv(io->argv, argv, flags);
571 static bool
572 io_open(struct io *io, const char *fmt, ...)
574         char name[SIZEOF_STR] = "";
575         bool fits;
576         va_list args;
578         init_io(io, NULL, IO_FD);
580         va_start(args, fmt);
581         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
582         va_end(args);
584         if (!fits) {
585                 io->error = ENAMETOOLONG;
586                 return FALSE;
587         }
588         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
589         if (io->pipe == -1)
590                 io->error = errno;
591         return io->pipe != -1;
594 static bool
595 kill_io(struct io *io)
597         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
600 static bool
601 done_io(struct io *io)
603         pid_t pid = io->pid;
605         if (io->pipe != -1)
606                 close(io->pipe);
607         free(io->buf);
608         reset_io(io);
610         while (pid > 0) {
611                 int status;
612                 pid_t waiting = waitpid(pid, &status, 0);
614                 if (waiting < 0) {
615                         if (errno == EINTR)
616                                 continue;
617                         report("waitpid failed (%s)", strerror(errno));
618                         return FALSE;
619                 }
621                 return waiting == pid &&
622                        !WIFSIGNALED(status) &&
623                        WIFEXITED(status) &&
624                        !WEXITSTATUS(status);
625         }
627         return TRUE;
630 static bool
631 start_io(struct io *io)
633         int pipefds[2] = { -1, -1 };
635         if (io->type == IO_FD)
636                 return TRUE;
638         if ((io->type == IO_RD || io->type == IO_WR) &&
639             pipe(pipefds) < 0)
640                 return FALSE;
641         else if (io->type == IO_AP)
642                 pipefds[1] = io->pipe;
644         if ((io->pid = fork())) {
645                 if (pipefds[!(io->type == IO_WR)] != -1)
646                         close(pipefds[!(io->type == IO_WR)]);
647                 if (io->pid != -1) {
648                         io->pipe = pipefds[!!(io->type == IO_WR)];
649                         return TRUE;
650                 }
652         } else {
653                 if (io->type != IO_FG) {
654                         int devnull = open("/dev/null", O_RDWR);
655                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
656                         int writefd = (io->type == IO_RD || io->type == IO_AP)
657                                                         ? pipefds[1] : devnull;
659                         dup2(readfd,  STDIN_FILENO);
660                         dup2(writefd, STDOUT_FILENO);
661                         dup2(devnull, STDERR_FILENO);
663                         close(devnull);
664                         if (pipefds[0] != -1)
665                                 close(pipefds[0]);
666                         if (pipefds[1] != -1)
667                                 close(pipefds[1]);
668                 }
670                 if (io->dir && *io->dir && chdir(io->dir) == -1)
671                         die("Failed to change directory: %s", strerror(errno));
673                 execvp(io->argv[0], (char *const*) io->argv);
674                 die("Failed to execute program: %s", strerror(errno));
675         }
677         if (pipefds[!!(io->type == IO_WR)] != -1)
678                 close(pipefds[!!(io->type == IO_WR)]);
679         return FALSE;
682 static bool
683 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
685         init_io(io, dir, type);
686         if (!format_argv(io->argv, argv, FORMAT_NONE))
687                 return FALSE;
688         return start_io(io);
691 static int
692 run_io_do(struct io *io)
694         return start_io(io) && done_io(io);
697 static int
698 run_io_bg(const char **argv)
700         struct io io = {};
702         init_io(&io, NULL, IO_BG);
703         if (!format_argv(io.argv, argv, FORMAT_NONE))
704                 return FALSE;
705         return run_io_do(&io);
708 static bool
709 run_io_fg(const char **argv, const char *dir)
711         struct io io = {};
713         init_io(&io, dir, IO_FG);
714         if (!format_argv(io.argv, argv, FORMAT_NONE))
715                 return FALSE;
716         return run_io_do(&io);
719 static bool
720 run_io_append(const char **argv, enum format_flags flags, int fd)
722         struct io io = {};
724         init_io(&io, NULL, IO_AP);
725         io.pipe = fd;
726         if (format_argv(io.argv, argv, flags))
727                 return run_io_do(&io);
728         close(fd);
729         return FALSE;
732 static bool
733 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
735         return init_io_rd(io, argv, dir, flags) && start_io(io);
738 static bool
739 io_eof(struct io *io)
741         return io->eof;
744 static int
745 io_error(struct io *io)
747         return io->error;
750 static char *
751 io_strerror(struct io *io)
753         return strerror(io->error);
756 static bool
757 io_can_read(struct io *io)
759         struct timeval tv = { 0, 500 };
760         fd_set fds;
762         FD_ZERO(&fds);
763         FD_SET(io->pipe, &fds);
765         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
768 static ssize_t
769 io_read(struct io *io, void *buf, size_t bufsize)
771         do {
772                 ssize_t readsize = read(io->pipe, buf, bufsize);
774                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
775                         continue;
776                 else if (readsize == -1)
777                         io->error = errno;
778                 else if (readsize == 0)
779                         io->eof = 1;
780                 return readsize;
781         } while (1);
784 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
786 static char *
787 io_get(struct io *io, int c, bool can_read)
789         char *eol;
790         ssize_t readsize;
792         while (TRUE) {
793                 if (io->bufsize > 0) {
794                         eol = memchr(io->bufpos, c, io->bufsize);
795                         if (eol) {
796                                 char *line = io->bufpos;
798                                 *eol = 0;
799                                 io->bufpos = eol + 1;
800                                 io->bufsize -= io->bufpos - line;
801                                 return line;
802                         }
803                 }
805                 if (io_eof(io)) {
806                         if (io->bufsize) {
807                                 io->bufpos[io->bufsize] = 0;
808                                 io->bufsize = 0;
809                                 return io->bufpos;
810                         }
811                         return NULL;
812                 }
814                 if (!can_read)
815                         return NULL;
817                 if (io->bufsize > 0 && io->bufpos > io->buf)
818                         memmove(io->buf, io->bufpos, io->bufsize);
820                 if (io->bufalloc == io->bufsize) {
821                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
822                                 return NULL;
823                         io->bufalloc += BUFSIZ;
824                 }
826                 io->bufpos = io->buf;
827                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
828                 if (io_error(io))
829                         return NULL;
830                 io->bufsize += readsize;
831         }
834 static bool
835 io_write(struct io *io, const void *buf, size_t bufsize)
837         size_t written = 0;
839         while (!io_error(io) && written < bufsize) {
840                 ssize_t size;
842                 size = write(io->pipe, buf + written, bufsize - written);
843                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
844                         continue;
845                 else if (size == -1)
846                         io->error = errno;
847                 else
848                         written += size;
849         }
851         return written == bufsize;
854 static bool
855 io_read_buf(struct io *io, char buf[], size_t bufsize)
857         char *result = io_get(io, '\n', TRUE);
859         if (result) {
860                 result = chomp_string(result);
861                 string_ncopy_do(buf, bufsize, result, strlen(result));
862         }
864         return done_io(io) && result;
867 static bool
868 run_io_buf(const char **argv, char buf[], size_t bufsize)
870         struct io io = {};
872         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
873             && io_read_buf(&io, buf, bufsize);
876 static int
877 io_load(struct io *io, const char *separators,
878         int (*read_property)(char *, size_t, char *, size_t))
880         char *name;
881         int state = OK;
883         if (!start_io(io))
884                 return ERR;
886         while (state == OK && (name = io_get(io, '\n', TRUE))) {
887                 char *value;
888                 size_t namelen;
889                 size_t valuelen;
891                 name = chomp_string(name);
892                 namelen = strcspn(name, separators);
894                 if (name[namelen]) {
895                         name[namelen] = 0;
896                         value = chomp_string(name + namelen + 1);
897                         valuelen = strlen(value);
899                 } else {
900                         value = "";
901                         valuelen = 0;
902                 }
904                 state = read_property(name, namelen, value, valuelen);
905         }
907         if (state != ERR && io_error(io))
908                 state = ERR;
909         done_io(io);
911         return state;
914 static int
915 run_io_load(const char **argv, const char *separators,
916             int (*read_property)(char *, size_t, char *, size_t))
918         struct io io = {};
920         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
921                 ? io_load(&io, separators, read_property) : ERR;
925 /*
926  * User requests
927  */
929 #define REQ_INFO \
930         /* XXX: Keep the view request first and in sync with views[]. */ \
931         REQ_GROUP("View switching") \
932         REQ_(VIEW_MAIN,         "Show main view"), \
933         REQ_(VIEW_DIFF,         "Show diff view"), \
934         REQ_(VIEW_LOG,          "Show log view"), \
935         REQ_(VIEW_TREE,         "Show tree view"), \
936         REQ_(VIEW_BLOB,         "Show blob view"), \
937         REQ_(VIEW_BLAME,        "Show blame view"), \
938         REQ_(VIEW_BRANCH,       "Show branch view"), \
939         REQ_(VIEW_HELP,         "Show help page"), \
940         REQ_(VIEW_PAGER,        "Show pager view"), \
941         REQ_(VIEW_STATUS,       "Show status view"), \
942         REQ_(VIEW_STAGE,        "Show stage view"), \
943         \
944         REQ_GROUP("View manipulation") \
945         REQ_(ENTER,             "Enter current line and scroll"), \
946         REQ_(NEXT,              "Move to next"), \
947         REQ_(PREVIOUS,          "Move to previous"), \
948         REQ_(PARENT,            "Move to parent"), \
949         REQ_(VIEW_NEXT,         "Move focus to next view"), \
950         REQ_(REFRESH,           "Reload and refresh"), \
951         REQ_(MAXIMIZE,          "Maximize the current view"), \
952         REQ_(VIEW_CLOSE,        "Close the current view"), \
953         REQ_(QUIT,              "Close all views and quit"), \
954         \
955         REQ_GROUP("View specific requests") \
956         REQ_(STATUS_UPDATE,     "Update file status"), \
957         REQ_(STATUS_REVERT,     "Revert file changes"), \
958         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
959         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
960         \
961         REQ_GROUP("Cursor navigation") \
962         REQ_(MOVE_UP,           "Move cursor one line up"), \
963         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
964         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
965         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
966         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
967         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
968         \
969         REQ_GROUP("Scrolling") \
970         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
971         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
972         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
973         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
974         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
975         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
976         \
977         REQ_GROUP("Searching") \
978         REQ_(SEARCH,            "Search the view"), \
979         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
980         REQ_(FIND_NEXT,         "Find next search match"), \
981         REQ_(FIND_PREV,         "Find previous search match"), \
982         \
983         REQ_GROUP("Option manipulation") \
984         REQ_(OPTIONS,           "Open option menu"), \
985         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
986         REQ_(TOGGLE_DATE,       "Toggle date display"), \
987         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
988         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
989         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
990         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
991         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
992         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
993         \
994         REQ_GROUP("Misc") \
995         REQ_(PROMPT,            "Bring up the prompt"), \
996         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
997         REQ_(SHOW_VERSION,      "Show version information"), \
998         REQ_(STOP_LOADING,      "Stop all loading views"), \
999         REQ_(EDIT,              "Open in editor"), \
1000         REQ_(NONE,              "Do nothing")
1003 /* User action requests. */
1004 enum request {
1005 #define REQ_GROUP(help)
1006 #define REQ_(req, help) REQ_##req
1008         /* Offset all requests to avoid conflicts with ncurses getch values. */
1009         REQ_OFFSET = KEY_MAX + 1,
1010         REQ_INFO
1012 #undef  REQ_GROUP
1013 #undef  REQ_
1014 };
1016 struct request_info {
1017         enum request request;
1018         const char *name;
1019         int namelen;
1020         const char *help;
1021 };
1023 static const struct request_info req_info[] = {
1024 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1025 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1026         REQ_INFO
1027 #undef  REQ_GROUP
1028 #undef  REQ_
1029 };
1031 static enum request
1032 get_request(const char *name)
1034         int namelen = strlen(name);
1035         int i;
1037         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1038                 if (enum_equals(req_info[i], name, namelen))
1039                         return req_info[i].request;
1041         return REQ_NONE;
1045 /*
1046  * Options
1047  */
1049 /* Option and state variables. */
1050 static enum date opt_date               = DATE_DEFAULT;
1051 static enum author opt_author           = AUTHOR_DEFAULT;
1052 static bool opt_line_number             = FALSE;
1053 static bool opt_line_graphics           = TRUE;
1054 static bool opt_rev_graph               = FALSE;
1055 static bool opt_show_refs               = TRUE;
1056 static int opt_num_interval             = 5;
1057 static double opt_hscroll               = 0.50;
1058 static double opt_scale_split_view      = 2.0 / 3.0;
1059 static int opt_tab_size                 = 8;
1060 static int opt_author_cols              = AUTHOR_COLS;
1061 static char opt_path[SIZEOF_STR]        = "";
1062 static char opt_file[SIZEOF_STR]        = "";
1063 static char opt_ref[SIZEOF_REF]         = "";
1064 static char opt_head[SIZEOF_REF]        = "";
1065 static char opt_head_rev[SIZEOF_REV]    = "";
1066 static char opt_remote[SIZEOF_REF]      = "";
1067 static char opt_encoding[20]            = "UTF-8";
1068 static char opt_codeset[20]             = "UTF-8";
1069 static iconv_t opt_iconv_in             = ICONV_NONE;
1070 static iconv_t opt_iconv_out            = ICONV_NONE;
1071 static char opt_search[SIZEOF_STR]      = "";
1072 static char opt_cdup[SIZEOF_STR]        = "";
1073 static char opt_prefix[SIZEOF_STR]      = "";
1074 static char opt_git_dir[SIZEOF_STR]     = "";
1075 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1076 static char opt_editor[SIZEOF_STR]      = "";
1077 static FILE *opt_tty                    = NULL;
1079 #define is_initial_commit()     (!*opt_head_rev)
1080 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1081 #define mkdate(time)            string_date(time, opt_date)
1084 /*
1085  * Line-oriented content detection.
1086  */
1088 #define LINE_INFO \
1089 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1090 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1091 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1092 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1093 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1094 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1095 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1096 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1097 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1098 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1099 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1100 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1101 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1102 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1103 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1104 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1105 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1106 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1107 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1108 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1109 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1110 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1111 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1112 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1113 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1114 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1115 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1116 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1117 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1118 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1119 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1120 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1121 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1122 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1123 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1124 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1125 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1126 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1127 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1128 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1129 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1130 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1131 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1132 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1133 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1134 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1135 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1136 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1137 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1138 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1139 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1140 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1141 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1142 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1143 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1144 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1145 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1147 enum line_type {
1148 #define LINE(type, line, fg, bg, attr) \
1149         LINE_##type
1150         LINE_INFO,
1151         LINE_NONE
1152 #undef  LINE
1153 };
1155 struct line_info {
1156         const char *name;       /* Option name. */
1157         int namelen;            /* Size of option name. */
1158         const char *line;       /* The start of line to match. */
1159         int linelen;            /* Size of string to match. */
1160         int fg, bg, attr;       /* Color and text attributes for the lines. */
1161 };
1163 static struct line_info line_info[] = {
1164 #define LINE(type, line, fg, bg, attr) \
1165         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1166         LINE_INFO
1167 #undef  LINE
1168 };
1170 static enum line_type
1171 get_line_type(const char *line)
1173         int linelen = strlen(line);
1174         enum line_type type;
1176         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1177                 /* Case insensitive search matches Signed-off-by lines better. */
1178                 if (linelen >= line_info[type].linelen &&
1179                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1180                         return type;
1182         return LINE_DEFAULT;
1185 static inline int
1186 get_line_attr(enum line_type type)
1188         assert(type < ARRAY_SIZE(line_info));
1189         return COLOR_PAIR(type) | line_info[type].attr;
1192 static struct line_info *
1193 get_line_info(const char *name)
1195         size_t namelen = strlen(name);
1196         enum line_type type;
1198         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1199                 if (enum_equals(line_info[type], name, namelen))
1200                         return &line_info[type];
1202         return NULL;
1205 static void
1206 init_colors(void)
1208         int default_bg = line_info[LINE_DEFAULT].bg;
1209         int default_fg = line_info[LINE_DEFAULT].fg;
1210         enum line_type type;
1212         start_color();
1214         if (assume_default_colors(default_fg, default_bg) == ERR) {
1215                 default_bg = COLOR_BLACK;
1216                 default_fg = COLOR_WHITE;
1217         }
1219         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1220                 struct line_info *info = &line_info[type];
1221                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1222                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1224                 init_pair(type, fg, bg);
1225         }
1228 struct line {
1229         enum line_type type;
1231         /* State flags */
1232         unsigned int selected:1;
1233         unsigned int dirty:1;
1234         unsigned int cleareol:1;
1235         unsigned int other:16;
1237         void *data;             /* User data */
1238 };
1241 /*
1242  * Keys
1243  */
1245 struct keybinding {
1246         int alias;
1247         enum request request;
1248 };
1250 static const struct keybinding default_keybindings[] = {
1251         /* View switching */
1252         { 'm',          REQ_VIEW_MAIN },
1253         { 'd',          REQ_VIEW_DIFF },
1254         { 'l',          REQ_VIEW_LOG },
1255         { 't',          REQ_VIEW_TREE },
1256         { 'f',          REQ_VIEW_BLOB },
1257         { 'B',          REQ_VIEW_BLAME },
1258         { 'H',          REQ_VIEW_BRANCH },
1259         { 'p',          REQ_VIEW_PAGER },
1260         { 'h',          REQ_VIEW_HELP },
1261         { 'S',          REQ_VIEW_STATUS },
1262         { 'c',          REQ_VIEW_STAGE },
1264         /* View manipulation */
1265         { 'q',          REQ_VIEW_CLOSE },
1266         { KEY_TAB,      REQ_VIEW_NEXT },
1267         { KEY_RETURN,   REQ_ENTER },
1268         { KEY_UP,       REQ_PREVIOUS },
1269         { KEY_DOWN,     REQ_NEXT },
1270         { 'R',          REQ_REFRESH },
1271         { KEY_F(5),     REQ_REFRESH },
1272         { 'O',          REQ_MAXIMIZE },
1274         /* Cursor navigation */
1275         { 'k',          REQ_MOVE_UP },
1276         { 'j',          REQ_MOVE_DOWN },
1277         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1278         { KEY_END,      REQ_MOVE_LAST_LINE },
1279         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1280         { ' ',          REQ_MOVE_PAGE_DOWN },
1281         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1282         { 'b',          REQ_MOVE_PAGE_UP },
1283         { '-',          REQ_MOVE_PAGE_UP },
1285         /* Scrolling */
1286         { KEY_LEFT,     REQ_SCROLL_LEFT },
1287         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1288         { KEY_IC,       REQ_SCROLL_LINE_UP },
1289         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1290         { 'w',          REQ_SCROLL_PAGE_UP },
1291         { 's',          REQ_SCROLL_PAGE_DOWN },
1293         /* Searching */
1294         { '/',          REQ_SEARCH },
1295         { '?',          REQ_SEARCH_BACK },
1296         { 'n',          REQ_FIND_NEXT },
1297         { 'N',          REQ_FIND_PREV },
1299         /* Misc */
1300         { 'Q',          REQ_QUIT },
1301         { 'z',          REQ_STOP_LOADING },
1302         { 'v',          REQ_SHOW_VERSION },
1303         { 'r',          REQ_SCREEN_REDRAW },
1304         { 'o',          REQ_OPTIONS },
1305         { '.',          REQ_TOGGLE_LINENO },
1306         { 'D',          REQ_TOGGLE_DATE },
1307         { 'A',          REQ_TOGGLE_AUTHOR },
1308         { 'g',          REQ_TOGGLE_REV_GRAPH },
1309         { 'F',          REQ_TOGGLE_REFS },
1310         { 'I',          REQ_TOGGLE_SORT_ORDER },
1311         { 'i',          REQ_TOGGLE_SORT_FIELD },
1312         { ':',          REQ_PROMPT },
1313         { 'u',          REQ_STATUS_UPDATE },
1314         { '!',          REQ_STATUS_REVERT },
1315         { 'M',          REQ_STATUS_MERGE },
1316         { '@',          REQ_STAGE_NEXT },
1317         { ',',          REQ_PARENT },
1318         { 'e',          REQ_EDIT },
1319 };
1321 #define KEYMAP_INFO \
1322         KEYMAP_(GENERIC), \
1323         KEYMAP_(MAIN), \
1324         KEYMAP_(DIFF), \
1325         KEYMAP_(LOG), \
1326         KEYMAP_(TREE), \
1327         KEYMAP_(BLOB), \
1328         KEYMAP_(BLAME), \
1329         KEYMAP_(BRANCH), \
1330         KEYMAP_(PAGER), \
1331         KEYMAP_(HELP), \
1332         KEYMAP_(STATUS), \
1333         KEYMAP_(STAGE)
1335 enum keymap {
1336 #define KEYMAP_(name) KEYMAP_##name
1337         KEYMAP_INFO
1338 #undef  KEYMAP_
1339 };
1341 static const struct enum_map keymap_table[] = {
1342 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1343         KEYMAP_INFO
1344 #undef  KEYMAP_
1345 };
1347 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1349 struct keybinding_table {
1350         struct keybinding *data;
1351         size_t size;
1352 };
1354 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1356 static void
1357 add_keybinding(enum keymap keymap, enum request request, int key)
1359         struct keybinding_table *table = &keybindings[keymap];
1361         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1362         if (!table->data)
1363                 die("Failed to allocate keybinding");
1364         table->data[table->size].alias = key;
1365         table->data[table->size++].request = request;
1368 /* Looks for a key binding first in the given map, then in the generic map, and
1369  * lastly in the default keybindings. */
1370 static enum request
1371 get_keybinding(enum keymap keymap, int key)
1373         size_t i;
1375         for (i = 0; i < keybindings[keymap].size; i++)
1376                 if (keybindings[keymap].data[i].alias == key)
1377                         return keybindings[keymap].data[i].request;
1379         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1380                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1381                         return keybindings[KEYMAP_GENERIC].data[i].request;
1383         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1384                 if (default_keybindings[i].alias == key)
1385                         return default_keybindings[i].request;
1387         return (enum request) key;
1391 struct key {
1392         const char *name;
1393         int value;
1394 };
1396 static const struct key key_table[] = {
1397         { "Enter",      KEY_RETURN },
1398         { "Space",      ' ' },
1399         { "Backspace",  KEY_BACKSPACE },
1400         { "Tab",        KEY_TAB },
1401         { "Escape",     KEY_ESC },
1402         { "Left",       KEY_LEFT },
1403         { "Right",      KEY_RIGHT },
1404         { "Up",         KEY_UP },
1405         { "Down",       KEY_DOWN },
1406         { "Insert",     KEY_IC },
1407         { "Delete",     KEY_DC },
1408         { "Hash",       '#' },
1409         { "Home",       KEY_HOME },
1410         { "End",        KEY_END },
1411         { "PageUp",     KEY_PPAGE },
1412         { "PageDown",   KEY_NPAGE },
1413         { "F1",         KEY_F(1) },
1414         { "F2",         KEY_F(2) },
1415         { "F3",         KEY_F(3) },
1416         { "F4",         KEY_F(4) },
1417         { "F5",         KEY_F(5) },
1418         { "F6",         KEY_F(6) },
1419         { "F7",         KEY_F(7) },
1420         { "F8",         KEY_F(8) },
1421         { "F9",         KEY_F(9) },
1422         { "F10",        KEY_F(10) },
1423         { "F11",        KEY_F(11) },
1424         { "F12",        KEY_F(12) },
1425 };
1427 static int
1428 get_key_value(const char *name)
1430         int i;
1432         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1433                 if (!strcasecmp(key_table[i].name, name))
1434                         return key_table[i].value;
1436         if (strlen(name) == 1 && isprint(*name))
1437                 return (int) *name;
1439         return ERR;
1442 static const char *
1443 get_key_name(int key_value)
1445         static char key_char[] = "'X'";
1446         const char *seq = NULL;
1447         int key;
1449         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1450                 if (key_table[key].value == key_value)
1451                         seq = key_table[key].name;
1453         if (seq == NULL &&
1454             key_value < 127 &&
1455             isprint(key_value)) {
1456                 key_char[1] = (char) key_value;
1457                 seq = key_char;
1458         }
1460         return seq ? seq : "(no key)";
1463 static bool
1464 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1466         const char *sep = *pos > 0 ? ", " : "";
1467         const char *keyname = get_key_name(keybinding->alias);
1469         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1472 static bool
1473 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1474                            enum keymap keymap, bool all)
1476         int i;
1478         for (i = 0; i < keybindings[keymap].size; i++) {
1479                 if (keybindings[keymap].data[i].request == request) {
1480                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1481                                 return FALSE;
1482                         if (!all)
1483                                 break;
1484                 }
1485         }
1487         return TRUE;
1490 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1492 static const char *
1493 get_keys(enum keymap keymap, enum request request, bool all)
1495         static char buf[BUFSIZ];
1496         size_t pos = 0;
1497         int i;
1499         buf[pos] = 0;
1501         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1502                 return "Too many keybindings!";
1503         if (pos > 0 && !all)
1504                 return buf;
1506         if (keymap != KEYMAP_GENERIC) {
1507                 /* Only the generic keymap includes the default keybindings when
1508                  * listing all keys. */
1509                 if (all)
1510                         return buf;
1512                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1513                         return "Too many keybindings!";
1514                 if (pos)
1515                         return buf;
1516         }
1518         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1519                 if (default_keybindings[i].request == request) {
1520                         if (!append_key(buf, &pos, &default_keybindings[i]))
1521                                 return "Too many keybindings!";
1522                         if (!all)
1523                                 return buf;
1524                 }
1525         }
1527         return buf;
1530 struct run_request {
1531         enum keymap keymap;
1532         int key;
1533         const char *argv[SIZEOF_ARG];
1534 };
1536 static struct run_request *run_request;
1537 static size_t run_requests;
1539 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1541 static enum request
1542 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1544         struct run_request *req;
1546         if (argc >= ARRAY_SIZE(req->argv) - 1)
1547                 return REQ_NONE;
1549         if (!realloc_run_requests(&run_request, run_requests, 1))
1550                 return REQ_NONE;
1552         req = &run_request[run_requests];
1553         req->keymap = keymap;
1554         req->key = key;
1555         req->argv[0] = NULL;
1557         if (!format_argv(req->argv, argv, FORMAT_NONE))
1558                 return REQ_NONE;
1560         return REQ_NONE + ++run_requests;
1563 static struct run_request *
1564 get_run_request(enum request request)
1566         if (request <= REQ_NONE)
1567                 return NULL;
1568         return &run_request[request - REQ_NONE - 1];
1571 static void
1572 add_builtin_run_requests(void)
1574         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1575         const char *commit[] = { "git", "commit", NULL };
1576         const char *gc[] = { "git", "gc", NULL };
1577         struct {
1578                 enum keymap keymap;
1579                 int key;
1580                 int argc;
1581                 const char **argv;
1582         } reqs[] = {
1583                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1584                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1585                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1586         };
1587         int i;
1589         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1590                 enum request req;
1592                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1593                 if (req != REQ_NONE)
1594                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1595         }
1598 /*
1599  * User config file handling.
1600  */
1602 static int   config_lineno;
1603 static bool  config_errors;
1604 static const char *config_msg;
1606 static const struct enum_map color_map[] = {
1607 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1608         COLOR_MAP(DEFAULT),
1609         COLOR_MAP(BLACK),
1610         COLOR_MAP(BLUE),
1611         COLOR_MAP(CYAN),
1612         COLOR_MAP(GREEN),
1613         COLOR_MAP(MAGENTA),
1614         COLOR_MAP(RED),
1615         COLOR_MAP(WHITE),
1616         COLOR_MAP(YELLOW),
1617 };
1619 static const struct enum_map attr_map[] = {
1620 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1621         ATTR_MAP(NORMAL),
1622         ATTR_MAP(BLINK),
1623         ATTR_MAP(BOLD),
1624         ATTR_MAP(DIM),
1625         ATTR_MAP(REVERSE),
1626         ATTR_MAP(STANDOUT),
1627         ATTR_MAP(UNDERLINE),
1628 };
1630 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1632 static int parse_step(double *opt, const char *arg)
1634         *opt = atoi(arg);
1635         if (!strchr(arg, '%'))
1636                 return OK;
1638         /* "Shift down" so 100% and 1 does not conflict. */
1639         *opt = (*opt - 1) / 100;
1640         if (*opt >= 1.0) {
1641                 *opt = 0.99;
1642                 config_msg = "Step value larger than 100%";
1643                 return ERR;
1644         }
1645         if (*opt < 0.0) {
1646                 *opt = 1;
1647                 config_msg = "Invalid step value";
1648                 return ERR;
1649         }
1650         return OK;
1653 static int
1654 parse_int(int *opt, const char *arg, int min, int max)
1656         int value = atoi(arg);
1658         if (min <= value && value <= max) {
1659                 *opt = value;
1660                 return OK;
1661         }
1663         config_msg = "Integer value out of bound";
1664         return ERR;
1667 static bool
1668 set_color(int *color, const char *name)
1670         if (map_enum(color, color_map, name))
1671                 return TRUE;
1672         if (!prefixcmp(name, "color"))
1673                 return parse_int(color, name + 5, 0, 255) == OK;
1674         return FALSE;
1677 /* Wants: object fgcolor bgcolor [attribute] */
1678 static int
1679 option_color_command(int argc, const char *argv[])
1681         struct line_info *info;
1683         if (argc < 3) {
1684                 config_msg = "Wrong number of arguments given to color command";
1685                 return ERR;
1686         }
1688         info = get_line_info(argv[0]);
1689         if (!info) {
1690                 static const struct enum_map obsolete[] = {
1691                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1692                         ENUM_MAP("main-date",   LINE_DATE),
1693                         ENUM_MAP("main-author", LINE_AUTHOR),
1694                 };
1695                 int index;
1697                 if (!map_enum(&index, obsolete, argv[0])) {
1698                         config_msg = "Unknown color name";
1699                         return ERR;
1700                 }
1701                 info = &line_info[index];
1702         }
1704         if (!set_color(&info->fg, argv[1]) ||
1705             !set_color(&info->bg, argv[2])) {
1706                 config_msg = "Unknown color";
1707                 return ERR;
1708         }
1710         info->attr = 0;
1711         while (argc-- > 3) {
1712                 int attr;
1714                 if (!set_attribute(&attr, argv[argc])) {
1715                         config_msg = "Unknown attribute";
1716                         return ERR;
1717                 }
1718                 info->attr |= attr;
1719         }
1721         return OK;
1724 static int parse_bool(bool *opt, const char *arg)
1726         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1727                 ? TRUE : FALSE;
1728         return OK;
1731 static int parse_enum_do(unsigned int *opt, const char *arg,
1732                          const struct enum_map *map, size_t map_size)
1734         bool is_true;
1736         assert(map_size > 1);
1738         if (map_enum_do(map, map_size, (int *) opt, arg))
1739                 return OK;
1741         if (parse_bool(&is_true, arg) != OK)
1742                 return ERR;
1744         *opt = is_true ? map[1].value : map[0].value;
1745         return OK;
1748 #define parse_enum(opt, arg, map) \
1749         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1751 static int
1752 parse_string(char *opt, const char *arg, size_t optsize)
1754         int arglen = strlen(arg);
1756         switch (arg[0]) {
1757         case '\"':
1758         case '\'':
1759                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1760                         config_msg = "Unmatched quotation";
1761                         return ERR;
1762                 }
1763                 arg += 1; arglen -= 2;
1764         default:
1765                 string_ncopy_do(opt, optsize, arg, arglen);
1766                 return OK;
1767         }
1770 /* Wants: name = value */
1771 static int
1772 option_set_command(int argc, const char *argv[])
1774         if (argc != 3) {
1775                 config_msg = "Wrong number of arguments given to set command";
1776                 return ERR;
1777         }
1779         if (strcmp(argv[1], "=")) {
1780                 config_msg = "No value assigned";
1781                 return ERR;
1782         }
1784         if (!strcmp(argv[0], "show-author"))
1785                 return parse_enum(&opt_author, argv[2], author_map);
1787         if (!strcmp(argv[0], "show-date"))
1788                 return parse_enum(&opt_date, argv[2], date_map);
1790         if (!strcmp(argv[0], "show-rev-graph"))
1791                 return parse_bool(&opt_rev_graph, argv[2]);
1793         if (!strcmp(argv[0], "show-refs"))
1794                 return parse_bool(&opt_show_refs, argv[2]);
1796         if (!strcmp(argv[0], "show-line-numbers"))
1797                 return parse_bool(&opt_line_number, argv[2]);
1799         if (!strcmp(argv[0], "line-graphics"))
1800                 return parse_bool(&opt_line_graphics, argv[2]);
1802         if (!strcmp(argv[0], "line-number-interval"))
1803                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1805         if (!strcmp(argv[0], "author-width"))
1806                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1808         if (!strcmp(argv[0], "horizontal-scroll"))
1809                 return parse_step(&opt_hscroll, argv[2]);
1811         if (!strcmp(argv[0], "split-view-height"))
1812                 return parse_step(&opt_scale_split_view, argv[2]);
1814         if (!strcmp(argv[0], "tab-size"))
1815                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1817         if (!strcmp(argv[0], "commit-encoding"))
1818                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1820         config_msg = "Unknown variable name";
1821         return ERR;
1824 /* Wants: mode request key */
1825 static int
1826 option_bind_command(int argc, const char *argv[])
1828         enum request request;
1829         int keymap = -1;
1830         int key;
1832         if (argc < 3) {
1833                 config_msg = "Wrong number of arguments given to bind command";
1834                 return ERR;
1835         }
1837         if (set_keymap(&keymap, argv[0]) == ERR) {
1838                 config_msg = "Unknown key map";
1839                 return ERR;
1840         }
1842         key = get_key_value(argv[1]);
1843         if (key == ERR) {
1844                 config_msg = "Unknown key";
1845                 return ERR;
1846         }
1848         request = get_request(argv[2]);
1849         if (request == REQ_NONE) {
1850                 static const struct enum_map obsolete[] = {
1851                         ENUM_MAP("cherry-pick",         REQ_NONE),
1852                         ENUM_MAP("screen-resize",       REQ_NONE),
1853                         ENUM_MAP("tree-parent",         REQ_PARENT),
1854                 };
1855                 int alias;
1857                 if (map_enum(&alias, obsolete, argv[2])) {
1858                         if (alias != REQ_NONE)
1859                                 add_keybinding(keymap, alias, key);
1860                         config_msg = "Obsolete request name";
1861                         return ERR;
1862                 }
1863         }
1864         if (request == REQ_NONE && *argv[2]++ == '!')
1865                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1866         if (request == REQ_NONE) {
1867                 config_msg = "Unknown request name";
1868                 return ERR;
1869         }
1871         add_keybinding(keymap, request, key);
1873         return OK;
1876 static int
1877 set_option(const char *opt, char *value)
1879         const char *argv[SIZEOF_ARG];
1880         int argc = 0;
1882         if (!argv_from_string(argv, &argc, value)) {
1883                 config_msg = "Too many option arguments";
1884                 return ERR;
1885         }
1887         if (!strcmp(opt, "color"))
1888                 return option_color_command(argc, argv);
1890         if (!strcmp(opt, "set"))
1891                 return option_set_command(argc, argv);
1893         if (!strcmp(opt, "bind"))
1894                 return option_bind_command(argc, argv);
1896         config_msg = "Unknown option command";
1897         return ERR;
1900 static int
1901 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1903         int status = OK;
1905         config_lineno++;
1906         config_msg = "Internal error";
1908         /* Check for comment markers, since read_properties() will
1909          * only ensure opt and value are split at first " \t". */
1910         optlen = strcspn(opt, "#");
1911         if (optlen == 0)
1912                 return OK;
1914         if (opt[optlen] != 0) {
1915                 config_msg = "No option value";
1916                 status = ERR;
1918         }  else {
1919                 /* Look for comment endings in the value. */
1920                 size_t len = strcspn(value, "#");
1922                 if (len < valuelen) {
1923                         valuelen = len;
1924                         value[valuelen] = 0;
1925                 }
1927                 status = set_option(opt, value);
1928         }
1930         if (status == ERR) {
1931                 warn("Error on line %d, near '%.*s': %s",
1932                      config_lineno, (int) optlen, opt, config_msg);
1933                 config_errors = TRUE;
1934         }
1936         /* Always keep going if errors are encountered. */
1937         return OK;
1940 static void
1941 load_option_file(const char *path)
1943         struct io io = {};
1945         /* It's OK that the file doesn't exist. */
1946         if (!io_open(&io, "%s", path))
1947                 return;
1949         config_lineno = 0;
1950         config_errors = FALSE;
1952         if (io_load(&io, " \t", read_option) == ERR ||
1953             config_errors == TRUE)
1954                 warn("Errors while loading %s.", path);
1957 static int
1958 load_options(void)
1960         const char *home = getenv("HOME");
1961         const char *tigrc_user = getenv("TIGRC_USER");
1962         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1963         char buf[SIZEOF_STR];
1965         add_builtin_run_requests();
1967         if (!tigrc_system)
1968                 tigrc_system = SYSCONFDIR "/tigrc";
1969         load_option_file(tigrc_system);
1971         if (!tigrc_user) {
1972                 if (!home || !string_format(buf, "%s/.tigrc", home))
1973                         return ERR;
1974                 tigrc_user = buf;
1975         }
1976         load_option_file(tigrc_user);
1978         return OK;
1982 /*
1983  * The viewer
1984  */
1986 struct view;
1987 struct view_ops;
1989 /* The display array of active views and the index of the current view. */
1990 static struct view *display[2];
1991 static unsigned int current_view;
1993 #define foreach_displayed_view(view, i) \
1994         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1996 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1998 /* Current head and commit ID */
1999 static char ref_blob[SIZEOF_REF]        = "";
2000 static char ref_commit[SIZEOF_REF]      = "HEAD";
2001 static char ref_head[SIZEOF_REF]        = "HEAD";
2003 struct view {
2004         const char *name;       /* View name */
2005         const char *cmd_env;    /* Command line set via environment */
2006         const char *id;         /* Points to either of ref_{head,commit,blob} */
2008         struct view_ops *ops;   /* View operations */
2010         enum keymap keymap;     /* What keymap does this view have */
2011         bool git_dir;           /* Whether the view requires a git directory. */
2013         char ref[SIZEOF_REF];   /* Hovered commit reference */
2014         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2016         int height, width;      /* The width and height of the main window */
2017         WINDOW *win;            /* The main window */
2018         WINDOW *title;          /* The title window living below the main window */
2020         /* Navigation */
2021         unsigned long offset;   /* Offset of the window top */
2022         unsigned long yoffset;  /* Offset from the window side. */
2023         unsigned long lineno;   /* Current line number */
2024         unsigned long p_offset; /* Previous offset of the window top */
2025         unsigned long p_yoffset;/* Previous offset from the window side */
2026         unsigned long p_lineno; /* Previous current line number */
2027         bool p_restore;         /* Should the previous position be restored. */
2029         /* Searching */
2030         char grep[SIZEOF_STR];  /* Search string */
2031         regex_t *regex;         /* Pre-compiled regexp */
2033         /* If non-NULL, points to the view that opened this view. If this view
2034          * is closed tig will switch back to the parent view. */
2035         struct view *parent;
2037         /* Buffering */
2038         size_t lines;           /* Total number of lines */
2039         struct line *line;      /* Line index */
2040         unsigned int digits;    /* Number of digits in the lines member. */
2042         /* Drawing */
2043         struct line *curline;   /* Line currently being drawn. */
2044         enum line_type curtype; /* Attribute currently used for drawing. */
2045         unsigned long col;      /* Column when drawing. */
2046         bool has_scrolled;      /* View was scrolled. */
2048         /* Loading */
2049         struct io io;
2050         struct io *pipe;
2051         time_t start_time;
2052         time_t update_secs;
2053 };
2055 struct view_ops {
2056         /* What type of content being displayed. Used in the title bar. */
2057         const char *type;
2058         /* Default command arguments. */
2059         const char **argv;
2060         /* Open and reads in all view content. */
2061         bool (*open)(struct view *view);
2062         /* Read one line; updates view->line. */
2063         bool (*read)(struct view *view, char *data);
2064         /* Draw one line; @lineno must be < view->height. */
2065         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2066         /* Depending on view handle a special requests. */
2067         enum request (*request)(struct view *view, enum request request, struct line *line);
2068         /* Search for regexp in a line. */
2069         bool (*grep)(struct view *view, struct line *line);
2070         /* Select line */
2071         void (*select)(struct view *view, struct line *line);
2072         /* Prepare view for loading */
2073         bool (*prepare)(struct view *view);
2074 };
2076 static struct view_ops blame_ops;
2077 static struct view_ops blob_ops;
2078 static struct view_ops diff_ops;
2079 static struct view_ops help_ops;
2080 static struct view_ops log_ops;
2081 static struct view_ops main_ops;
2082 static struct view_ops pager_ops;
2083 static struct view_ops stage_ops;
2084 static struct view_ops status_ops;
2085 static struct view_ops tree_ops;
2086 static struct view_ops branch_ops;
2088 #define VIEW_STR(name, env, ref, ops, map, git) \
2089         { name, #env, ref, ops, map, git }
2091 #define VIEW_(id, name, ops, git, ref) \
2092         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2095 static struct view views[] = {
2096         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2097         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2098         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2099         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2100         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2101         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2102         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2103         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2104         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2105         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2106         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2107 };
2109 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2110 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2112 #define foreach_view(view, i) \
2113         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2115 #define view_is_displayed(view) \
2116         (view == display[0] || view == display[1])
2119 enum line_graphic {
2120         LINE_GRAPHIC_VLINE
2121 };
2123 static chtype line_graphics[] = {
2124         /* LINE_GRAPHIC_VLINE: */ '|'
2125 };
2127 static inline void
2128 set_view_attr(struct view *view, enum line_type type)
2130         if (!view->curline->selected && view->curtype != type) {
2131                 wattrset(view->win, get_line_attr(type));
2132                 wchgat(view->win, -1, 0, type, NULL);
2133                 view->curtype = type;
2134         }
2137 static int
2138 draw_chars(struct view *view, enum line_type type, const char *string,
2139            int max_len, bool use_tilde)
2141         static char out_buffer[BUFSIZ * 2];
2142         int len = 0;
2143         int col = 0;
2144         int trimmed = FALSE;
2145         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2147         if (max_len <= 0)
2148                 return 0;
2150         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2152         set_view_attr(view, type);
2153         if (len > 0) {
2154                 if (opt_iconv_out != ICONV_NONE) {
2155                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2156                         size_t inlen = len + 1;
2158                         char *outbuf = out_buffer;
2159                         size_t outlen = sizeof(out_buffer);
2161                         size_t ret;
2163                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2164                         if (ret != (size_t) -1) {
2165                                 string = out_buffer;
2166                                 len = sizeof(out_buffer) - outlen;
2167                         }
2168                 }
2170                 waddnstr(view->win, string, len);
2171         }
2172         if (trimmed && use_tilde) {
2173                 set_view_attr(view, LINE_DELIMITER);
2174                 waddch(view->win, '~');
2175                 col++;
2176         }
2178         return col;
2181 static int
2182 draw_space(struct view *view, enum line_type type, int max, int spaces)
2184         static char space[] = "                    ";
2185         int col = 0;
2187         spaces = MIN(max, spaces);
2189         while (spaces > 0) {
2190                 int len = MIN(spaces, sizeof(space) - 1);
2192                 col += draw_chars(view, type, space, len, FALSE);
2193                 spaces -= len;
2194         }
2196         return col;
2199 static bool
2200 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2202         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2203         return view->width + view->yoffset <= view->col;
2206 static bool
2207 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2209         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2210         int max = view->width + view->yoffset - view->col;
2211         int i;
2213         if (max < size)
2214                 size = max;
2216         set_view_attr(view, type);
2217         /* Using waddch() instead of waddnstr() ensures that
2218          * they'll be rendered correctly for the cursor line. */
2219         for (i = skip; i < size; i++)
2220                 waddch(view->win, graphic[i]);
2222         view->col += size;
2223         if (size < max && skip <= size)
2224                 waddch(view->win, ' ');
2225         view->col++;
2227         return view->width + view->yoffset <= view->col;
2230 static bool
2231 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2233         int max = MIN(view->width + view->yoffset - view->col, len);
2234         int col;
2236         if (text)
2237                 col = draw_chars(view, type, text, max - 1, trim);
2238         else
2239                 col = draw_space(view, type, max - 1, max - 1);
2241         view->col += col;
2242         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2243         return view->width + view->yoffset <= view->col;
2246 static bool
2247 draw_date(struct view *view, time_t *time)
2249         const char *date = time ? mkdate(time) : "";
2250         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2252         return draw_field(view, LINE_DATE, date, cols, FALSE);
2255 static bool
2256 draw_author(struct view *view, const char *author)
2258         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2259         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2261         if (abbreviate && author)
2262                 author = get_author_initials(author, opt_author_cols);
2264         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2267 static bool
2268 draw_mode(struct view *view, mode_t mode)
2270         const char *str;
2272         if (S_ISDIR(mode))
2273                 str = "drwxr-xr-x";
2274         else if (S_ISLNK(mode))
2275                 str = "lrwxrwxrwx";
2276         else if (S_ISGITLINK(mode))
2277                 str = "m---------";
2278         else if (S_ISREG(mode) && mode & S_IXUSR)
2279                 str = "-rwxr-xr-x";
2280         else if (S_ISREG(mode))
2281                 str = "-rw-r--r--";
2282         else
2283                 str = "----------";
2285         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2288 static bool
2289 draw_lineno(struct view *view, unsigned int lineno)
2291         char number[10];
2292         int digits3 = view->digits < 3 ? 3 : view->digits;
2293         int max = MIN(view->width + view->yoffset - view->col, digits3);
2294         char *text = NULL;
2296         lineno += view->offset + 1;
2297         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2298                 static char fmt[] = "%1ld";
2300                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2301                 if (string_format(number, fmt, lineno))
2302                         text = number;
2303         }
2304         if (text)
2305                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2306         else
2307                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2308         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2311 static bool
2312 draw_view_line(struct view *view, unsigned int lineno)
2314         struct line *line;
2315         bool selected = (view->offset + lineno == view->lineno);
2317         assert(view_is_displayed(view));
2319         if (view->offset + lineno >= view->lines)
2320                 return FALSE;
2322         line = &view->line[view->offset + lineno];
2324         wmove(view->win, lineno, 0);
2325         if (line->cleareol)
2326                 wclrtoeol(view->win);
2327         view->col = 0;
2328         view->curline = line;
2329         view->curtype = LINE_NONE;
2330         line->selected = FALSE;
2331         line->dirty = line->cleareol = 0;
2333         if (selected) {
2334                 set_view_attr(view, LINE_CURSOR);
2335                 line->selected = TRUE;
2336                 view->ops->select(view, line);
2337         }
2339         return view->ops->draw(view, line, lineno);
2342 static void
2343 redraw_view_dirty(struct view *view)
2345         bool dirty = FALSE;
2346         int lineno;
2348         for (lineno = 0; lineno < view->height; lineno++) {
2349                 if (view->offset + lineno >= view->lines)
2350                         break;
2351                 if (!view->line[view->offset + lineno].dirty)
2352                         continue;
2353                 dirty = TRUE;
2354                 if (!draw_view_line(view, lineno))
2355                         break;
2356         }
2358         if (!dirty)
2359                 return;
2360         wnoutrefresh(view->win);
2363 static void
2364 redraw_view_from(struct view *view, int lineno)
2366         assert(0 <= lineno && lineno < view->height);
2368         for (; lineno < view->height; lineno++) {
2369                 if (!draw_view_line(view, lineno))
2370                         break;
2371         }
2373         wnoutrefresh(view->win);
2376 static void
2377 redraw_view(struct view *view)
2379         werase(view->win);
2380         redraw_view_from(view, 0);
2384 static void
2385 update_view_title(struct view *view)
2387         char buf[SIZEOF_STR];
2388         char state[SIZEOF_STR];
2389         size_t bufpos = 0, statelen = 0;
2391         assert(view_is_displayed(view));
2393         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2394                 unsigned int view_lines = view->offset + view->height;
2395                 unsigned int lines = view->lines
2396                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2397                                    : 0;
2399                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2400                                    view->ops->type,
2401                                    view->lineno + 1,
2402                                    view->lines,
2403                                    lines);
2405         }
2407         if (view->pipe) {
2408                 time_t secs = time(NULL) - view->start_time;
2410                 /* Three git seconds are a long time ... */
2411                 if (secs > 2)
2412                         string_format_from(state, &statelen, " loading %lds", secs);
2413         }
2415         string_format_from(buf, &bufpos, "[%s]", view->name);
2416         if (*view->ref && bufpos < view->width) {
2417                 size_t refsize = strlen(view->ref);
2418                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2420                 if (minsize < view->width)
2421                         refsize = view->width - minsize + 7;
2422                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2423         }
2425         if (statelen && bufpos < view->width) {
2426                 string_format_from(buf, &bufpos, "%s", state);
2427         }
2429         if (view == display[current_view])
2430                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2431         else
2432                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2434         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2435         wclrtoeol(view->title);
2436         wnoutrefresh(view->title);
2439 static int
2440 apply_step(double step, int value)
2442         if (step >= 1)
2443                 return (int) step;
2444         value *= step + 0.01;
2445         return value ? value : 1;
2448 static void
2449 resize_display(void)
2451         int offset, i;
2452         struct view *base = display[0];
2453         struct view *view = display[1] ? display[1] : display[0];
2455         /* Setup window dimensions */
2457         getmaxyx(stdscr, base->height, base->width);
2459         /* Make room for the status window. */
2460         base->height -= 1;
2462         if (view != base) {
2463                 /* Horizontal split. */
2464                 view->width   = base->width;
2465                 view->height  = apply_step(opt_scale_split_view, base->height);
2466                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2467                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2468                 base->height -= view->height;
2470                 /* Make room for the title bar. */
2471                 view->height -= 1;
2472         }
2474         /* Make room for the title bar. */
2475         base->height -= 1;
2477         offset = 0;
2479         foreach_displayed_view (view, i) {
2480                 if (!view->win) {
2481                         view->win = newwin(view->height, 0, offset, 0);
2482                         if (!view->win)
2483                                 die("Failed to create %s view", view->name);
2485                         scrollok(view->win, FALSE);
2487                         view->title = newwin(1, 0, offset + view->height, 0);
2488                         if (!view->title)
2489                                 die("Failed to create title window");
2491                 } else {
2492                         wresize(view->win, view->height, view->width);
2493                         mvwin(view->win,   offset, 0);
2494                         mvwin(view->title, offset + view->height, 0);
2495                 }
2497                 offset += view->height + 1;
2498         }
2501 static void
2502 redraw_display(bool clear)
2504         struct view *view;
2505         int i;
2507         foreach_displayed_view (view, i) {
2508                 if (clear)
2509                         wclear(view->win);
2510                 redraw_view(view);
2511                 update_view_title(view);
2512         }
2515 static void
2516 toggle_enum_option_do(unsigned int *opt, const char *help,
2517                       const struct enum_map *map, size_t size)
2519         *opt = (*opt + 1) % size;
2520         redraw_display(FALSE);
2521         report("Displaying %s %s", enum_name(map[*opt]), help);
2524 #define toggle_enum_option(opt, help, map) \
2525         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2527 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2528 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2530 static void
2531 toggle_view_option(bool *option, const char *help)
2533         *option = !*option;
2534         redraw_display(FALSE);
2535         report("%sabling %s", *option ? "En" : "Dis", help);
2538 static void
2539 open_option_menu(void)
2541         const struct menu_item menu[] = {
2542                 { '.', "line numbers", &opt_line_number },
2543                 { 'D', "date display", &opt_date },
2544                 { 'A', "author display", &opt_author },
2545                 { 'g', "revision graph display", &opt_rev_graph },
2546                 { 'F', "reference display", &opt_show_refs },
2547                 { 0 }
2548         };
2549         int selected = 0;
2551         if (prompt_menu("Toggle option", menu, &selected)) {
2552                 if (menu[selected].data == &opt_date)
2553                         toggle_date();
2554                 else if (menu[selected].data == &opt_author)
2555                         toggle_author();
2556                 else
2557                         toggle_view_option(menu[selected].data, menu[selected].text);
2558         }
2561 static void
2562 maximize_view(struct view *view)
2564         memset(display, 0, sizeof(display));
2565         current_view = 0;
2566         display[current_view] = view;
2567         resize_display();
2568         redraw_display(FALSE);
2569         report("");
2573 /*
2574  * Navigation
2575  */
2577 static bool
2578 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2580         if (lineno >= view->lines)
2581                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2583         if (offset > lineno || offset + view->height <= lineno) {
2584                 unsigned long half = view->height / 2;
2586                 if (lineno > half)
2587                         offset = lineno - half;
2588                 else
2589                         offset = 0;
2590         }
2592         if (offset != view->offset || lineno != view->lineno) {
2593                 view->offset = offset;
2594                 view->lineno = lineno;
2595                 return TRUE;
2596         }
2598         return FALSE;
2601 /* Scrolling backend */
2602 static void
2603 do_scroll_view(struct view *view, int lines)
2605         bool redraw_current_line = FALSE;
2607         /* The rendering expects the new offset. */
2608         view->offset += lines;
2610         assert(0 <= view->offset && view->offset < view->lines);
2611         assert(lines);
2613         /* Move current line into the view. */
2614         if (view->lineno < view->offset) {
2615                 view->lineno = view->offset;
2616                 redraw_current_line = TRUE;
2617         } else if (view->lineno >= view->offset + view->height) {
2618                 view->lineno = view->offset + view->height - 1;
2619                 redraw_current_line = TRUE;
2620         }
2622         assert(view->offset <= view->lineno && view->lineno < view->lines);
2624         /* Redraw the whole screen if scrolling is pointless. */
2625         if (view->height < ABS(lines)) {
2626                 redraw_view(view);
2628         } else {
2629                 int line = lines > 0 ? view->height - lines : 0;
2630                 int end = line + ABS(lines);
2632                 scrollok(view->win, TRUE);
2633                 wscrl(view->win, lines);
2634                 scrollok(view->win, FALSE);
2636                 while (line < end && draw_view_line(view, line))
2637                         line++;
2639                 if (redraw_current_line)
2640                         draw_view_line(view, view->lineno - view->offset);
2641                 wnoutrefresh(view->win);
2642         }
2644         view->has_scrolled = TRUE;
2645         report("");
2648 /* Scroll frontend */
2649 static void
2650 scroll_view(struct view *view, enum request request)
2652         int lines = 1;
2654         assert(view_is_displayed(view));
2656         switch (request) {
2657         case REQ_SCROLL_LEFT:
2658                 if (view->yoffset == 0) {
2659                         report("Cannot scroll beyond the first column");
2660                         return;
2661                 }
2662                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2663                         view->yoffset = 0;
2664                 else
2665                         view->yoffset -= apply_step(opt_hscroll, view->width);
2666                 redraw_view_from(view, 0);
2667                 report("");
2668                 return;
2669         case REQ_SCROLL_RIGHT:
2670                 view->yoffset += apply_step(opt_hscroll, view->width);
2671                 redraw_view(view);
2672                 report("");
2673                 return;
2674         case REQ_SCROLL_PAGE_DOWN:
2675                 lines = view->height;
2676         case REQ_SCROLL_LINE_DOWN:
2677                 if (view->offset + lines > view->lines)
2678                         lines = view->lines - view->offset;
2680                 if (lines == 0 || view->offset + view->height >= view->lines) {
2681                         report("Cannot scroll beyond the last line");
2682                         return;
2683                 }
2684                 break;
2686         case REQ_SCROLL_PAGE_UP:
2687                 lines = view->height;
2688         case REQ_SCROLL_LINE_UP:
2689                 if (lines > view->offset)
2690                         lines = view->offset;
2692                 if (lines == 0) {
2693                         report("Cannot scroll beyond the first line");
2694                         return;
2695                 }
2697                 lines = -lines;
2698                 break;
2700         default:
2701                 die("request %d not handled in switch", request);
2702         }
2704         do_scroll_view(view, lines);
2707 /* Cursor moving */
2708 static void
2709 move_view(struct view *view, enum request request)
2711         int scroll_steps = 0;
2712         int steps;
2714         switch (request) {
2715         case REQ_MOVE_FIRST_LINE:
2716                 steps = -view->lineno;
2717                 break;
2719         case REQ_MOVE_LAST_LINE:
2720                 steps = view->lines - view->lineno - 1;
2721                 break;
2723         case REQ_MOVE_PAGE_UP:
2724                 steps = view->height > view->lineno
2725                       ? -view->lineno : -view->height;
2726                 break;
2728         case REQ_MOVE_PAGE_DOWN:
2729                 steps = view->lineno + view->height >= view->lines
2730                       ? view->lines - view->lineno - 1 : view->height;
2731                 break;
2733         case REQ_MOVE_UP:
2734                 steps = -1;
2735                 break;
2737         case REQ_MOVE_DOWN:
2738                 steps = 1;
2739                 break;
2741         default:
2742                 die("request %d not handled in switch", request);
2743         }
2745         if (steps <= 0 && view->lineno == 0) {
2746                 report("Cannot move beyond the first line");
2747                 return;
2749         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2750                 report("Cannot move beyond the last line");
2751                 return;
2752         }
2754         /* Move the current line */
2755         view->lineno += steps;
2756         assert(0 <= view->lineno && view->lineno < view->lines);
2758         /* Check whether the view needs to be scrolled */
2759         if (view->lineno < view->offset ||
2760             view->lineno >= view->offset + view->height) {
2761                 scroll_steps = steps;
2762                 if (steps < 0 && -steps > view->offset) {
2763                         scroll_steps = -view->offset;
2765                 } else if (steps > 0) {
2766                         if (view->lineno == view->lines - 1 &&
2767                             view->lines > view->height) {
2768                                 scroll_steps = view->lines - view->offset - 1;
2769                                 if (scroll_steps >= view->height)
2770                                         scroll_steps -= view->height - 1;
2771                         }
2772                 }
2773         }
2775         if (!view_is_displayed(view)) {
2776                 view->offset += scroll_steps;
2777                 assert(0 <= view->offset && view->offset < view->lines);
2778                 view->ops->select(view, &view->line[view->lineno]);
2779                 return;
2780         }
2782         /* Repaint the old "current" line if we be scrolling */
2783         if (ABS(steps) < view->height)
2784                 draw_view_line(view, view->lineno - steps - view->offset);
2786         if (scroll_steps) {
2787                 do_scroll_view(view, scroll_steps);
2788                 return;
2789         }
2791         /* Draw the current line */
2792         draw_view_line(view, view->lineno - view->offset);
2794         wnoutrefresh(view->win);
2795         report("");
2799 /*
2800  * Searching
2801  */
2803 static void search_view(struct view *view, enum request request);
2805 static bool
2806 grep_text(struct view *view, const char *text[])
2808         regmatch_t pmatch;
2809         size_t i;
2811         for (i = 0; text[i]; i++)
2812                 if (*text[i] &&
2813                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2814                         return TRUE;
2815         return FALSE;
2818 static void
2819 select_view_line(struct view *view, unsigned long lineno)
2821         unsigned long old_lineno = view->lineno;
2822         unsigned long old_offset = view->offset;
2824         if (goto_view_line(view, view->offset, lineno)) {
2825                 if (view_is_displayed(view)) {
2826                         if (old_offset != view->offset) {
2827                                 redraw_view(view);
2828                         } else {
2829                                 draw_view_line(view, old_lineno - view->offset);
2830                                 draw_view_line(view, view->lineno - view->offset);
2831                                 wnoutrefresh(view->win);
2832                         }
2833                 } else {
2834                         view->ops->select(view, &view->line[view->lineno]);
2835                 }
2836         }
2839 static void
2840 find_next(struct view *view, enum request request)
2842         unsigned long lineno = view->lineno;
2843         int direction;
2845         if (!*view->grep) {
2846                 if (!*opt_search)
2847                         report("No previous search");
2848                 else
2849                         search_view(view, request);
2850                 return;
2851         }
2853         switch (request) {
2854         case REQ_SEARCH:
2855         case REQ_FIND_NEXT:
2856                 direction = 1;
2857                 break;
2859         case REQ_SEARCH_BACK:
2860         case REQ_FIND_PREV:
2861                 direction = -1;
2862                 break;
2864         default:
2865                 return;
2866         }
2868         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2869                 lineno += direction;
2871         /* Note, lineno is unsigned long so will wrap around in which case it
2872          * will become bigger than view->lines. */
2873         for (; lineno < view->lines; lineno += direction) {
2874                 if (view->ops->grep(view, &view->line[lineno])) {
2875                         select_view_line(view, lineno);
2876                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2877                         return;
2878                 }
2879         }
2881         report("No match found for '%s'", view->grep);
2884 static void
2885 search_view(struct view *view, enum request request)
2887         int regex_err;
2889         if (view->regex) {
2890                 regfree(view->regex);
2891                 *view->grep = 0;
2892         } else {
2893                 view->regex = calloc(1, sizeof(*view->regex));
2894                 if (!view->regex)
2895                         return;
2896         }
2898         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2899         if (regex_err != 0) {
2900                 char buf[SIZEOF_STR] = "unknown error";
2902                 regerror(regex_err, view->regex, buf, sizeof(buf));
2903                 report("Search failed: %s", buf);
2904                 return;
2905         }
2907         string_copy(view->grep, opt_search);
2909         find_next(view, request);
2912 /*
2913  * Incremental updating
2914  */
2916 static void
2917 reset_view(struct view *view)
2919         int i;
2921         for (i = 0; i < view->lines; i++)
2922                 free(view->line[i].data);
2923         free(view->line);
2925         view->p_offset = view->offset;
2926         view->p_yoffset = view->yoffset;
2927         view->p_lineno = view->lineno;
2929         view->line = NULL;
2930         view->offset = 0;
2931         view->yoffset = 0;
2932         view->lines  = 0;
2933         view->lineno = 0;
2934         view->vid[0] = 0;
2935         view->update_secs = 0;
2938 static void
2939 free_argv(const char *argv[])
2941         int argc;
2943         for (argc = 0; argv[argc]; argc++)
2944                 free((void *) argv[argc]);
2947 static const char *
2948 format_arg(const char *name)
2950         static struct {
2951                 const char *name;
2952                 size_t namelen;
2953                 const char *value;
2954                 const char *value_if_empty;
2955         } vars[] = {
2956 #define FORMAT_VAR(name, value, value_if_empty) \
2957         { name, STRING_SIZE(name), value, value_if_empty }
2958                 FORMAT_VAR("%(directory)",      opt_path,       ""),
2959                 FORMAT_VAR("%(file)",           opt_file,       ""),
2960                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
2961                 FORMAT_VAR("%(head)",           ref_head,       ""),
2962                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
2963                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
2964         };
2965         int i;
2967         for (i = 0; i < ARRAY_SIZE(vars); i++)
2968                 if (!strncmp(name, vars[i].name, vars[i].namelen))
2969                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2971         return NULL;
2973 static bool
2974 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2976         char buf[SIZEOF_STR];
2977         int argc;
2978         bool noreplace = flags == FORMAT_NONE;
2980         free_argv(dst_argv);
2982         for (argc = 0; src_argv[argc]; argc++) {
2983                 const char *arg = src_argv[argc];
2984                 size_t bufpos = 0;
2986                 while (arg) {
2987                         char *next = strstr(arg, "%(");
2988                         int len = next - arg;
2989                         const char *value;
2991                         if (!next || noreplace) {
2992                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2993                                         noreplace = TRUE;
2994                                 len = strlen(arg);
2995                                 value = "";
2997                         } else {
2998                                 value = format_arg(next);
3000                                 if (!value) {
3001                                         report("Unknown replacement: `%s`", next);
3002                                         return FALSE;
3003                                 }
3004                         }
3006                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3007                                 return FALSE;
3009                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3010                 }
3012                 dst_argv[argc] = strdup(buf);
3013                 if (!dst_argv[argc])
3014                         break;
3015         }
3017         dst_argv[argc] = NULL;
3019         return src_argv[argc] == NULL;
3022 static bool
3023 restore_view_position(struct view *view)
3025         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3026                 return FALSE;
3028         /* Changing the view position cancels the restoring. */
3029         /* FIXME: Changing back to the first line is not detected. */
3030         if (view->offset != 0 || view->lineno != 0) {
3031                 view->p_restore = FALSE;
3032                 return FALSE;
3033         }
3035         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3036             view_is_displayed(view))
3037                 werase(view->win);
3039         view->yoffset = view->p_yoffset;
3040         view->p_restore = FALSE;
3042         return TRUE;
3045 static void
3046 end_update(struct view *view, bool force)
3048         if (!view->pipe)
3049                 return;
3050         while (!view->ops->read(view, NULL))
3051                 if (!force)
3052                         return;
3053         set_nonblocking_input(FALSE);
3054         if (force)
3055                 kill_io(view->pipe);
3056         done_io(view->pipe);
3057         view->pipe = NULL;
3060 static void
3061 setup_update(struct view *view, const char *vid)
3063         set_nonblocking_input(TRUE);
3064         reset_view(view);
3065         string_copy_rev(view->vid, vid);
3066         view->pipe = &view->io;
3067         view->start_time = time(NULL);
3070 static bool
3071 prepare_update(struct view *view, const char *argv[], const char *dir,
3072                enum format_flags flags)
3074         if (view->pipe)
3075                 end_update(view, TRUE);
3076         return init_io_rd(&view->io, argv, dir, flags);
3079 static bool
3080 prepare_update_file(struct view *view, const char *name)
3082         if (view->pipe)
3083                 end_update(view, TRUE);
3084         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3087 static bool
3088 begin_update(struct view *view, bool refresh)
3090         if (view->pipe)
3091                 end_update(view, TRUE);
3093         if (!refresh) {
3094                 if (view->ops->prepare) {
3095                         if (!view->ops->prepare(view))
3096                                 return FALSE;
3097                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3098                         return FALSE;
3099                 }
3101                 /* Put the current ref_* value to the view title ref
3102                  * member. This is needed by the blob view. Most other
3103                  * views sets it automatically after loading because the
3104                  * first line is a commit line. */
3105                 string_copy_rev(view->ref, view->id);
3106         }
3108         if (!start_io(&view->io))
3109                 return FALSE;
3111         setup_update(view, view->id);
3113         return TRUE;
3116 static bool
3117 update_view(struct view *view)
3119         char out_buffer[BUFSIZ * 2];
3120         char *line;
3121         /* Clear the view and redraw everything since the tree sorting
3122          * might have rearranged things. */
3123         bool redraw = view->lines == 0;
3124         bool can_read = TRUE;
3126         if (!view->pipe)
3127                 return TRUE;
3129         if (!io_can_read(view->pipe)) {
3130                 if (view->lines == 0 && view_is_displayed(view)) {
3131                         time_t secs = time(NULL) - view->start_time;
3133                         if (secs > 1 && secs > view->update_secs) {
3134                                 if (view->update_secs == 0)
3135                                         redraw_view(view);
3136                                 update_view_title(view);
3137                                 view->update_secs = secs;
3138                         }
3139                 }
3140                 return TRUE;
3141         }
3143         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3144                 if (opt_iconv_in != ICONV_NONE) {
3145                         ICONV_CONST char *inbuf = line;
3146                         size_t inlen = strlen(line) + 1;
3148                         char *outbuf = out_buffer;
3149                         size_t outlen = sizeof(out_buffer);
3151                         size_t ret;
3153                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3154                         if (ret != (size_t) -1)
3155                                 line = out_buffer;
3156                 }
3158                 if (!view->ops->read(view, line)) {
3159                         report("Allocation failure");
3160                         end_update(view, TRUE);
3161                         return FALSE;
3162                 }
3163         }
3165         {
3166                 unsigned long lines = view->lines;
3167                 int digits;
3169                 for (digits = 0; lines; digits++)
3170                         lines /= 10;
3172                 /* Keep the displayed view in sync with line number scaling. */
3173                 if (digits != view->digits) {
3174                         view->digits = digits;
3175                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3176                                 redraw = TRUE;
3177                 }
3178         }
3180         if (io_error(view->pipe)) {
3181                 report("Failed to read: %s", io_strerror(view->pipe));
3182                 end_update(view, TRUE);
3184         } else if (io_eof(view->pipe)) {
3185                 report("");
3186                 end_update(view, FALSE);
3187         }
3189         if (restore_view_position(view))
3190                 redraw = TRUE;
3192         if (!view_is_displayed(view))
3193                 return TRUE;
3195         if (redraw)
3196                 redraw_view_from(view, 0);
3197         else
3198                 redraw_view_dirty(view);
3200         /* Update the title _after_ the redraw so that if the redraw picks up a
3201          * commit reference in view->ref it'll be available here. */
3202         update_view_title(view);
3203         return TRUE;
3206 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3208 static struct line *
3209 add_line_data(struct view *view, void *data, enum line_type type)
3211         struct line *line;
3213         if (!realloc_lines(&view->line, view->lines, 1))
3214                 return NULL;
3216         line = &view->line[view->lines++];
3217         memset(line, 0, sizeof(*line));
3218         line->type = type;
3219         line->data = data;
3220         line->dirty = 1;
3222         return line;
3225 static struct line *
3226 add_line_text(struct view *view, const char *text, enum line_type type)
3228         char *data = text ? strdup(text) : NULL;
3230         return data ? add_line_data(view, data, type) : NULL;
3233 static struct line *
3234 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3236         char buf[SIZEOF_STR];
3237         va_list args;
3239         va_start(args, fmt);
3240         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3241                 buf[0] = 0;
3242         va_end(args);
3244         return buf[0] ? add_line_text(view, buf, type) : NULL;
3247 /*
3248  * View opening
3249  */
3251 enum open_flags {
3252         OPEN_DEFAULT = 0,       /* Use default view switching. */
3253         OPEN_SPLIT = 1,         /* Split current view. */
3254         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3255         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3256         OPEN_PREPARED = 32,     /* Open already prepared command. */
3257 };
3259 static void
3260 open_view(struct view *prev, enum request request, enum open_flags flags)
3262         bool split = !!(flags & OPEN_SPLIT);
3263         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3264         bool nomaximize = !!(flags & OPEN_REFRESH);
3265         struct view *view = VIEW(request);
3266         int nviews = displayed_views();
3267         struct view *base_view = display[0];
3269         if (view == prev && nviews == 1 && !reload) {
3270                 report("Already in %s view", view->name);
3271                 return;
3272         }
3274         if (view->git_dir && !opt_git_dir[0]) {
3275                 report("The %s view is disabled in pager view", view->name);
3276                 return;
3277         }
3279         if (split) {
3280                 display[1] = view;
3281                 current_view = 1;
3282         } else if (!nomaximize) {
3283                 /* Maximize the current view. */
3284                 memset(display, 0, sizeof(display));
3285                 current_view = 0;
3286                 display[current_view] = view;
3287         }
3289         /* No parent signals that this is the first loaded view. */
3290         if (prev && view != prev) {
3291                 view->parent = prev;
3292         }
3294         /* Resize the view when switching between split- and full-screen,
3295          * or when switching between two different full-screen views. */
3296         if (nviews != displayed_views() ||
3297             (nviews == 1 && base_view != display[0]))
3298                 resize_display();
3300         if (view->ops->open) {
3301                 if (view->pipe)
3302                         end_update(view, TRUE);
3303                 if (!view->ops->open(view)) {
3304                         report("Failed to load %s view", view->name);
3305                         return;
3306                 }
3307                 restore_view_position(view);
3309         } else if ((reload || strcmp(view->vid, view->id)) &&
3310                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3311                 report("Failed to load %s view", view->name);
3312                 return;
3313         }
3315         if (split && prev->lineno - prev->offset >= prev->height) {
3316                 /* Take the title line into account. */
3317                 int lines = prev->lineno - prev->offset - prev->height + 1;
3319                 /* Scroll the view that was split if the current line is
3320                  * outside the new limited view. */
3321                 do_scroll_view(prev, lines);
3322         }
3324         if (prev && view != prev && split && view_is_displayed(prev)) {
3325                 /* "Blur" the previous view. */
3326                 update_view_title(prev);
3327         }
3329         if (view->pipe && view->lines == 0) {
3330                 /* Clear the old view and let the incremental updating refill
3331                  * the screen. */
3332                 werase(view->win);
3333                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3334                 report("");
3335         } else if (view_is_displayed(view)) {
3336                 redraw_view(view);
3337                 report("");
3338         }
3341 static void
3342 open_external_viewer(const char *argv[], const char *dir)
3344         def_prog_mode();           /* save current tty modes */
3345         endwin();                  /* restore original tty modes */
3346         run_io_fg(argv, dir);
3347         fprintf(stderr, "Press Enter to continue");
3348         getc(opt_tty);
3349         reset_prog_mode();
3350         redraw_display(TRUE);
3353 static void
3354 open_mergetool(const char *file)
3356         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3358         open_external_viewer(mergetool_argv, opt_cdup);
3361 static void
3362 open_editor(const char *file)
3364         const char *editor_argv[] = { "vi", file, NULL };
3365         const char *editor;
3367         editor = getenv("GIT_EDITOR");
3368         if (!editor && *opt_editor)
3369                 editor = opt_editor;
3370         if (!editor)
3371                 editor = getenv("VISUAL");
3372         if (!editor)
3373                 editor = getenv("EDITOR");
3374         if (!editor)
3375                 editor = "vi";
3377         editor_argv[0] = editor;
3378         open_external_viewer(editor_argv, opt_cdup);
3381 static void
3382 open_run_request(enum request request)
3384         struct run_request *req = get_run_request(request);
3385         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3387         if (!req) {
3388                 report("Unknown run request");
3389                 return;
3390         }
3392         if (format_argv(argv, req->argv, FORMAT_ALL))
3393                 open_external_viewer(argv, NULL);
3394         free_argv(argv);
3397 /*
3398  * User request switch noodle
3399  */
3401 static int
3402 view_driver(struct view *view, enum request request)
3404         int i;
3406         if (request == REQ_NONE)
3407                 return TRUE;
3409         if (request > REQ_NONE) {
3410                 open_run_request(request);
3411                 /* FIXME: When all views can refresh always do this. */
3412                 if (view == VIEW(REQ_VIEW_STATUS) ||
3413                     view == VIEW(REQ_VIEW_MAIN) ||
3414                     view == VIEW(REQ_VIEW_LOG) ||
3415                     view == VIEW(REQ_VIEW_BRANCH) ||
3416                     view == VIEW(REQ_VIEW_STAGE))
3417                         request = REQ_REFRESH;
3418                 else
3419                         return TRUE;
3420         }
3422         if (view && view->lines) {
3423                 request = view->ops->request(view, request, &view->line[view->lineno]);
3424                 if (request == REQ_NONE)
3425                         return TRUE;
3426         }
3428         switch (request) {
3429         case REQ_MOVE_UP:
3430         case REQ_MOVE_DOWN:
3431         case REQ_MOVE_PAGE_UP:
3432         case REQ_MOVE_PAGE_DOWN:
3433         case REQ_MOVE_FIRST_LINE:
3434         case REQ_MOVE_LAST_LINE:
3435                 move_view(view, request);
3436                 break;
3438         case REQ_SCROLL_LEFT:
3439         case REQ_SCROLL_RIGHT:
3440         case REQ_SCROLL_LINE_DOWN:
3441         case REQ_SCROLL_LINE_UP:
3442         case REQ_SCROLL_PAGE_DOWN:
3443         case REQ_SCROLL_PAGE_UP:
3444                 scroll_view(view, request);
3445                 break;
3447         case REQ_VIEW_BLAME:
3448                 if (!opt_file[0]) {
3449                         report("No file chosen, press %s to open tree view",
3450                                get_key(view->keymap, REQ_VIEW_TREE));
3451                         break;
3452                 }
3453                 open_view(view, request, OPEN_DEFAULT);
3454                 break;
3456         case REQ_VIEW_BLOB:
3457                 if (!ref_blob[0]) {
3458                         report("No file chosen, press %s to open tree view",
3459                                get_key(view->keymap, REQ_VIEW_TREE));
3460                         break;
3461                 }
3462                 open_view(view, request, OPEN_DEFAULT);
3463                 break;
3465         case REQ_VIEW_PAGER:
3466                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3467                         report("No pager content, press %s to run command from prompt",
3468                                get_key(view->keymap, REQ_PROMPT));
3469                         break;
3470                 }
3471                 open_view(view, request, OPEN_DEFAULT);
3472                 break;
3474         case REQ_VIEW_STAGE:
3475                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3476                         report("No stage content, press %s to open the status view and choose file",
3477                                get_key(view->keymap, REQ_VIEW_STATUS));
3478                         break;
3479                 }
3480                 open_view(view, request, OPEN_DEFAULT);
3481                 break;
3483         case REQ_VIEW_STATUS:
3484                 if (opt_is_inside_work_tree == FALSE) {
3485                         report("The status view requires a working tree");
3486                         break;
3487                 }
3488                 open_view(view, request, OPEN_DEFAULT);
3489                 break;
3491         case REQ_VIEW_MAIN:
3492         case REQ_VIEW_DIFF:
3493         case REQ_VIEW_LOG:
3494         case REQ_VIEW_TREE:
3495         case REQ_VIEW_HELP:
3496         case REQ_VIEW_BRANCH:
3497                 open_view(view, request, OPEN_DEFAULT);
3498                 break;
3500         case REQ_NEXT:
3501         case REQ_PREVIOUS:
3502                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3504                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3505                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3506                    (view == VIEW(REQ_VIEW_DIFF) &&
3507                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3508                    (view == VIEW(REQ_VIEW_STAGE) &&
3509                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3510                    (view == VIEW(REQ_VIEW_BLOB) &&
3511                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3512                    (view == VIEW(REQ_VIEW_MAIN) &&
3513                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3514                         int line;
3516                         view = view->parent;
3517                         line = view->lineno;
3518                         move_view(view, request);
3519                         if (view_is_displayed(view))
3520                                 update_view_title(view);
3521                         if (line != view->lineno)
3522                                 view->ops->request(view, REQ_ENTER,
3523                                                    &view->line[view->lineno]);
3525                 } else {
3526                         move_view(view, request);
3527                 }
3528                 break;
3530         case REQ_VIEW_NEXT:
3531         {
3532                 int nviews = displayed_views();
3533                 int next_view = (current_view + 1) % nviews;
3535                 if (next_view == current_view) {
3536                         report("Only one view is displayed");
3537                         break;
3538                 }
3540                 current_view = next_view;
3541                 /* Blur out the title of the previous view. */
3542                 update_view_title(view);
3543                 report("");
3544                 break;
3545         }
3546         case REQ_REFRESH:
3547                 report("Refreshing is not yet supported for the %s view", view->name);
3548                 break;
3550         case REQ_MAXIMIZE:
3551                 if (displayed_views() == 2)
3552                         maximize_view(view);
3553                 break;
3555         case REQ_OPTIONS:
3556                 open_option_menu();
3557                 break;
3559         case REQ_TOGGLE_LINENO:
3560                 toggle_view_option(&opt_line_number, "line numbers");
3561                 break;
3563         case REQ_TOGGLE_DATE:
3564                 toggle_date();
3565                 break;
3567         case REQ_TOGGLE_AUTHOR:
3568                 toggle_author();
3569                 break;
3571         case REQ_TOGGLE_REV_GRAPH:
3572                 toggle_view_option(&opt_rev_graph, "revision graph display");
3573                 break;
3575         case REQ_TOGGLE_REFS:
3576                 toggle_view_option(&opt_show_refs, "reference display");
3577                 break;
3579         case REQ_TOGGLE_SORT_FIELD:
3580         case REQ_TOGGLE_SORT_ORDER:
3581                 report("Sorting is not yet supported for the %s view", view->name);
3582                 break;
3584         case REQ_SEARCH:
3585         case REQ_SEARCH_BACK:
3586                 search_view(view, request);
3587                 break;
3589         case REQ_FIND_NEXT:
3590         case REQ_FIND_PREV:
3591                 find_next(view, request);
3592                 break;
3594         case REQ_STOP_LOADING:
3595                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3596                         view = &views[i];
3597                         if (view->pipe)
3598                                 report("Stopped loading the %s view", view->name),
3599                         end_update(view, TRUE);
3600                 }
3601                 break;
3603         case REQ_SHOW_VERSION:
3604                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3605                 return TRUE;
3607         case REQ_SCREEN_REDRAW:
3608                 redraw_display(TRUE);
3609                 break;
3611         case REQ_EDIT:
3612                 report("Nothing to edit");
3613                 break;
3615         case REQ_ENTER:
3616                 report("Nothing to enter");
3617                 break;
3619         case REQ_VIEW_CLOSE:
3620                 /* XXX: Mark closed views by letting view->parent point to the
3621                  * view itself. Parents to closed view should never be
3622                  * followed. */
3623                 if (view->parent &&
3624                     view->parent->parent != view->parent) {
3625                         maximize_view(view->parent);
3626                         view->parent = view;
3627                         break;
3628                 }
3629                 /* Fall-through */
3630         case REQ_QUIT:
3631                 return FALSE;
3633         default:
3634                 report("Unknown key, press %s for help",
3635                        get_key(view->keymap, REQ_VIEW_HELP));
3636                 return TRUE;
3637         }
3639         return TRUE;
3643 /*
3644  * View backend utilities
3645  */
3647 enum sort_field {
3648         ORDERBY_NAME,
3649         ORDERBY_DATE,
3650         ORDERBY_AUTHOR,
3651 };
3653 struct sort_state {
3654         const enum sort_field *fields;
3655         size_t size, current;
3656         bool reverse;
3657 };
3659 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3660 #define get_sort_field(state) ((state).fields[(state).current])
3661 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3663 static void
3664 sort_view(struct view *view, enum request request, struct sort_state *state,
3665           int (*compare)(const void *, const void *))
3667         switch (request) {
3668         case REQ_TOGGLE_SORT_FIELD:
3669                 state->current = (state->current + 1) % state->size;
3670                 break;
3672         case REQ_TOGGLE_SORT_ORDER:
3673                 state->reverse = !state->reverse;
3674                 break;
3675         default:
3676                 die("Not a sort request");
3677         }
3679         qsort(view->line, view->lines, sizeof(*view->line), compare);
3680         redraw_view(view);
3683 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3685 /* Small author cache to reduce memory consumption. It uses binary
3686  * search to lookup or find place to position new entries. No entries
3687  * are ever freed. */
3688 static const char *
3689 get_author(const char *name)
3691         static const char **authors;
3692         static size_t authors_size;
3693         int from = 0, to = authors_size - 1;
3695         while (from <= to) {
3696                 size_t pos = (to + from) / 2;
3697                 int cmp = strcmp(name, authors[pos]);
3699                 if (!cmp)
3700                         return authors[pos];
3702                 if (cmp < 0)
3703                         to = pos - 1;
3704                 else
3705                         from = pos + 1;
3706         }
3708         if (!realloc_authors(&authors, authors_size, 1))
3709                 return NULL;
3710         name = strdup(name);
3711         if (!name)
3712                 return NULL;
3714         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3715         authors[from] = name;
3716         authors_size++;
3718         return name;
3721 static void
3722 parse_timezone(time_t *time, const char *zone)
3724         long tz;
3726         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3727         tz += ('0' - zone[2]) * 60 * 60;
3728         tz += ('0' - zone[3]) * 60;
3729         tz += ('0' - zone[4]);
3731         if (zone[0] == '-')
3732                 tz = -tz;
3734         *time -= 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, time_t *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                 *time = (time_t) atol(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 (!run_io_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 (!run_io_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 (!run_io_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         time_t 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, 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 time_t 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 (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4331                         report("Failed to load tree data");
4332                         return TRUE;
4333                 }
4335                 done_io(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                         kill_io(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->author ? &entry->time : NULL))
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 (!run_io_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                 opt_date ? mkdate(&entry->time) : "",
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 init_io_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         time_t 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 (!run_io_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 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4819                         report("Failed to load blame data");
4820                         return TRUE;
4821                 }
4823                 done_io(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                 commit->time = (time_t) atol(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         time_t *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 (!run_io(&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         done_io(&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 && opt_date ? mkdate(&commit->time) : "",
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         time_t 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, 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->author ? &branch->time : NULL))
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 (!run_io_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 (!run_io(&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                 done_io(&io);
5432                 return FALSE;
5433         }
5435         if (!view->line[view->lines - 1].data)
5436                 add_line_data(view, NULL, LINE_STAT_NONE);
5438         done_io(&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         run_io_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 run_io(io, staged_argv, opt_cdup, IO_WR);
5769         case LINE_STAT_UNSTAGED:
5770         case LINE_STAT_UNTRACKED:
5771                 return run_io(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 done_io(&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 done_io(&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 (!run_io_fg(reset_argv, opt_cdup))
5918                                 return FALSE;
5919                         if (status->old.mode == 0 && status->new.mode == 0)
5920                                 return TRUE;
5921                 }
5923                 return run_io_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 (!run_io(&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         done_io(&io);
6118         run_io_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         time_t 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         if (opt_line_graphics)
6438                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6440         filler = &fillers[DEFAULT];
6442         for (i = 0; i < graph->pos; i++) {
6443                 append_to_rev_graph(graph, filler->line);
6444                 if (graph_parent_is_merge(graph->prev) &&
6445                     graph->prev->pos == i)
6446                         filler = &fillers[RSHARP];
6448                 append_to_rev_graph(graph, filler->separator);
6449         }
6451         /* Place the symbol for this revision. */
6452         append_to_rev_graph(graph, symbol);
6454         if (graph->prev->size > graph->size)
6455                 filler = &fillers[RDIAG];
6456         else
6457                 filler = &fillers[DEFAULT];
6459         i++;
6461         for (; i < graph->size; i++) {
6462                 append_to_rev_graph(graph, filler->separator);
6463                 append_to_rev_graph(graph, filler->line);
6464                 if (graph_parent_is_merge(graph->prev) &&
6465                     i < graph->prev->pos + graph->parents->size)
6466                         filler = &fillers[RSHARP];
6467                 if (graph->prev->size > graph->size)
6468                         filler = &fillers[LDIAG];
6469         }
6471         if (graph->prev->size > graph->size) {
6472                 append_to_rev_graph(graph, filler->separator);
6473                 if (filler->line != ' ')
6474                         append_to_rev_graph(graph, filler->line);
6475         }
6478 /* Prepare the next rev graph */
6479 static void
6480 prepare_rev_graph(struct rev_graph *graph)
6482         size_t i;
6484         /* First, traverse all lines of revisions up to the active one. */
6485         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6486                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6487                         break;
6489                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6490         }
6492         /* Interleave the new revision parent(s). */
6493         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6494                 push_rev_graph(graph->next, graph->parents->rev[i]);
6496         /* Lastly, put any remaining revisions. */
6497         for (i = graph->pos + 1; i < graph->size; i++)
6498                 push_rev_graph(graph->next, graph->rev[i]);
6501 static void
6502 update_rev_graph(struct view *view, struct rev_graph *graph)
6504         /* If this is the finalizing update ... */
6505         if (graph->commit)
6506                 prepare_rev_graph(graph);
6508         /* Graph visualization needs a one rev look-ahead,
6509          * so the first update doesn't visualize anything. */
6510         if (!graph->prev->commit)
6511                 return;
6513         if (view->lines > 2)
6514                 view->line[view->lines - 3].dirty = 1;
6515         if (view->lines > 1)
6516                 view->line[view->lines - 2].dirty = 1;
6517         draw_rev_graph(graph->prev);
6518         done_rev_graph(graph->prev->prev);
6522 /*
6523  * Main view backend
6524  */
6526 static const char *main_argv[SIZEOF_ARG] = {
6527         "git", "log", "--no-color", "--pretty=raw", "--parents",
6528                       "--topo-order", "%(head)", NULL
6529 };
6531 static bool
6532 main_draw(struct view *view, struct line *line, unsigned int lineno)
6534         struct commit *commit = line->data;
6536         if (!commit->author)
6537                 return FALSE;
6539         if (opt_date && draw_date(view, &commit->time))
6540                 return TRUE;
6542         if (opt_author && draw_author(view, commit->author))
6543                 return TRUE;
6545         if (opt_rev_graph && commit->graph_size &&
6546             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6547                 return TRUE;
6549         if (opt_show_refs && commit->refs) {
6550                 size_t i;
6552                 for (i = 0; i < commit->refs->size; i++) {
6553                         struct ref *ref = commit->refs->refs[i];
6554                         enum line_type type;
6556                         if (ref->head)
6557                                 type = LINE_MAIN_HEAD;
6558                         else if (ref->ltag)
6559                                 type = LINE_MAIN_LOCAL_TAG;
6560                         else if (ref->tag)
6561                                 type = LINE_MAIN_TAG;
6562                         else if (ref->tracked)
6563                                 type = LINE_MAIN_TRACKED;
6564                         else if (ref->remote)
6565                                 type = LINE_MAIN_REMOTE;
6566                         else
6567                                 type = LINE_MAIN_REF;
6569                         if (draw_text(view, type, "[", TRUE) ||
6570                             draw_text(view, type, ref->name, TRUE) ||
6571                             draw_text(view, type, "]", TRUE))
6572                                 return TRUE;
6574                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6575                                 return TRUE;
6576                 }
6577         }
6579         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6580         return TRUE;
6583 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6584 static bool
6585 main_read(struct view *view, char *line)
6587         static struct rev_graph *graph = graph_stacks;
6588         enum line_type type;
6589         struct commit *commit;
6591         if (!line) {
6592                 int i;
6594                 if (!view->lines && !view->parent)
6595                         die("No revisions match the given arguments.");
6596                 if (view->lines > 0) {
6597                         commit = view->line[view->lines - 1].data;
6598                         view->line[view->lines - 1].dirty = 1;
6599                         if (!commit->author) {
6600                                 view->lines--;
6601                                 free(commit);
6602                                 graph->commit = NULL;
6603                         }
6604                 }
6605                 update_rev_graph(view, graph);
6607                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6608                         clear_rev_graph(&graph_stacks[i]);
6609                 return TRUE;
6610         }
6612         type = get_line_type(line);
6613         if (type == LINE_COMMIT) {
6614                 commit = calloc(1, sizeof(struct commit));
6615                 if (!commit)
6616                         return FALSE;
6618                 line += STRING_SIZE("commit ");
6619                 if (*line == '-') {
6620                         graph->boundary = 1;
6621                         line++;
6622                 }
6624                 string_copy_rev(commit->id, line);
6625                 commit->refs = get_ref_list(commit->id);
6626                 graph->commit = commit;
6627                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6629                 while ((line = strchr(line, ' '))) {
6630                         line++;
6631                         push_rev_graph(graph->parents, line);
6632                         commit->has_parents = TRUE;
6633                 }
6634                 return TRUE;
6635         }
6637         if (!view->lines)
6638                 return TRUE;
6639         commit = view->line[view->lines - 1].data;
6641         switch (type) {
6642         case LINE_PARENT:
6643                 if (commit->has_parents)
6644                         break;
6645                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6646                 break;
6648         case LINE_AUTHOR:
6649                 parse_author_line(line + STRING_SIZE("author "),
6650                                   &commit->author, &commit->time);
6651                 update_rev_graph(view, graph);
6652                 graph = graph->next;
6653                 break;
6655         default:
6656                 /* Fill in the commit title if it has not already been set. */
6657                 if (commit->title[0])
6658                         break;
6660                 /* Require titles to start with a non-space character at the
6661                  * offset used by git log. */
6662                 if (strncmp(line, "    ", 4))
6663                         break;
6664                 line += 4;
6665                 /* Well, if the title starts with a whitespace character,
6666                  * try to be forgiving.  Otherwise we end up with no title. */
6667                 while (isspace(*line))
6668                         line++;
6669                 if (*line == '\0')
6670                         break;
6671                 /* FIXME: More graceful handling of titles; append "..." to
6672                  * shortened titles, etc. */
6674                 string_expand(commit->title, sizeof(commit->title), line, 1);
6675                 view->line[view->lines - 1].dirty = 1;
6676         }
6678         return TRUE;
6681 static enum request
6682 main_request(struct view *view, enum request request, struct line *line)
6684         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6686         switch (request) {
6687         case REQ_ENTER:
6688                 open_view(view, REQ_VIEW_DIFF, flags);
6689                 break;
6690         case REQ_REFRESH:
6691                 load_refs();
6692                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6693                 break;
6694         default:
6695                 return request;
6696         }
6698         return REQ_NONE;
6701 static bool
6702 grep_refs(struct ref_list *list, regex_t *regex)
6704         regmatch_t pmatch;
6705         size_t i;
6707         if (!opt_show_refs || !list)
6708                 return FALSE;
6710         for (i = 0; i < list->size; i++) {
6711                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6712                         return TRUE;
6713         }
6715         return FALSE;
6718 static bool
6719 main_grep(struct view *view, struct line *line)
6721         struct commit *commit = line->data;
6722         const char *text[] = {
6723                 commit->title,
6724                 opt_author ? commit->author : "",
6725                 opt_date ? mkdate(&commit->time) : "",
6726                 NULL
6727         };
6729         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6732 static void
6733 main_select(struct view *view, struct line *line)
6735         struct commit *commit = line->data;
6737         string_copy_rev(view->ref, commit->id);
6738         string_copy_rev(ref_commit, view->ref);
6741 static struct view_ops main_ops = {
6742         "commit",
6743         main_argv,
6744         NULL,
6745         main_read,
6746         main_draw,
6747         main_request,
6748         main_grep,
6749         main_select,
6750 };
6753 /*
6754  * Unicode / UTF-8 handling
6755  *
6756  * NOTE: Much of the following code for dealing with Unicode is derived from
6757  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6758  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6759  */
6761 static inline int
6762 unicode_width(unsigned long c)
6764         if (c >= 0x1100 &&
6765            (c <= 0x115f                         /* Hangul Jamo */
6766             || c == 0x2329
6767             || c == 0x232a
6768             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6769                                                 /* CJK ... Yi */
6770             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6771             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6772             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6773             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6774             || (c >= 0xffe0  && c <= 0xffe6)
6775             || (c >= 0x20000 && c <= 0x2fffd)
6776             || (c >= 0x30000 && c <= 0x3fffd)))
6777                 return 2;
6779         if (c == '\t')
6780                 return opt_tab_size;
6782         return 1;
6785 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6786  * Illegal bytes are set one. */
6787 static const unsigned char utf8_bytes[256] = {
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         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,
6793         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,
6794         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,
6795         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,
6796 };
6798 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6799 static inline unsigned long
6800 utf8_to_unicode(const char *string, size_t length)
6802         unsigned long unicode;
6804         switch (length) {
6805         case 1:
6806                 unicode  =   string[0];
6807                 break;
6808         case 2:
6809                 unicode  =  (string[0] & 0x1f) << 6;
6810                 unicode +=  (string[1] & 0x3f);
6811                 break;
6812         case 3:
6813                 unicode  =  (string[0] & 0x0f) << 12;
6814                 unicode += ((string[1] & 0x3f) << 6);
6815                 unicode +=  (string[2] & 0x3f);
6816                 break;
6817         case 4:
6818                 unicode  =  (string[0] & 0x0f) << 18;
6819                 unicode += ((string[1] & 0x3f) << 12);
6820                 unicode += ((string[2] & 0x3f) << 6);
6821                 unicode +=  (string[3] & 0x3f);
6822                 break;
6823         case 5:
6824                 unicode  =  (string[0] & 0x0f) << 24;
6825                 unicode += ((string[1] & 0x3f) << 18);
6826                 unicode += ((string[2] & 0x3f) << 12);
6827                 unicode += ((string[3] & 0x3f) << 6);
6828                 unicode +=  (string[4] & 0x3f);
6829                 break;
6830         case 6:
6831                 unicode  =  (string[0] & 0x01) << 30;
6832                 unicode += ((string[1] & 0x3f) << 24);
6833                 unicode += ((string[2] & 0x3f) << 18);
6834                 unicode += ((string[3] & 0x3f) << 12);
6835                 unicode += ((string[4] & 0x3f) << 6);
6836                 unicode +=  (string[5] & 0x3f);
6837                 break;
6838         default:
6839                 die("Invalid Unicode length");
6840         }
6842         /* Invalid characters could return the special 0xfffd value but NUL
6843          * should be just as good. */
6844         return unicode > 0xffff ? 0 : unicode;
6847 /* Calculates how much of string can be shown within the given maximum width
6848  * and sets trimmed parameter to non-zero value if all of string could not be
6849  * shown. If the reserve flag is TRUE, it will reserve at least one
6850  * trailing character, which can be useful when drawing a delimiter.
6851  *
6852  * Returns the number of bytes to output from string to satisfy max_width. */
6853 static size_t
6854 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6856         const char *string = *start;
6857         const char *end = strchr(string, '\0');
6858         unsigned char last_bytes = 0;
6859         size_t last_ucwidth = 0;
6861         *width = 0;
6862         *trimmed = 0;
6864         while (string < end) {
6865                 int c = *(unsigned char *) string;
6866                 unsigned char bytes = utf8_bytes[c];
6867                 size_t ucwidth;
6868                 unsigned long unicode;
6870                 if (string + bytes > end)
6871                         break;
6873                 /* Change representation to figure out whether
6874                  * it is a single- or double-width character. */
6876                 unicode = utf8_to_unicode(string, bytes);
6877                 /* FIXME: Graceful handling of invalid Unicode character. */
6878                 if (!unicode)
6879                         break;
6881                 ucwidth = unicode_width(unicode);
6882                 if (skip > 0) {
6883                         skip -= ucwidth <= skip ? ucwidth : skip;
6884                         *start += bytes;
6885                 }
6886                 *width  += ucwidth;
6887                 if (*width > max_width) {
6888                         *trimmed = 1;
6889                         *width -= ucwidth;
6890                         if (reserve && *width == max_width) {
6891                                 string -= last_bytes;
6892                                 *width -= last_ucwidth;
6893                         }
6894                         break;
6895                 }
6897                 string  += bytes;
6898                 last_bytes = ucwidth ? bytes : 0;
6899                 last_ucwidth = ucwidth;
6900         }
6902         return string - *start;
6906 /*
6907  * Status management
6908  */
6910 /* Whether or not the curses interface has been initialized. */
6911 static bool cursed = FALSE;
6913 /* Terminal hacks and workarounds. */
6914 static bool use_scroll_redrawwin;
6915 static bool use_scroll_status_wclear;
6917 /* The status window is used for polling keystrokes. */
6918 static WINDOW *status_win;
6920 /* Reading from the prompt? */
6921 static bool input_mode = FALSE;
6923 static bool status_empty = FALSE;
6925 /* Update status and title window. */
6926 static void
6927 report(const char *msg, ...)
6929         struct view *view = display[current_view];
6931         if (input_mode)
6932                 return;
6934         if (!view) {
6935                 char buf[SIZEOF_STR];
6936                 va_list args;
6938                 va_start(args, msg);
6939                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6940                         buf[sizeof(buf) - 1] = 0;
6941                         buf[sizeof(buf) - 2] = '.';
6942                         buf[sizeof(buf) - 3] = '.';
6943                         buf[sizeof(buf) - 4] = '.';
6944                 }
6945                 va_end(args);
6946                 die("%s", buf);
6947         }
6949         if (!status_empty || *msg) {
6950                 va_list args;
6952                 va_start(args, msg);
6954                 wmove(status_win, 0, 0);
6955                 if (view->has_scrolled && use_scroll_status_wclear)
6956                         wclear(status_win);
6957                 if (*msg) {
6958                         vwprintw(status_win, msg, args);
6959                         status_empty = FALSE;
6960                 } else {
6961                         status_empty = TRUE;
6962                 }
6963                 wclrtoeol(status_win);
6964                 wnoutrefresh(status_win);
6966                 va_end(args);
6967         }
6969         update_view_title(view);
6972 /* Controls when nodelay should be in effect when polling user input. */
6973 static void
6974 set_nonblocking_input(bool loading)
6976         static unsigned int loading_views;
6978         if ((loading == FALSE && loading_views-- == 1) ||
6979             (loading == TRUE  && loading_views++ == 0))
6980                 nodelay(status_win, loading);
6983 static void
6984 init_display(void)
6986         const char *term;
6987         int x, y;
6989         /* Initialize the curses library */
6990         if (isatty(STDIN_FILENO)) {
6991                 cursed = !!initscr();
6992                 opt_tty = stdin;
6993         } else {
6994                 /* Leave stdin and stdout alone when acting as a pager. */
6995                 opt_tty = fopen("/dev/tty", "r+");
6996                 if (!opt_tty)
6997                         die("Failed to open /dev/tty");
6998                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6999         }
7001         if (!cursed)
7002                 die("Failed to initialize curses");
7004         nonl();         /* Disable conversion and detect newlines from input. */
7005         cbreak();       /* Take input chars one at a time, no wait for \n */
7006         noecho();       /* Don't echo input */
7007         leaveok(stdscr, FALSE);
7009         if (has_colors())
7010                 init_colors();
7012         getmaxyx(stdscr, y, x);
7013         status_win = newwin(1, 0, y - 1, 0);
7014         if (!status_win)
7015                 die("Failed to create status window");
7017         /* Enable keyboard mapping */
7018         keypad(status_win, TRUE);
7019         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7021         TABSIZE = opt_tab_size;
7022         if (opt_line_graphics) {
7023                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
7024         }
7026         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7027         if (term && !strcmp(term, "gnome-terminal")) {
7028                 /* In the gnome-terminal-emulator, the message from
7029                  * scrolling up one line when impossible followed by
7030                  * scrolling down one line causes corruption of the
7031                  * status line. This is fixed by calling wclear. */
7032                 use_scroll_status_wclear = TRUE;
7033                 use_scroll_redrawwin = FALSE;
7035         } else if (term && !strcmp(term, "xrvt-xpm")) {
7036                 /* No problems with full optimizations in xrvt-(unicode)
7037                  * and aterm. */
7038                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7040         } else {
7041                 /* When scrolling in (u)xterm the last line in the
7042                  * scrolling direction will update slowly. */
7043                 use_scroll_redrawwin = TRUE;
7044                 use_scroll_status_wclear = FALSE;
7045         }
7048 static int
7049 get_input(int prompt_position)
7051         struct view *view;
7052         int i, key, cursor_y, cursor_x;
7054         if (prompt_position)
7055                 input_mode = TRUE;
7057         while (TRUE) {
7058                 foreach_view (view, i) {
7059                         update_view(view);
7060                         if (view_is_displayed(view) && view->has_scrolled &&
7061                             use_scroll_redrawwin)
7062                                 redrawwin(view->win);
7063                         view->has_scrolled = FALSE;
7064                 }
7066                 /* Update the cursor position. */
7067                 if (prompt_position) {
7068                         getbegyx(status_win, cursor_y, cursor_x);
7069                         cursor_x = prompt_position;
7070                 } else {
7071                         view = display[current_view];
7072                         getbegyx(view->win, cursor_y, cursor_x);
7073                         cursor_x = view->width - 1;
7074                         cursor_y += view->lineno - view->offset;
7075                 }
7076                 setsyx(cursor_y, cursor_x);
7078                 /* Refresh, accept single keystroke of input */
7079                 doupdate();
7080                 key = wgetch(status_win);
7082                 /* wgetch() with nodelay() enabled returns ERR when
7083                  * there's no input. */
7084                 if (key == ERR) {
7086                 } else if (key == KEY_RESIZE) {
7087                         int height, width;
7089                         getmaxyx(stdscr, height, width);
7091                         wresize(status_win, 1, width);
7092                         mvwin(status_win, height - 1, 0);
7093                         wnoutrefresh(status_win);
7094                         resize_display();
7095                         redraw_display(TRUE);
7097                 } else {
7098                         input_mode = FALSE;
7099                         return key;
7100                 }
7101         }
7104 static char *
7105 prompt_input(const char *prompt, input_handler handler, void *data)
7107         enum input_status status = INPUT_OK;
7108         static char buf[SIZEOF_STR];
7109         size_t pos = 0;
7111         buf[pos] = 0;
7113         while (status == INPUT_OK || status == INPUT_SKIP) {
7114                 int key;
7116                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7117                 wclrtoeol(status_win);
7119                 key = get_input(pos + 1);
7120                 switch (key) {
7121                 case KEY_RETURN:
7122                 case KEY_ENTER:
7123                 case '\n':
7124                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7125                         break;
7127                 case KEY_BACKSPACE:
7128                         if (pos > 0)
7129                                 buf[--pos] = 0;
7130                         else
7131                                 status = INPUT_CANCEL;
7132                         break;
7134                 case KEY_ESC:
7135                         status = INPUT_CANCEL;
7136                         break;
7138                 default:
7139                         if (pos >= sizeof(buf)) {
7140                                 report("Input string too long");
7141                                 return NULL;
7142                         }
7144                         status = handler(data, buf, key);
7145                         if (status == INPUT_OK)
7146                                 buf[pos++] = (char) key;
7147                 }
7148         }
7150         /* Clear the status window */
7151         status_empty = FALSE;
7152         report("");
7154         if (status == INPUT_CANCEL)
7155                 return NULL;
7157         buf[pos++] = 0;
7159         return buf;
7162 static enum input_status
7163 prompt_yesno_handler(void *data, char *buf, int c)
7165         if (c == 'y' || c == 'Y')
7166                 return INPUT_STOP;
7167         if (c == 'n' || c == 'N')
7168                 return INPUT_CANCEL;
7169         return INPUT_SKIP;
7172 static bool
7173 prompt_yesno(const char *prompt)
7175         char prompt2[SIZEOF_STR];
7177         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7178                 return FALSE;
7180         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7183 static enum input_status
7184 read_prompt_handler(void *data, char *buf, int c)
7186         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7189 static char *
7190 read_prompt(const char *prompt)
7192         return prompt_input(prompt, read_prompt_handler, NULL);
7195 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7197         enum input_status status = INPUT_OK;
7198         int size = 0;
7200         while (items[size].text)
7201                 size++;
7203         while (status == INPUT_OK) {
7204                 const struct menu_item *item = &items[*selected];
7205                 int key;
7206                 int i;
7208                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7209                           prompt, *selected + 1, size);
7210                 if (item->hotkey)
7211                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7212                 wprintw(status_win, "%s", item->text);
7213                 wclrtoeol(status_win);
7215                 key = get_input(COLS - 1);
7216                 switch (key) {
7217                 case KEY_RETURN:
7218                 case KEY_ENTER:
7219                 case '\n':
7220                         status = INPUT_STOP;
7221                         break;
7223                 case KEY_LEFT:
7224                 case KEY_UP:
7225                         *selected = *selected - 1;
7226                         if (*selected < 0)
7227                                 *selected = size - 1;
7228                         break;
7230                 case KEY_RIGHT:
7231                 case KEY_DOWN:
7232                         *selected = (*selected + 1) % size;
7233                         break;
7235                 case KEY_ESC:
7236                         status = INPUT_CANCEL;
7237                         break;
7239                 default:
7240                         for (i = 0; items[i].text; i++)
7241                                 if (items[i].hotkey == key) {
7242                                         *selected = i;
7243                                         status = INPUT_STOP;
7244                                         break;
7245                                 }
7246                 }
7247         }
7249         /* Clear the status window */
7250         status_empty = FALSE;
7251         report("");
7253         return status != INPUT_CANCEL;
7256 /*
7257  * Repository properties
7258  */
7260 static struct ref **refs = NULL;
7261 static size_t refs_size = 0;
7263 static struct ref_list **ref_lists = NULL;
7264 static size_t ref_lists_size = 0;
7266 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7267 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7268 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7270 static int
7271 compare_refs(const void *ref1_, const void *ref2_)
7273         const struct ref *ref1 = *(const struct ref **)ref1_;
7274         const struct ref *ref2 = *(const struct ref **)ref2_;
7276         if (ref1->tag != ref2->tag)
7277                 return ref2->tag - ref1->tag;
7278         if (ref1->ltag != ref2->ltag)
7279                 return ref2->ltag - ref2->ltag;
7280         if (ref1->head != ref2->head)
7281                 return ref2->head - ref1->head;
7282         if (ref1->tracked != ref2->tracked)
7283                 return ref2->tracked - ref1->tracked;
7284         if (ref1->remote != ref2->remote)
7285                 return ref2->remote - ref1->remote;
7286         return strcmp(ref1->name, ref2->name);
7289 static void
7290 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7292         size_t i;
7294         for (i = 0; i < refs_size; i++)
7295                 if (!visitor(data, refs[i]))
7296                         break;
7299 static struct ref_list *
7300 get_ref_list(const char *id)
7302         struct ref_list *list;
7303         size_t i;
7305         for (i = 0; i < ref_lists_size; i++)
7306                 if (!strcmp(id, ref_lists[i]->id))
7307                         return ref_lists[i];
7309         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7310                 return NULL;
7311         list = calloc(1, sizeof(*list));
7312         if (!list)
7313                 return NULL;
7315         for (i = 0; i < refs_size; i++) {
7316                 if (!strcmp(id, refs[i]->id) &&
7317                     realloc_refs_list(&list->refs, list->size, 1))
7318                         list->refs[list->size++] = refs[i];
7319         }
7321         if (!list->refs) {
7322                 free(list);
7323                 return NULL;
7324         }
7326         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7327         ref_lists[ref_lists_size++] = list;
7328         return list;
7331 static int
7332 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7334         struct ref *ref = NULL;
7335         bool tag = FALSE;
7336         bool ltag = FALSE;
7337         bool remote = FALSE;
7338         bool tracked = FALSE;
7339         bool head = FALSE;
7340         int from = 0, to = refs_size - 1;
7342         if (!prefixcmp(name, "refs/tags/")) {
7343                 if (!suffixcmp(name, namelen, "^{}")) {
7344                         namelen -= 3;
7345                         name[namelen] = 0;
7346                 } else {
7347                         ltag = TRUE;
7348                 }
7350                 tag = TRUE;
7351                 namelen -= STRING_SIZE("refs/tags/");
7352                 name    += STRING_SIZE("refs/tags/");
7354         } else if (!prefixcmp(name, "refs/remotes/")) {
7355                 remote = TRUE;
7356                 namelen -= STRING_SIZE("refs/remotes/");
7357                 name    += STRING_SIZE("refs/remotes/");
7358                 tracked  = !strcmp(opt_remote, name);
7360         } else if (!prefixcmp(name, "refs/heads/")) {
7361                 namelen -= STRING_SIZE("refs/heads/");
7362                 name    += STRING_SIZE("refs/heads/");
7363                 head     = !strncmp(opt_head, name, namelen);
7365         } else if (!strcmp(name, "HEAD")) {
7366                 string_ncopy(opt_head_rev, id, idlen);
7367                 return OK;
7368         }
7370         /* If we are reloading or it's an annotated tag, replace the
7371          * previous SHA1 with the resolved commit id; relies on the fact
7372          * git-ls-remote lists the commit id of an annotated tag right
7373          * before the commit id it points to. */
7374         while (from <= to) {
7375                 size_t pos = (to + from) / 2;
7376                 int cmp = strcmp(name, refs[pos]->name);
7378                 if (!cmp) {
7379                         ref = refs[pos];
7380                         break;
7381                 }
7383                 if (cmp < 0)
7384                         to = pos - 1;
7385                 else
7386                         from = pos + 1;
7387         }
7389         if (!ref) {
7390                 if (!realloc_refs(&refs, refs_size, 1))
7391                         return ERR;
7392                 ref = calloc(1, sizeof(*ref) + namelen);
7393                 if (!ref)
7394                         return ERR;
7395                 memmove(refs + from + 1, refs + from,
7396                         (refs_size - from) * sizeof(*refs));
7397                 refs[from] = ref;
7398                 strncpy(ref->name, name, namelen);
7399                 refs_size++;
7400         }
7402         ref->head = head;
7403         ref->tag = tag;
7404         ref->ltag = ltag;
7405         ref->remote = remote;
7406         ref->tracked = tracked;
7407         string_copy_rev(ref->id, id);
7409         return OK;
7412 static int
7413 load_refs(void)
7415         const char *head_argv[] = {
7416                 "git", "symbolic-ref", "HEAD", NULL
7417         };
7418         static const char *ls_remote_argv[SIZEOF_ARG] = {
7419                 "git", "ls-remote", opt_git_dir, NULL
7420         };
7421         static bool init = FALSE;
7422         size_t i;
7424         if (!init) {
7425                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7426                 init = TRUE;
7427         }
7429         if (!*opt_git_dir)
7430                 return OK;
7432         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7433             !prefixcmp(opt_head, "refs/heads/")) {
7434                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7436                 memmove(opt_head, offset, strlen(offset) + 1);
7437         }
7439         for (i = 0; i < refs_size; i++)
7440                 refs[i]->id[0] = 0;
7442         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7443                 return ERR;
7445         /* Update the ref lists to reflect changes. */
7446         for (i = 0; i < ref_lists_size; i++) {
7447                 struct ref_list *list = ref_lists[i];
7448                 size_t old, new;
7450                 for (old = new = 0; old < list->size; old++)
7451                         if (!strcmp(list->id, list->refs[old]->id))
7452                                 list->refs[new++] = list->refs[old];
7453                 list->size = new;
7454         }
7456         return OK;
7459 static void
7460 set_remote_branch(const char *name, const char *value, size_t valuelen)
7462         if (!strcmp(name, ".remote")) {
7463                 string_ncopy(opt_remote, value, valuelen);
7465         } else if (*opt_remote && !strcmp(name, ".merge")) {
7466                 size_t from = strlen(opt_remote);
7468                 if (!prefixcmp(value, "refs/heads/"))
7469                         value += STRING_SIZE("refs/heads/");
7471                 if (!string_format_from(opt_remote, &from, "/%s", value))
7472                         opt_remote[0] = 0;
7473         }
7476 static void
7477 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7479         const char *argv[SIZEOF_ARG] = { name, "=" };
7480         int argc = 1 + (cmd == option_set_command);
7481         int error = ERR;
7483         if (!argv_from_string(argv, &argc, value))
7484                 config_msg = "Too many option arguments";
7485         else
7486                 error = cmd(argc, argv);
7488         if (error == ERR)
7489                 warn("Option 'tig.%s': %s", name, config_msg);
7492 static bool
7493 set_environment_variable(const char *name, const char *value)
7495         size_t len = strlen(name) + 1 + strlen(value) + 1;
7496         char *env = malloc(len);
7498         if (env &&
7499             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7500             putenv(env) == 0)
7501                 return TRUE;
7502         free(env);
7503         return FALSE;
7506 static void
7507 set_work_tree(const char *value)
7509         char cwd[SIZEOF_STR];
7511         if (!getcwd(cwd, sizeof(cwd)))
7512                 die("Failed to get cwd path: %s", strerror(errno));
7513         if (chdir(opt_git_dir) < 0)
7514                 die("Failed to chdir(%s): %s", strerror(errno));
7515         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7516                 die("Failed to get git path: %s", strerror(errno));
7517         if (chdir(cwd) < 0)
7518                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7519         if (chdir(value) < 0)
7520                 die("Failed to chdir(%s): %s", value, strerror(errno));
7521         if (!getcwd(cwd, sizeof(cwd)))
7522                 die("Failed to get cwd path: %s", strerror(errno));
7523         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7524                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7525         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7526                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7527         opt_is_inside_work_tree = TRUE;
7530 static int
7531 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7533         if (!strcmp(name, "i18n.commitencoding"))
7534                 string_ncopy(opt_encoding, value, valuelen);
7536         else if (!strcmp(name, "core.editor"))
7537                 string_ncopy(opt_editor, value, valuelen);
7539         else if (!strcmp(name, "core.worktree"))
7540                 set_work_tree(value);
7542         else if (!prefixcmp(name, "tig.color."))
7543                 set_repo_config_option(name + 10, value, option_color_command);
7545         else if (!prefixcmp(name, "tig.bind."))
7546                 set_repo_config_option(name + 9, value, option_bind_command);
7548         else if (!prefixcmp(name, "tig."))
7549                 set_repo_config_option(name + 4, value, option_set_command);
7551         else if (*opt_head && !prefixcmp(name, "branch.") &&
7552                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7553                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7555         return OK;
7558 static int
7559 load_git_config(void)
7561         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7563         return run_io_load(config_list_argv, "=", read_repo_config_option);
7566 static int
7567 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7569         if (!opt_git_dir[0]) {
7570                 string_ncopy(opt_git_dir, name, namelen);
7572         } else if (opt_is_inside_work_tree == -1) {
7573                 /* This can be 3 different values depending on the
7574                  * version of git being used. If git-rev-parse does not
7575                  * understand --is-inside-work-tree it will simply echo
7576                  * the option else either "true" or "false" is printed.
7577                  * Default to true for the unknown case. */
7578                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7580         } else if (*name == '.') {
7581                 string_ncopy(opt_cdup, name, namelen);
7583         } else {
7584                 string_ncopy(opt_prefix, name, namelen);
7585         }
7587         return OK;
7590 static int
7591 load_repo_info(void)
7593         const char *rev_parse_argv[] = {
7594                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7595                         "--show-cdup", "--show-prefix", NULL
7596         };
7598         return run_io_load(rev_parse_argv, "=", read_repo_info);
7602 /*
7603  * Main
7604  */
7606 static const char usage[] =
7607 "tig " TIG_VERSION " (" __DATE__ ")\n"
7608 "\n"
7609 "Usage: tig        [options] [revs] [--] [paths]\n"
7610 "   or: tig show   [options] [revs] [--] [paths]\n"
7611 "   or: tig blame  [rev] path\n"
7612 "   or: tig status\n"
7613 "   or: tig <      [git command output]\n"
7614 "\n"
7615 "Options:\n"
7616 "  -v, --version   Show version and exit\n"
7617 "  -h, --help      Show help message and exit";
7619 static void __NORETURN
7620 quit(int sig)
7622         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7623         if (cursed)
7624                 endwin();
7625         exit(0);
7628 static void __NORETURN
7629 die(const char *err, ...)
7631         va_list args;
7633         endwin();
7635         va_start(args, err);
7636         fputs("tig: ", stderr);
7637         vfprintf(stderr, err, args);
7638         fputs("\n", stderr);
7639         va_end(args);
7641         exit(1);
7644 static void
7645 warn(const char *msg, ...)
7647         va_list args;
7649         va_start(args, msg);
7650         fputs("tig warning: ", stderr);
7651         vfprintf(stderr, msg, args);
7652         fputs("\n", stderr);
7653         va_end(args);
7656 static enum request
7657 parse_options(int argc, const char *argv[])
7659         enum request request = REQ_VIEW_MAIN;
7660         const char *subcommand;
7661         bool seen_dashdash = FALSE;
7662         /* XXX: This is vulnerable to the user overriding options
7663          * required for the main view parser. */
7664         const char *custom_argv[SIZEOF_ARG] = {
7665                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7666                         "--topo-order", NULL
7667         };
7668         int i, j = 6;
7670         if (!isatty(STDIN_FILENO)) {
7671                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7672                 return REQ_VIEW_PAGER;
7673         }
7675         if (argc <= 1)
7676                 return REQ_NONE;
7678         subcommand = argv[1];
7679         if (!strcmp(subcommand, "status")) {
7680                 if (argc > 2)
7681                         warn("ignoring arguments after `%s'", subcommand);
7682                 return REQ_VIEW_STATUS;
7684         } else if (!strcmp(subcommand, "blame")) {
7685                 if (argc <= 2 || argc > 4)
7686                         die("invalid number of options to blame\n\n%s", usage);
7688                 i = 2;
7689                 if (argc == 4) {
7690                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7691                         i++;
7692                 }
7694                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7695                 return REQ_VIEW_BLAME;
7697         } else if (!strcmp(subcommand, "show")) {
7698                 request = REQ_VIEW_DIFF;
7700         } else {
7701                 subcommand = NULL;
7702         }
7704         if (subcommand) {
7705                 custom_argv[1] = subcommand;
7706                 j = 2;
7707         }
7709         for (i = 1 + !!subcommand; i < argc; i++) {
7710                 const char *opt = argv[i];
7712                 if (seen_dashdash || !strcmp(opt, "--")) {
7713                         seen_dashdash = TRUE;
7715                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7716                         printf("tig version %s\n", TIG_VERSION);
7717                         quit(0);
7719                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7720                         printf("%s\n", usage);
7721                         quit(0);
7722                 }
7724                 custom_argv[j++] = opt;
7725                 if (j >= ARRAY_SIZE(custom_argv))
7726                         die("command too long");
7727         }
7729         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7730                 die("Failed to format arguments");
7732         return request;
7735 int
7736 main(int argc, const char *argv[])
7738         enum request request = parse_options(argc, argv);
7739         struct view *view;
7740         size_t i;
7742         signal(SIGINT, quit);
7743         signal(SIGPIPE, SIG_IGN);
7745         if (setlocale(LC_ALL, "")) {
7746                 char *codeset = nl_langinfo(CODESET);
7748                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7749         }
7751         if (load_repo_info() == ERR)
7752                 die("Failed to load repo info.");
7754         if (load_options() == ERR)
7755                 die("Failed to load user config.");
7757         if (load_git_config() == ERR)
7758                 die("Failed to load repo config.");
7760         /* Require a git repository unless when running in pager mode. */
7761         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7762                 die("Not a git repository");
7764         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7765                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7766                 if (opt_iconv_in == ICONV_NONE)
7767                         die("Failed to initialize character set conversion");
7768         }
7770         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7771                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7772                 if (opt_iconv_out == ICONV_NONE)
7773                         die("Failed to initialize character set conversion");
7774         }
7776         if (load_refs() == ERR)
7777                 die("Failed to load refs.");
7779         foreach_view (view, i)
7780                 argv_from_env(view->ops->argv, view->cmd_env);
7782         init_display();
7784         if (request != REQ_NONE)
7785                 open_view(NULL, request, OPEN_PREPARED);
7786         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7788         while (view_driver(display[current_view], request)) {
7789                 int key = get_input(0);
7791                 view = display[current_view];
7792                 request = get_keybinding(view->keymap, key);
7794                 /* Some low-level request handling. This keeps access to
7795                  * status_win restricted. */
7796                 switch (request) {
7797                 case REQ_PROMPT:
7798                 {
7799                         char *cmd = read_prompt(":");
7801                         if (cmd && isdigit(*cmd)) {
7802                                 int lineno = view->lineno + 1;
7804                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7805                                         select_view_line(view, lineno - 1);
7806                                         report("");
7807                                 } else {
7808                                         report("Unable to parse '%s' as a line number", cmd);
7809                                 }
7811                         } else if (cmd) {
7812                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7813                                 const char *argv[SIZEOF_ARG] = { "git" };
7814                                 int argc = 1;
7816                                 /* When running random commands, initially show the
7817                                  * command in the title. However, it maybe later be
7818                                  * overwritten if a commit line is selected. */
7819                                 string_ncopy(next->ref, cmd, strlen(cmd));
7821                                 if (!argv_from_string(argv, &argc, cmd)) {
7822                                         report("Too many arguments");
7823                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7824                                         report("Failed to format command");
7825                                 } else {
7826                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7827                                 }
7828                         }
7830                         request = REQ_NONE;
7831                         break;
7832                 }
7833                 case REQ_SEARCH:
7834                 case REQ_SEARCH_BACK:
7835                 {
7836                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7837                         char *search = read_prompt(prompt);
7839                         if (search)
7840                                 string_ncopy(opt_search, search, strlen(search));
7841                         else if (*opt_search)
7842                                 request = request == REQ_SEARCH ?
7843                                         REQ_FIND_NEXT :
7844                                         REQ_FIND_PREV;
7845                         else
7846                                 request = REQ_NONE;
7847                         break;
7848                 }
7849                 default:
7850                         break;
7851                 }
7852         }
7854         quit(0);
7856         return 0;