Code

Update asciidoc table syntax to the one supported by version 8.4.4
[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
111 #define MIN_VIEW_HEIGHT 4
113 #define NULL_ID         "0000000000000000000000000000000000000000"
115 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 #ifndef GIT_CONFIG
118 #define GIT_CONFIG "config"
119 #endif
121 /* Some ASCII-shorthands fitted into the ncurses namespace. */
122 #define KEY_TAB         '\t'
123 #define KEY_RETURN      '\r'
124 #define KEY_ESC         27
127 struct ref {
128         char id[SIZEOF_REV];    /* Commit SHA1 ID */
129         unsigned int head:1;    /* Is it the current HEAD? */
130         unsigned int tag:1;     /* Is it a tag? */
131         unsigned int ltag:1;    /* If so, is the tag local? */
132         unsigned int remote:1;  /* Is it a remote ref? */
133         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
134         char name[1];           /* Ref name; tag or head names are shortened. */
135 };
137 struct ref_list {
138         char id[SIZEOF_REV];    /* Commit SHA1 ID */
139         size_t size;            /* Number of refs. */
140         struct ref **refs;      /* References for this ID. */
141 };
143 static struct ref_list *get_ref_list(const char *id);
144 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
145 static int load_refs(void);
147 enum format_flags {
148         FORMAT_ALL,             /* Perform replacement in all arguments. */
149         FORMAT_DASH,            /* Perform replacement up until "--". */
150         FORMAT_NONE             /* No replacement should be performed. */
151 };
153 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
155 enum input_status {
156         INPUT_OK,
157         INPUT_SKIP,
158         INPUT_STOP,
159         INPUT_CANCEL
160 };
162 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
164 static char *prompt_input(const char *prompt, input_handler handler, void *data);
165 static bool prompt_yesno(const char *prompt);
167 struct menu_item {
168         int hotkey;
169         const char *text;
170         void *data;
171 };
173 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
175 /*
176  * Allocation helpers ... Entering macro hell to never be seen again.
177  */
179 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
180 static type *                                                                   \
181 name(type **mem, size_t size, size_t increase)                                  \
182 {                                                                               \
183         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
184         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
185         type *tmp = *mem;                                                       \
186                                                                                 \
187         if (mem == NULL || num_chunks != num_chunks_new) {                      \
188                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
189                 if (tmp)                                                        \
190                         *mem = tmp;                                             \
191         }                                                                       \
192                                                                                 \
193         return tmp;                                                             \
196 /*
197  * String helpers
198  */
200 static inline void
201 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
203         if (srclen > dstlen - 1)
204                 srclen = dstlen - 1;
206         strncpy(dst, src, srclen);
207         dst[srclen] = 0;
210 /* Shorthands for safely copying into a fixed buffer. */
212 #define string_copy(dst, src) \
213         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
215 #define string_ncopy(dst, src, srclen) \
216         string_ncopy_do(dst, sizeof(dst), src, srclen)
218 #define string_copy_rev(dst, src) \
219         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
221 #define string_add(dst, from, src) \
222         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
224 static void
225 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
227         size_t size, pos;
229         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
230                 if (src[pos] == '\t') {
231                         size_t expanded = tabsize - (size % tabsize);
233                         if (expanded + size >= dstlen - 1)
234                                 expanded = dstlen - size - 1;
235                         memcpy(dst + size, "        ", expanded);
236                         size += expanded;
237                 } else {
238                         dst[size++] = src[pos];
239                 }
240         }
242         dst[size] = 0;
245 static char *
246 chomp_string(char *name)
248         int namelen;
250         while (isspace(*name))
251                 name++;
253         namelen = strlen(name) - 1;
254         while (namelen > 0 && isspace(name[namelen]))
255                 name[namelen--] = 0;
257         return name;
260 static bool
261 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
263         va_list args;
264         size_t pos = bufpos ? *bufpos : 0;
266         va_start(args, fmt);
267         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
268         va_end(args);
270         if (bufpos)
271                 *bufpos = pos;
273         return pos >= bufsize ? FALSE : TRUE;
276 #define string_format(buf, fmt, args...) \
277         string_nformat(buf, sizeof(buf), NULL, fmt, args)
279 #define string_format_from(buf, from, fmt, args...) \
280         string_nformat(buf, sizeof(buf), from, fmt, args)
282 static int
283 string_enum_compare(const char *str1, const char *str2, int len)
285         size_t i;
287 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
289         /* Diff-Header == DIFF_HEADER */
290         for (i = 0; i < len; i++) {
291                 if (toupper(str1[i]) == toupper(str2[i]))
292                         continue;
294                 if (string_enum_sep(str1[i]) &&
295                     string_enum_sep(str2[i]))
296                         continue;
298                 return str1[i] - str2[i];
299         }
301         return 0;
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 bool
313 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
315         size_t namelen = strlen(name);
316         int i;
318         for (i = 0; i < map_size; i++)
319                 if (namelen == map[i].namelen &&
320                     !string_enum_compare(name, map[i].name, namelen)) {
321                         *value = map[i].value;
322                         return TRUE;
323                 }
325         return FALSE;
328 #define map_enum(attr, map, name) \
329         map_enum_do(map, ARRAY_SIZE(map), attr, name)
331 #define prefixcmp(str1, str2) \
332         strncmp(str1, str2, STRING_SIZE(str2))
334 static inline int
335 suffixcmp(const char *str, int slen, const char *suffix)
337         size_t len = slen >= 0 ? slen : strlen(str);
338         size_t suffixlen = strlen(suffix);
340         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
344 /*
345  * What value of "tz" was in effect back then at "time" in the
346  * local timezone?
347  */
348 static int local_tzoffset(time_t time)
350         time_t t, t_local;
351         struct tm tm;
352         int offset, eastwest; 
354         t = time;
355         localtime_r(&t, &tm);
356         t_local = mktime(&tm);
358         if (t_local < t) {
359                 eastwest = -1;
360                 offset = t - t_local;
361         } else {
362                 eastwest = 1;
363                 offset = t_local - t;
364         }
365         offset /= 60; /* in minutes */
366         offset = (offset % 60) + ((offset / 60) * 100);
367         return offset * eastwest;
370 enum date {
371         DATE_NONE = 0,
372         DATE_DEFAULT,
373         DATE_RELATIVE,
374         DATE_SHORT
375 };
377 static char *
378 string_date(const time_t *time, enum date date)
380         static char buf[DATE_COLS + 1];
381         static const struct enum_map reldate[] = {
382                 { "second", 1,                  60 * 2 },
383                 { "minute", 60,                 60 * 60 * 2 },
384                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
385                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
386                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
387                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
388         };
389         struct tm tm;
391         if (date == DATE_RELATIVE) {
392                 struct timeval now;
393                 time_t date = *time + local_tzoffset(*time);
394                 time_t seconds;
395                 int i;
397                 gettimeofday(&now, NULL);
398                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
399                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
400                         if (seconds >= reldate[i].value)
401                                 continue;
403                         seconds /= reldate[i].namelen;
404                         if (!string_format(buf, "%ld %s%s %s",
405                                            seconds, reldate[i].name,
406                                            seconds > 1 ? "s" : "",
407                                            now.tv_sec >= date ? "ago" : "ahead"))
408                                 break;
409                         return buf;
410                 }
411         }
413         gmtime_r(time, &tm);
414         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
418 static bool
419 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
421         int valuelen;
423         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
424                 bool advance = cmd[valuelen] != 0;
426                 cmd[valuelen] = 0;
427                 argv[(*argc)++] = chomp_string(cmd);
428                 cmd = chomp_string(cmd + valuelen + advance);
429         }
431         if (*argc < SIZEOF_ARG)
432                 argv[*argc] = NULL;
433         return *argc < SIZEOF_ARG;
436 static void
437 argv_from_env(const char **argv, const char *name)
439         char *env = argv ? getenv(name) : NULL;
440         int argc = 0;
442         if (env && *env)
443                 env = strdup(env);
444         if (env && !argv_from_string(argv, &argc, env))
445                 die("Too many arguments in the `%s` environment variable", name);
449 /*
450  * Executing external commands.
451  */
453 enum io_type {
454         IO_FD,                  /* File descriptor based IO. */
455         IO_BG,                  /* Execute command in the background. */
456         IO_FG,                  /* Execute command with same std{in,out,err}. */
457         IO_RD,                  /* Read only fork+exec IO. */
458         IO_WR,                  /* Write only fork+exec IO. */
459         IO_AP,                  /* Append fork+exec output to file. */
460 };
462 struct io {
463         enum io_type type;      /* The requested type of pipe. */
464         const char *dir;        /* Directory from which to execute. */
465         pid_t pid;              /* Pipe for reading or writing. */
466         int pipe;               /* Pipe end for reading or writing. */
467         int error;              /* Error status. */
468         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
469         char *buf;              /* Read buffer. */
470         size_t bufalloc;        /* Allocated buffer size. */
471         size_t bufsize;         /* Buffer content size. */
472         char *bufpos;           /* Current buffer position. */
473         unsigned int eof:1;     /* Has end of file been reached. */
474 };
476 static void
477 reset_io(struct io *io)
479         io->pipe = -1;
480         io->pid = 0;
481         io->buf = io->bufpos = NULL;
482         io->bufalloc = io->bufsize = 0;
483         io->error = 0;
484         io->eof = 0;
487 static void
488 init_io(struct io *io, const char *dir, enum io_type type)
490         reset_io(io);
491         io->type = type;
492         io->dir = dir;
495 static bool
496 init_io_rd(struct io *io, const char *argv[], const char *dir,
497                 enum format_flags flags)
499         init_io(io, dir, IO_RD);
500         return format_argv(io->argv, argv, flags);
503 static bool
504 io_open(struct io *io, const char *name)
506         init_io(io, NULL, IO_FD);
507         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
508         if (io->pipe == -1)
509                 io->error = errno;
510         return io->pipe != -1;
513 static bool
514 kill_io(struct io *io)
516         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
519 static bool
520 done_io(struct io *io)
522         pid_t pid = io->pid;
524         if (io->pipe != -1)
525                 close(io->pipe);
526         free(io->buf);
527         reset_io(io);
529         while (pid > 0) {
530                 int status;
531                 pid_t waiting = waitpid(pid, &status, 0);
533                 if (waiting < 0) {
534                         if (errno == EINTR)
535                                 continue;
536                         report("waitpid failed (%s)", strerror(errno));
537                         return FALSE;
538                 }
540                 return waiting == pid &&
541                        !WIFSIGNALED(status) &&
542                        WIFEXITED(status) &&
543                        !WEXITSTATUS(status);
544         }
546         return TRUE;
549 static bool
550 start_io(struct io *io)
552         int pipefds[2] = { -1, -1 };
554         if (io->type == IO_FD)
555                 return TRUE;
557         if ((io->type == IO_RD || io->type == IO_WR) &&
558             pipe(pipefds) < 0)
559                 return FALSE;
560         else if (io->type == IO_AP)
561                 pipefds[1] = io->pipe;
563         if ((io->pid = fork())) {
564                 if (pipefds[!(io->type == IO_WR)] != -1)
565                         close(pipefds[!(io->type == IO_WR)]);
566                 if (io->pid != -1) {
567                         io->pipe = pipefds[!!(io->type == IO_WR)];
568                         return TRUE;
569                 }
571         } else {
572                 if (io->type != IO_FG) {
573                         int devnull = open("/dev/null", O_RDWR);
574                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
575                         int writefd = (io->type == IO_RD || io->type == IO_AP)
576                                                         ? pipefds[1] : devnull;
578                         dup2(readfd,  STDIN_FILENO);
579                         dup2(writefd, STDOUT_FILENO);
580                         dup2(devnull, STDERR_FILENO);
582                         close(devnull);
583                         if (pipefds[0] != -1)
584                                 close(pipefds[0]);
585                         if (pipefds[1] != -1)
586                                 close(pipefds[1]);
587                 }
589                 if (io->dir && *io->dir && chdir(io->dir) == -1)
590                         die("Failed to change directory: %s", strerror(errno));
592                 execvp(io->argv[0], (char *const*) io->argv);
593                 die("Failed to execute program: %s", strerror(errno));
594         }
596         if (pipefds[!!(io->type == IO_WR)] != -1)
597                 close(pipefds[!!(io->type == IO_WR)]);
598         return FALSE;
601 static bool
602 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
604         init_io(io, dir, type);
605         if (!format_argv(io->argv, argv, FORMAT_NONE))
606                 return FALSE;
607         return start_io(io);
610 static int
611 run_io_do(struct io *io)
613         return start_io(io) && done_io(io);
616 static int
617 run_io_bg(const char **argv)
619         struct io io = {};
621         init_io(&io, NULL, IO_BG);
622         if (!format_argv(io.argv, argv, FORMAT_NONE))
623                 return FALSE;
624         return run_io_do(&io);
627 static bool
628 run_io_fg(const char **argv, const char *dir)
630         struct io io = {};
632         init_io(&io, dir, IO_FG);
633         if (!format_argv(io.argv, argv, FORMAT_NONE))
634                 return FALSE;
635         return run_io_do(&io);
638 static bool
639 run_io_append(const char **argv, enum format_flags flags, int fd)
641         struct io io = {};
643         init_io(&io, NULL, IO_AP);
644         io.pipe = fd;
645         if (format_argv(io.argv, argv, flags))
646                 return run_io_do(&io);
647         close(fd);
648         return FALSE;
651 static bool
652 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
654         return init_io_rd(io, argv, NULL, flags) && start_io(io);
657 static bool
658 run_io_rd_dir(struct io *io, const char **argv, const char *dir, enum format_flags flags)
660         return init_io_rd(io, argv, dir, flags) && start_io(io);
663 static bool
664 io_eof(struct io *io)
666         return io->eof;
669 static int
670 io_error(struct io *io)
672         return io->error;
675 static char *
676 io_strerror(struct io *io)
678         return strerror(io->error);
681 static bool
682 io_can_read(struct io *io)
684         struct timeval tv = { 0, 500 };
685         fd_set fds;
687         FD_ZERO(&fds);
688         FD_SET(io->pipe, &fds);
690         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
693 static ssize_t
694 io_read(struct io *io, void *buf, size_t bufsize)
696         do {
697                 ssize_t readsize = read(io->pipe, buf, bufsize);
699                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
700                         continue;
701                 else if (readsize == -1)
702                         io->error = errno;
703                 else if (readsize == 0)
704                         io->eof = 1;
705                 return readsize;
706         } while (1);
709 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
711 static char *
712 io_get(struct io *io, int c, bool can_read)
714         char *eol;
715         ssize_t readsize;
717         while (TRUE) {
718                 if (io->bufsize > 0) {
719                         eol = memchr(io->bufpos, c, io->bufsize);
720                         if (eol) {
721                                 char *line = io->bufpos;
723                                 *eol = 0;
724                                 io->bufpos = eol + 1;
725                                 io->bufsize -= io->bufpos - line;
726                                 return line;
727                         }
728                 }
730                 if (io_eof(io)) {
731                         if (io->bufsize) {
732                                 io->bufpos[io->bufsize] = 0;
733                                 io->bufsize = 0;
734                                 return io->bufpos;
735                         }
736                         return NULL;
737                 }
739                 if (!can_read)
740                         return NULL;
742                 if (io->bufsize > 0 && io->bufpos > io->buf)
743                         memmove(io->buf, io->bufpos, io->bufsize);
745                 if (io->bufalloc == io->bufsize) {
746                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
747                                 return NULL;
748                         io->bufalloc += BUFSIZ;
749                 }
751                 io->bufpos = io->buf;
752                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
753                 if (io_error(io))
754                         return NULL;
755                 io->bufsize += readsize;
756         }
759 static bool
760 io_write(struct io *io, const void *buf, size_t bufsize)
762         size_t written = 0;
764         while (!io_error(io) && written < bufsize) {
765                 ssize_t size;
767                 size = write(io->pipe, buf + written, bufsize - written);
768                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
769                         continue;
770                 else if (size == -1)
771                         io->error = errno;
772                 else
773                         written += size;
774         }
776         return written == bufsize;
779 static bool
780 io_read_buf(struct io *io, char buf[], size_t bufsize)
782         char *result = io_get(io, '\n', TRUE);
784         if (result) {
785                 result = chomp_string(result);
786                 string_ncopy_do(buf, bufsize, result, strlen(result));
787         }
789         return done_io(io) && result;
792 static bool
793 run_io_buf(const char **argv, char buf[], size_t bufsize)
795         struct io io = {};
797         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
800 static int
801 io_load(struct io *io, const char *separators,
802         int (*read_property)(char *, size_t, char *, size_t))
804         char *name;
805         int state = OK;
807         if (!start_io(io))
808                 return ERR;
810         while (state == OK && (name = io_get(io, '\n', TRUE))) {
811                 char *value;
812                 size_t namelen;
813                 size_t valuelen;
815                 name = chomp_string(name);
816                 namelen = strcspn(name, separators);
818                 if (name[namelen]) {
819                         name[namelen] = 0;
820                         value = chomp_string(name + namelen + 1);
821                         valuelen = strlen(value);
823                 } else {
824                         value = "";
825                         valuelen = 0;
826                 }
828                 state = read_property(name, namelen, value, valuelen);
829         }
831         if (state != ERR && io_error(io))
832                 state = ERR;
833         done_io(io);
835         return state;
838 static int
839 run_io_load(const char **argv, const char *separators,
840             int (*read_property)(char *, size_t, char *, size_t))
842         struct io io = {};
844         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
845                 ? io_load(&io, separators, read_property) : ERR;
849 /*
850  * User requests
851  */
853 #define REQ_INFO \
854         /* XXX: Keep the view request first and in sync with views[]. */ \
855         REQ_GROUP("View switching") \
856         REQ_(VIEW_MAIN,         "Show main view"), \
857         REQ_(VIEW_DIFF,         "Show diff view"), \
858         REQ_(VIEW_LOG,          "Show log view"), \
859         REQ_(VIEW_TREE,         "Show tree view"), \
860         REQ_(VIEW_BLOB,         "Show blob view"), \
861         REQ_(VIEW_BLAME,        "Show blame view"), \
862         REQ_(VIEW_BRANCH,       "Show branch view"), \
863         REQ_(VIEW_HELP,         "Show help page"), \
864         REQ_(VIEW_PAGER,        "Show pager view"), \
865         REQ_(VIEW_STATUS,       "Show status view"), \
866         REQ_(VIEW_STAGE,        "Show stage view"), \
867         \
868         REQ_GROUP("View manipulation") \
869         REQ_(ENTER,             "Enter current line and scroll"), \
870         REQ_(NEXT,              "Move to next"), \
871         REQ_(PREVIOUS,          "Move to previous"), \
872         REQ_(PARENT,            "Move to parent"), \
873         REQ_(VIEW_NEXT,         "Move focus to next view"), \
874         REQ_(REFRESH,           "Reload and refresh"), \
875         REQ_(MAXIMIZE,          "Maximize the current view"), \
876         REQ_(VIEW_CLOSE,        "Close the current view"), \
877         REQ_(QUIT,              "Close all views and quit"), \
878         \
879         REQ_GROUP("View specific requests") \
880         REQ_(STATUS_UPDATE,     "Update file status"), \
881         REQ_(STATUS_REVERT,     "Revert file changes"), \
882         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
883         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
884         \
885         REQ_GROUP("Cursor navigation") \
886         REQ_(MOVE_UP,           "Move cursor one line up"), \
887         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
888         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
889         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
890         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
891         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
892         \
893         REQ_GROUP("Scrolling") \
894         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
895         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
896         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
897         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
898         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
899         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
900         \
901         REQ_GROUP("Searching") \
902         REQ_(SEARCH,            "Search the view"), \
903         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
904         REQ_(FIND_NEXT,         "Find next search match"), \
905         REQ_(FIND_PREV,         "Find previous search match"), \
906         \
907         REQ_GROUP("Option manipulation") \
908         REQ_(OPTIONS,           "Open option menu"), \
909         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
910         REQ_(TOGGLE_DATE,       "Toggle date display"), \
911         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
912         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
913         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
914         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
915         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
916         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
917         \
918         REQ_GROUP("Misc") \
919         REQ_(PROMPT,            "Bring up the prompt"), \
920         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
921         REQ_(SHOW_VERSION,      "Show version information"), \
922         REQ_(STOP_LOADING,      "Stop all loading views"), \
923         REQ_(EDIT,              "Open in editor"), \
924         REQ_(NONE,              "Do nothing")
927 /* User action requests. */
928 enum request {
929 #define REQ_GROUP(help)
930 #define REQ_(req, help) REQ_##req
932         /* Offset all requests to avoid conflicts with ncurses getch values. */
933         REQ_OFFSET = KEY_MAX + 1,
934         REQ_INFO
936 #undef  REQ_GROUP
937 #undef  REQ_
938 };
940 struct request_info {
941         enum request request;
942         const char *name;
943         int namelen;
944         const char *help;
945 };
947 static const struct request_info req_info[] = {
948 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
949 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
950         REQ_INFO
951 #undef  REQ_GROUP
952 #undef  REQ_
953 };
955 static enum request
956 get_request(const char *name)
958         int namelen = strlen(name);
959         int i;
961         for (i = 0; i < ARRAY_SIZE(req_info); i++)
962                 if (req_info[i].namelen == namelen &&
963                     !string_enum_compare(req_info[i].name, name, namelen))
964                         return req_info[i].request;
966         return REQ_NONE;
970 /*
971  * Options
972  */
974 /* Option and state variables. */
975 static enum date opt_date               = DATE_DEFAULT;
976 static bool opt_author                  = TRUE;
977 static bool opt_line_number             = FALSE;
978 static bool opt_line_graphics           = TRUE;
979 static bool opt_rev_graph               = FALSE;
980 static bool opt_show_refs               = TRUE;
981 static int opt_num_interval             = 5;
982 static double opt_hscroll               = 0.50;
983 static double opt_scale_split_view      = 2.0 / 3.0;
984 static int opt_tab_size                 = 8;
985 static int opt_author_cols              = 19;
986 static char opt_path[SIZEOF_STR]        = "";
987 static char opt_file[SIZEOF_STR]        = "";
988 static char opt_ref[SIZEOF_REF]         = "";
989 static char opt_head[SIZEOF_REF]        = "";
990 static char opt_head_rev[SIZEOF_REV]    = "";
991 static char opt_remote[SIZEOF_REF]      = "";
992 static char opt_encoding[20]            = "UTF-8";
993 static bool opt_utf8                    = TRUE;
994 static char opt_codeset[20]             = "UTF-8";
995 static iconv_t opt_iconv                = ICONV_NONE;
996 static char opt_search[SIZEOF_STR]      = "";
997 static char opt_cdup[SIZEOF_STR]        = "";
998 static char opt_prefix[SIZEOF_STR]      = "";
999 static char opt_git_dir[SIZEOF_STR]     = "";
1000 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1001 static char opt_editor[SIZEOF_STR]      = "";
1002 static FILE *opt_tty                    = NULL;
1004 #define is_initial_commit()     (!*opt_head_rev)
1005 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1006 #define mkdate(time)            string_date(time, opt_date)
1009 /*
1010  * Line-oriented content detection.
1011  */
1013 #define LINE_INFO \
1014 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1015 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1016 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1017 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1018 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1019 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1020 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1021 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1022 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1023 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1024 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1025 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1026 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1027 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1028 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1029 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1030 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1031 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1032 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1033 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1034 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1035 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1036 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1037 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1038 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1039 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1040 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1041 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1042 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1043 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1044 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1045 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1046 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1047 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1048 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1049 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1050 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1051 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1052 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1053 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1054 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1055 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1056 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1057 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1058 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1059 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1060 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1061 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1062 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1063 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1064 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1065 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1066 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1067 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1068 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1069 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1070 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1072 enum line_type {
1073 #define LINE(type, line, fg, bg, attr) \
1074         LINE_##type
1075         LINE_INFO,
1076         LINE_NONE
1077 #undef  LINE
1078 };
1080 struct line_info {
1081         const char *name;       /* Option name. */
1082         int namelen;            /* Size of option name. */
1083         const char *line;       /* The start of line to match. */
1084         int linelen;            /* Size of string to match. */
1085         int fg, bg, attr;       /* Color and text attributes for the lines. */
1086 };
1088 static struct line_info line_info[] = {
1089 #define LINE(type, line, fg, bg, attr) \
1090         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1091         LINE_INFO
1092 #undef  LINE
1093 };
1095 static enum line_type
1096 get_line_type(const char *line)
1098         int linelen = strlen(line);
1099         enum line_type type;
1101         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1102                 /* Case insensitive search matches Signed-off-by lines better. */
1103                 if (linelen >= line_info[type].linelen &&
1104                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1105                         return type;
1107         return LINE_DEFAULT;
1110 static inline int
1111 get_line_attr(enum line_type type)
1113         assert(type < ARRAY_SIZE(line_info));
1114         return COLOR_PAIR(type) | line_info[type].attr;
1117 static struct line_info *
1118 get_line_info(const char *name)
1120         size_t namelen = strlen(name);
1121         enum line_type type;
1123         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1124                 if (namelen == line_info[type].namelen &&
1125                     !string_enum_compare(line_info[type].name, name, namelen))
1126                         return &line_info[type];
1128         return NULL;
1131 static void
1132 init_colors(void)
1134         int default_bg = line_info[LINE_DEFAULT].bg;
1135         int default_fg = line_info[LINE_DEFAULT].fg;
1136         enum line_type type;
1138         start_color();
1140         if (assume_default_colors(default_fg, default_bg) == ERR) {
1141                 default_bg = COLOR_BLACK;
1142                 default_fg = COLOR_WHITE;
1143         }
1145         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1146                 struct line_info *info = &line_info[type];
1147                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1148                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1150                 init_pair(type, fg, bg);
1151         }
1154 struct line {
1155         enum line_type type;
1157         /* State flags */
1158         unsigned int selected:1;
1159         unsigned int dirty:1;
1160         unsigned int cleareol:1;
1161         unsigned int other:16;
1163         void *data;             /* User data */
1164 };
1167 /*
1168  * Keys
1169  */
1171 struct keybinding {
1172         int alias;
1173         enum request request;
1174 };
1176 static const struct keybinding default_keybindings[] = {
1177         /* View switching */
1178         { 'm',          REQ_VIEW_MAIN },
1179         { 'd',          REQ_VIEW_DIFF },
1180         { 'l',          REQ_VIEW_LOG },
1181         { 't',          REQ_VIEW_TREE },
1182         { 'f',          REQ_VIEW_BLOB },
1183         { 'B',          REQ_VIEW_BLAME },
1184         { 'H',          REQ_VIEW_BRANCH },
1185         { 'p',          REQ_VIEW_PAGER },
1186         { 'h',          REQ_VIEW_HELP },
1187         { 'S',          REQ_VIEW_STATUS },
1188         { 'c',          REQ_VIEW_STAGE },
1190         /* View manipulation */
1191         { 'q',          REQ_VIEW_CLOSE },
1192         { KEY_TAB,      REQ_VIEW_NEXT },
1193         { KEY_RETURN,   REQ_ENTER },
1194         { KEY_UP,       REQ_PREVIOUS },
1195         { KEY_DOWN,     REQ_NEXT },
1196         { 'R',          REQ_REFRESH },
1197         { KEY_F(5),     REQ_REFRESH },
1198         { 'O',          REQ_MAXIMIZE },
1200         /* Cursor navigation */
1201         { 'k',          REQ_MOVE_UP },
1202         { 'j',          REQ_MOVE_DOWN },
1203         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1204         { KEY_END,      REQ_MOVE_LAST_LINE },
1205         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1206         { ' ',          REQ_MOVE_PAGE_DOWN },
1207         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1208         { 'b',          REQ_MOVE_PAGE_UP },
1209         { '-',          REQ_MOVE_PAGE_UP },
1211         /* Scrolling */
1212         { KEY_LEFT,     REQ_SCROLL_LEFT },
1213         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1214         { KEY_IC,       REQ_SCROLL_LINE_UP },
1215         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1216         { 'w',          REQ_SCROLL_PAGE_UP },
1217         { 's',          REQ_SCROLL_PAGE_DOWN },
1219         /* Searching */
1220         { '/',          REQ_SEARCH },
1221         { '?',          REQ_SEARCH_BACK },
1222         { 'n',          REQ_FIND_NEXT },
1223         { 'N',          REQ_FIND_PREV },
1225         /* Misc */
1226         { 'Q',          REQ_QUIT },
1227         { 'z',          REQ_STOP_LOADING },
1228         { 'v',          REQ_SHOW_VERSION },
1229         { 'r',          REQ_SCREEN_REDRAW },
1230         { 'o',          REQ_OPTIONS },
1231         { '.',          REQ_TOGGLE_LINENO },
1232         { 'D',          REQ_TOGGLE_DATE },
1233         { 'A',          REQ_TOGGLE_AUTHOR },
1234         { 'g',          REQ_TOGGLE_REV_GRAPH },
1235         { 'F',          REQ_TOGGLE_REFS },
1236         { 'I',          REQ_TOGGLE_SORT_ORDER },
1237         { 'i',          REQ_TOGGLE_SORT_FIELD },
1238         { ':',          REQ_PROMPT },
1239         { 'u',          REQ_STATUS_UPDATE },
1240         { '!',          REQ_STATUS_REVERT },
1241         { 'M',          REQ_STATUS_MERGE },
1242         { '@',          REQ_STAGE_NEXT },
1243         { ',',          REQ_PARENT },
1244         { 'e',          REQ_EDIT },
1245 };
1247 #define KEYMAP_INFO \
1248         KEYMAP_(GENERIC), \
1249         KEYMAP_(MAIN), \
1250         KEYMAP_(DIFF), \
1251         KEYMAP_(LOG), \
1252         KEYMAP_(TREE), \
1253         KEYMAP_(BLOB), \
1254         KEYMAP_(BLAME), \
1255         KEYMAP_(BRANCH), \
1256         KEYMAP_(PAGER), \
1257         KEYMAP_(HELP), \
1258         KEYMAP_(STATUS), \
1259         KEYMAP_(STAGE)
1261 enum keymap {
1262 #define KEYMAP_(name) KEYMAP_##name
1263         KEYMAP_INFO
1264 #undef  KEYMAP_
1265 };
1267 static const struct enum_map keymap_table[] = {
1268 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1269         KEYMAP_INFO
1270 #undef  KEYMAP_
1271 };
1273 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1275 struct keybinding_table {
1276         struct keybinding *data;
1277         size_t size;
1278 };
1280 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1282 static void
1283 add_keybinding(enum keymap keymap, enum request request, int key)
1285         struct keybinding_table *table = &keybindings[keymap];
1287         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1288         if (!table->data)
1289                 die("Failed to allocate keybinding");
1290         table->data[table->size].alias = key;
1291         table->data[table->size++].request = request;
1294 /* Looks for a key binding first in the given map, then in the generic map, and
1295  * lastly in the default keybindings. */
1296 static enum request
1297 get_keybinding(enum keymap keymap, int key)
1299         size_t i;
1301         for (i = 0; i < keybindings[keymap].size; i++)
1302                 if (keybindings[keymap].data[i].alias == key)
1303                         return keybindings[keymap].data[i].request;
1305         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1306                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1307                         return keybindings[KEYMAP_GENERIC].data[i].request;
1309         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1310                 if (default_keybindings[i].alias == key)
1311                         return default_keybindings[i].request;
1313         return (enum request) key;
1317 struct key {
1318         const char *name;
1319         int value;
1320 };
1322 static const struct key key_table[] = {
1323         { "Enter",      KEY_RETURN },
1324         { "Space",      ' ' },
1325         { "Backspace",  KEY_BACKSPACE },
1326         { "Tab",        KEY_TAB },
1327         { "Escape",     KEY_ESC },
1328         { "Left",       KEY_LEFT },
1329         { "Right",      KEY_RIGHT },
1330         { "Up",         KEY_UP },
1331         { "Down",       KEY_DOWN },
1332         { "Insert",     KEY_IC },
1333         { "Delete",     KEY_DC },
1334         { "Hash",       '#' },
1335         { "Home",       KEY_HOME },
1336         { "End",        KEY_END },
1337         { "PageUp",     KEY_PPAGE },
1338         { "PageDown",   KEY_NPAGE },
1339         { "F1",         KEY_F(1) },
1340         { "F2",         KEY_F(2) },
1341         { "F3",         KEY_F(3) },
1342         { "F4",         KEY_F(4) },
1343         { "F5",         KEY_F(5) },
1344         { "F6",         KEY_F(6) },
1345         { "F7",         KEY_F(7) },
1346         { "F8",         KEY_F(8) },
1347         { "F9",         KEY_F(9) },
1348         { "F10",        KEY_F(10) },
1349         { "F11",        KEY_F(11) },
1350         { "F12",        KEY_F(12) },
1351 };
1353 static int
1354 get_key_value(const char *name)
1356         int i;
1358         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1359                 if (!strcasecmp(key_table[i].name, name))
1360                         return key_table[i].value;
1362         if (strlen(name) == 1 && isprint(*name))
1363                 return (int) *name;
1365         return ERR;
1368 static const char *
1369 get_key_name(int key_value)
1371         static char key_char[] = "'X'";
1372         const char *seq = NULL;
1373         int key;
1375         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1376                 if (key_table[key].value == key_value)
1377                         seq = key_table[key].name;
1379         if (seq == NULL &&
1380             key_value < 127 &&
1381             isprint(key_value)) {
1382                 key_char[1] = (char) key_value;
1383                 seq = key_char;
1384         }
1386         return seq ? seq : "(no key)";
1389 static bool
1390 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1392         const char *sep = *pos > 0 ? ", " : "";
1393         const char *keyname = get_key_name(keybinding->alias);
1395         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1398 static bool
1399 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1400                            enum keymap keymap, bool all)
1402         int i;
1404         for (i = 0; i < keybindings[keymap].size; i++) {
1405                 if (keybindings[keymap].data[i].request == request) {
1406                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1407                                 return FALSE;
1408                         if (!all)
1409                                 break;
1410                 }
1411         }
1413         return TRUE;
1416 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1418 static const char *
1419 get_keys(enum keymap keymap, enum request request, bool all)
1421         static char buf[BUFSIZ];
1422         size_t pos = 0;
1423         int i;
1425         buf[pos] = 0;
1427         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1428                 return "Too many keybindings!";
1429         if (pos > 0 && !all)
1430                 return buf;
1432         if (keymap != KEYMAP_GENERIC) {
1433                 /* Only the generic keymap includes the default keybindings when
1434                  * listing all keys. */
1435                 if (all)
1436                         return buf;
1438                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1439                         return "Too many keybindings!";
1440                 if (pos)
1441                         return buf;
1442         }
1444         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1445                 if (default_keybindings[i].request == request) {
1446                         if (!append_key(buf, &pos, &default_keybindings[i]))
1447                                 return "Too many keybindings!";
1448                         if (!all)
1449                                 return buf;
1450                 }
1451         }
1453         return buf;
1456 struct run_request {
1457         enum keymap keymap;
1458         int key;
1459         const char *argv[SIZEOF_ARG];
1460 };
1462 static struct run_request *run_request;
1463 static size_t run_requests;
1465 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1467 static enum request
1468 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1470         struct run_request *req;
1472         if (argc >= ARRAY_SIZE(req->argv) - 1)
1473                 return REQ_NONE;
1475         if (!realloc_run_requests(&run_request, run_requests, 1))
1476                 return REQ_NONE;
1478         req = &run_request[run_requests];
1479         req->keymap = keymap;
1480         req->key = key;
1481         req->argv[0] = NULL;
1483         if (!format_argv(req->argv, argv, FORMAT_NONE))
1484                 return REQ_NONE;
1486         return REQ_NONE + ++run_requests;
1489 static struct run_request *
1490 get_run_request(enum request request)
1492         if (request <= REQ_NONE)
1493                 return NULL;
1494         return &run_request[request - REQ_NONE - 1];
1497 static void
1498 add_builtin_run_requests(void)
1500         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1501         const char *commit[] = { "git", "commit", NULL };
1502         const char *gc[] = { "git", "gc", NULL };
1503         struct {
1504                 enum keymap keymap;
1505                 int key;
1506                 int argc;
1507                 const char **argv;
1508         } reqs[] = {
1509                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1510                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1511                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1512         };
1513         int i;
1515         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1516                 enum request req;
1518                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1519                 if (req != REQ_NONE)
1520                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1521         }
1524 /*
1525  * User config file handling.
1526  */
1528 static int   config_lineno;
1529 static bool  config_errors;
1530 static const char *config_msg;
1532 static const struct enum_map color_map[] = {
1533 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1534         COLOR_MAP(DEFAULT),
1535         COLOR_MAP(BLACK),
1536         COLOR_MAP(BLUE),
1537         COLOR_MAP(CYAN),
1538         COLOR_MAP(GREEN),
1539         COLOR_MAP(MAGENTA),
1540         COLOR_MAP(RED),
1541         COLOR_MAP(WHITE),
1542         COLOR_MAP(YELLOW),
1543 };
1545 static const struct enum_map attr_map[] = {
1546 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1547         ATTR_MAP(NORMAL),
1548         ATTR_MAP(BLINK),
1549         ATTR_MAP(BOLD),
1550         ATTR_MAP(DIM),
1551         ATTR_MAP(REVERSE),
1552         ATTR_MAP(STANDOUT),
1553         ATTR_MAP(UNDERLINE),
1554 };
1556 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1558 static int parse_step(double *opt, const char *arg)
1560         *opt = atoi(arg);
1561         if (!strchr(arg, '%'))
1562                 return OK;
1564         /* "Shift down" so 100% and 1 does not conflict. */
1565         *opt = (*opt - 1) / 100;
1566         if (*opt >= 1.0) {
1567                 *opt = 0.99;
1568                 config_msg = "Step value larger than 100%";
1569                 return ERR;
1570         }
1571         if (*opt < 0.0) {
1572                 *opt = 1;
1573                 config_msg = "Invalid step value";
1574                 return ERR;
1575         }
1576         return OK;
1579 static int
1580 parse_int(int *opt, const char *arg, int min, int max)
1582         int value = atoi(arg);
1584         if (min <= value && value <= max) {
1585                 *opt = value;
1586                 return OK;
1587         }
1589         config_msg = "Integer value out of bound";
1590         return ERR;
1593 static bool
1594 set_color(int *color, const char *name)
1596         if (map_enum(color, color_map, name))
1597                 return TRUE;
1598         if (!prefixcmp(name, "color"))
1599                 return parse_int(color, name + 5, 0, 255) == OK;
1600         return FALSE;
1603 /* Wants: object fgcolor bgcolor [attribute] */
1604 static int
1605 option_color_command(int argc, const char *argv[])
1607         struct line_info *info;
1609         if (argc < 3) {
1610                 config_msg = "Wrong number of arguments given to color command";
1611                 return ERR;
1612         }
1614         info = get_line_info(argv[0]);
1615         if (!info) {
1616                 static const struct enum_map obsolete[] = {
1617                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1618                         ENUM_MAP("main-date",   LINE_DATE),
1619                         ENUM_MAP("main-author", LINE_AUTHOR),
1620                 };
1621                 int index;
1623                 if (!map_enum(&index, obsolete, argv[0])) {
1624                         config_msg = "Unknown color name";
1625                         return ERR;
1626                 }
1627                 info = &line_info[index];
1628         }
1630         if (!set_color(&info->fg, argv[1]) ||
1631             !set_color(&info->bg, argv[2])) {
1632                 config_msg = "Unknown color";
1633                 return ERR;
1634         }
1636         info->attr = 0;
1637         while (argc-- > 3) {
1638                 int attr;
1640                 if (!set_attribute(&attr, argv[argc])) {
1641                         config_msg = "Unknown attribute";
1642                         return ERR;
1643                 }
1644                 info->attr |= attr;
1645         }
1647         return OK;
1650 static int parse_bool(bool *opt, const char *arg)
1652         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1653                 ? TRUE : FALSE;
1654         return OK;
1657 static int
1658 parse_string(char *opt, const char *arg, size_t optsize)
1660         int arglen = strlen(arg);
1662         switch (arg[0]) {
1663         case '\"':
1664         case '\'':
1665                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1666                         config_msg = "Unmatched quotation";
1667                         return ERR;
1668                 }
1669                 arg += 1; arglen -= 2;
1670         default:
1671                 string_ncopy_do(opt, optsize, arg, arglen);
1672                 return OK;
1673         }
1676 /* Wants: name = value */
1677 static int
1678 option_set_command(int argc, const char *argv[])
1680         if (argc != 3) {
1681                 config_msg = "Wrong number of arguments given to set command";
1682                 return ERR;
1683         }
1685         if (strcmp(argv[1], "=")) {
1686                 config_msg = "No value assigned";
1687                 return ERR;
1688         }
1690         if (!strcmp(argv[0], "show-author"))
1691                 return parse_bool(&opt_author, argv[2]);
1693         if (!strcmp(argv[0], "show-date")) {
1694                 bool show_date;
1696                 if (!strcmp(argv[2], "relative")) {
1697                         opt_date = DATE_RELATIVE;
1698                         return OK;
1699                 } else if (!strcmp(argv[2], "short")) {
1700                         opt_date = DATE_SHORT;
1701                         return OK;
1702                 } else if (parse_bool(&show_date, argv[2])) {
1703                         opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1704                 }
1705                 return ERR;
1706         }
1708         if (!strcmp(argv[0], "show-rev-graph"))
1709                 return parse_bool(&opt_rev_graph, argv[2]);
1711         if (!strcmp(argv[0], "show-refs"))
1712                 return parse_bool(&opt_show_refs, argv[2]);
1714         if (!strcmp(argv[0], "show-line-numbers"))
1715                 return parse_bool(&opt_line_number, argv[2]);
1717         if (!strcmp(argv[0], "line-graphics"))
1718                 return parse_bool(&opt_line_graphics, argv[2]);
1720         if (!strcmp(argv[0], "line-number-interval"))
1721                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1723         if (!strcmp(argv[0], "author-width"))
1724                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1726         if (!strcmp(argv[0], "horizontal-scroll"))
1727                 return parse_step(&opt_hscroll, argv[2]);
1729         if (!strcmp(argv[0], "split-view-height"))
1730                 return parse_step(&opt_scale_split_view, argv[2]);
1732         if (!strcmp(argv[0], "tab-size"))
1733                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1735         if (!strcmp(argv[0], "commit-encoding"))
1736                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1738         config_msg = "Unknown variable name";
1739         return ERR;
1742 /* Wants: mode request key */
1743 static int
1744 option_bind_command(int argc, const char *argv[])
1746         enum request request;
1747         int keymap = -1;
1748         int key;
1750         if (argc < 3) {
1751                 config_msg = "Wrong number of arguments given to bind command";
1752                 return ERR;
1753         }
1755         if (set_keymap(&keymap, argv[0]) == ERR) {
1756                 config_msg = "Unknown key map";
1757                 return ERR;
1758         }
1760         key = get_key_value(argv[1]);
1761         if (key == ERR) {
1762                 config_msg = "Unknown key";
1763                 return ERR;
1764         }
1766         request = get_request(argv[2]);
1767         if (request == REQ_NONE) {
1768                 static const struct enum_map obsolete[] = {
1769                         ENUM_MAP("cherry-pick",         REQ_NONE),
1770                         ENUM_MAP("screen-resize",       REQ_NONE),
1771                         ENUM_MAP("tree-parent",         REQ_PARENT),
1772                 };
1773                 int alias;
1775                 if (map_enum(&alias, obsolete, argv[2])) {
1776                         if (alias != REQ_NONE)
1777                                 add_keybinding(keymap, alias, key);
1778                         config_msg = "Obsolete request name";
1779                         return ERR;
1780                 }
1781         }
1782         if (request == REQ_NONE && *argv[2]++ == '!')
1783                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1784         if (request == REQ_NONE) {
1785                 config_msg = "Unknown request name";
1786                 return ERR;
1787         }
1789         add_keybinding(keymap, request, key);
1791         return OK;
1794 static int
1795 set_option(const char *opt, char *value)
1797         const char *argv[SIZEOF_ARG];
1798         int argc = 0;
1800         if (!argv_from_string(argv, &argc, value)) {
1801                 config_msg = "Too many option arguments";
1802                 return ERR;
1803         }
1805         if (!strcmp(opt, "color"))
1806                 return option_color_command(argc, argv);
1808         if (!strcmp(opt, "set"))
1809                 return option_set_command(argc, argv);
1811         if (!strcmp(opt, "bind"))
1812                 return option_bind_command(argc, argv);
1814         config_msg = "Unknown option command";
1815         return ERR;
1818 static int
1819 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1821         int status = OK;
1823         config_lineno++;
1824         config_msg = "Internal error";
1826         /* Check for comment markers, since read_properties() will
1827          * only ensure opt and value are split at first " \t". */
1828         optlen = strcspn(opt, "#");
1829         if (optlen == 0)
1830                 return OK;
1832         if (opt[optlen] != 0) {
1833                 config_msg = "No option value";
1834                 status = ERR;
1836         }  else {
1837                 /* Look for comment endings in the value. */
1838                 size_t len = strcspn(value, "#");
1840                 if (len < valuelen) {
1841                         valuelen = len;
1842                         value[valuelen] = 0;
1843                 }
1845                 status = set_option(opt, value);
1846         }
1848         if (status == ERR) {
1849                 warn("Error on line %d, near '%.*s': %s",
1850                      config_lineno, (int) optlen, opt, config_msg);
1851                 config_errors = TRUE;
1852         }
1854         /* Always keep going if errors are encountered. */
1855         return OK;
1858 static void
1859 load_option_file(const char *path)
1861         struct io io = {};
1863         /* It's OK that the file doesn't exist. */
1864         if (!io_open(&io, path))
1865                 return;
1867         config_lineno = 0;
1868         config_errors = FALSE;
1870         if (io_load(&io, " \t", read_option) == ERR ||
1871             config_errors == TRUE)
1872                 warn("Errors while loading %s.", path);
1875 static int
1876 load_options(void)
1878         const char *home = getenv("HOME");
1879         const char *tigrc_user = getenv("TIGRC_USER");
1880         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1881         char buf[SIZEOF_STR];
1883         add_builtin_run_requests();
1885         if (!tigrc_system)
1886                 tigrc_system = SYSCONFDIR "/tigrc";
1887         load_option_file(tigrc_system);
1889         if (!tigrc_user) {
1890                 if (!home || !string_format(buf, "%s/.tigrc", home))
1891                         return ERR;
1892                 tigrc_user = buf;
1893         }
1894         load_option_file(tigrc_user);
1896         return OK;
1900 /*
1901  * The viewer
1902  */
1904 struct view;
1905 struct view_ops;
1907 /* The display array of active views and the index of the current view. */
1908 static struct view *display[2];
1909 static unsigned int current_view;
1911 #define foreach_displayed_view(view, i) \
1912         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1914 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1916 /* Current head and commit ID */
1917 static char ref_blob[SIZEOF_REF]        = "";
1918 static char ref_commit[SIZEOF_REF]      = "HEAD";
1919 static char ref_head[SIZEOF_REF]        = "HEAD";
1921 struct view {
1922         const char *name;       /* View name */
1923         const char *cmd_env;    /* Command line set via environment */
1924         const char *id;         /* Points to either of ref_{head,commit,blob} */
1926         struct view_ops *ops;   /* View operations */
1928         enum keymap keymap;     /* What keymap does this view have */
1929         bool git_dir;           /* Whether the view requires a git directory. */
1931         char ref[SIZEOF_REF];   /* Hovered commit reference */
1932         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1934         int height, width;      /* The width and height of the main window */
1935         WINDOW *win;            /* The main window */
1936         WINDOW *title;          /* The title window living below the main window */
1938         /* Navigation */
1939         unsigned long offset;   /* Offset of the window top */
1940         unsigned long yoffset;  /* Offset from the window side. */
1941         unsigned long lineno;   /* Current line number */
1942         unsigned long p_offset; /* Previous offset of the window top */
1943         unsigned long p_yoffset;/* Previous offset from the window side */
1944         unsigned long p_lineno; /* Previous current line number */
1945         bool p_restore;         /* Should the previous position be restored. */
1947         /* Searching */
1948         char grep[SIZEOF_STR];  /* Search string */
1949         regex_t *regex;         /* Pre-compiled regexp */
1951         /* If non-NULL, points to the view that opened this view. If this view
1952          * is closed tig will switch back to the parent view. */
1953         struct view *parent;
1955         /* Buffering */
1956         size_t lines;           /* Total number of lines */
1957         struct line *line;      /* Line index */
1958         unsigned int digits;    /* Number of digits in the lines member. */
1960         /* Drawing */
1961         struct line *curline;   /* Line currently being drawn. */
1962         enum line_type curtype; /* Attribute currently used for drawing. */
1963         unsigned long col;      /* Column when drawing. */
1964         bool has_scrolled;      /* View was scrolled. */
1966         /* Loading */
1967         struct io io;
1968         struct io *pipe;
1969         time_t start_time;
1970         time_t update_secs;
1971 };
1973 struct view_ops {
1974         /* What type of content being displayed. Used in the title bar. */
1975         const char *type;
1976         /* Default command arguments. */
1977         const char **argv;
1978         /* Open and reads in all view content. */
1979         bool (*open)(struct view *view);
1980         /* Read one line; updates view->line. */
1981         bool (*read)(struct view *view, char *data);
1982         /* Draw one line; @lineno must be < view->height. */
1983         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1984         /* Depending on view handle a special requests. */
1985         enum request (*request)(struct view *view, enum request request, struct line *line);
1986         /* Search for regexp in a line. */
1987         bool (*grep)(struct view *view, struct line *line);
1988         /* Select line */
1989         void (*select)(struct view *view, struct line *line);
1990         /* Prepare view for loading */
1991         bool (*prepare)(struct view *view);
1992 };
1994 static struct view_ops blame_ops;
1995 static struct view_ops blob_ops;
1996 static struct view_ops diff_ops;
1997 static struct view_ops help_ops;
1998 static struct view_ops log_ops;
1999 static struct view_ops main_ops;
2000 static struct view_ops pager_ops;
2001 static struct view_ops stage_ops;
2002 static struct view_ops status_ops;
2003 static struct view_ops tree_ops;
2004 static struct view_ops branch_ops;
2006 #define VIEW_STR(name, env, ref, ops, map, git) \
2007         { name, #env, ref, ops, map, git }
2009 #define VIEW_(id, name, ops, git, ref) \
2010         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2013 static struct view views[] = {
2014         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2015         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2016         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2017         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2018         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2019         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2020         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2021         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2022         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2023         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2024         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2025 };
2027 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2028 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2030 #define foreach_view(view, i) \
2031         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2033 #define view_is_displayed(view) \
2034         (view == display[0] || view == display[1])
2037 enum line_graphic {
2038         LINE_GRAPHIC_VLINE
2039 };
2041 static chtype line_graphics[] = {
2042         /* LINE_GRAPHIC_VLINE: */ '|'
2043 };
2045 static inline void
2046 set_view_attr(struct view *view, enum line_type type)
2048         if (!view->curline->selected && view->curtype != type) {
2049                 wattrset(view->win, get_line_attr(type));
2050                 wchgat(view->win, -1, 0, type, NULL);
2051                 view->curtype = type;
2052         }
2055 static int
2056 draw_chars(struct view *view, enum line_type type, const char *string,
2057            int max_len, bool use_tilde)
2059         int len = 0;
2060         int col = 0;
2061         int trimmed = FALSE;
2062         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2064         if (max_len <= 0)
2065                 return 0;
2067         if (opt_utf8) {
2068                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2069         } else {
2070                 col = len = strlen(string);
2071                 if (len > max_len) {
2072                         if (use_tilde) {
2073                                 max_len -= 1;
2074                         }
2075                         col = len = max_len;
2076                         trimmed = TRUE;
2077                 }
2078         }
2080         set_view_attr(view, type);
2081         if (len > 0)
2082                 waddnstr(view->win, string, len);
2083         if (trimmed && use_tilde) {
2084                 set_view_attr(view, LINE_DELIMITER);
2085                 waddch(view->win, '~');
2086                 col++;
2087         }
2089         return col;
2092 static int
2093 draw_space(struct view *view, enum line_type type, int max, int spaces)
2095         static char space[] = "                    ";
2096         int col = 0;
2098         spaces = MIN(max, spaces);
2100         while (spaces > 0) {
2101                 int len = MIN(spaces, sizeof(space) - 1);
2103                 col += draw_chars(view, type, space, len, FALSE);
2104                 spaces -= len;
2105         }
2107         return col;
2110 static bool
2111 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2113         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2114         return view->width + view->yoffset <= view->col;
2117 static bool
2118 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2120         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2121         int max = view->width + view->yoffset - view->col;
2122         int i;
2124         if (max < size)
2125                 size = max;
2127         set_view_attr(view, type);
2128         /* Using waddch() instead of waddnstr() ensures that
2129          * they'll be rendered correctly for the cursor line. */
2130         for (i = skip; i < size; i++)
2131                 waddch(view->win, graphic[i]);
2133         view->col += size;
2134         if (size < max && skip <= size)
2135                 waddch(view->win, ' ');
2136         view->col++;
2138         return view->width + view->yoffset <= view->col;
2141 static bool
2142 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2144         int max = MIN(view->width + view->yoffset - view->col, len);
2145         int col;
2147         if (text)
2148                 col = draw_chars(view, type, text, max - 1, trim);
2149         else
2150                 col = draw_space(view, type, max - 1, max - 1);
2152         view->col += col;
2153         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2154         return view->width + view->yoffset <= view->col;
2157 static bool
2158 draw_date(struct view *view, time_t *time)
2160         const char *date = time ? mkdate(time) : "";
2161         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2163         return draw_field(view, LINE_DATE, date, cols, FALSE);
2166 static bool
2167 draw_author(struct view *view, const char *author)
2169         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2171         if (!trim) {
2172                 static char initials[10];
2173                 size_t pos;
2175 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2177                 memset(initials, 0, sizeof(initials));
2178                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2179                         while (is_initial_sep(*author))
2180                                 author++;
2181                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2182                         while (*author && !is_initial_sep(author[1]))
2183                                 author++;
2184                 }
2186                 author = initials;
2187         }
2189         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2192 static bool
2193 draw_mode(struct view *view, mode_t mode)
2195         const char *str;
2197         if (S_ISDIR(mode))
2198                 str = "drwxr-xr-x";
2199         else if (S_ISLNK(mode))
2200                 str = "lrwxrwxrwx";
2201         else if (S_ISGITLINK(mode))
2202                 str = "m---------";
2203         else if (S_ISREG(mode) && mode & S_IXUSR)
2204                 str = "-rwxr-xr-x";
2205         else if (S_ISREG(mode))
2206                 str = "-rw-r--r--";
2207         else
2208                 str = "----------";
2210         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2213 static bool
2214 draw_lineno(struct view *view, unsigned int lineno)
2216         char number[10];
2217         int digits3 = view->digits < 3 ? 3 : view->digits;
2218         int max = MIN(view->width + view->yoffset - view->col, digits3);
2219         char *text = NULL;
2221         lineno += view->offset + 1;
2222         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2223                 static char fmt[] = "%1ld";
2225                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2226                 if (string_format(number, fmt, lineno))
2227                         text = number;
2228         }
2229         if (text)
2230                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2231         else
2232                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2233         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2236 static bool
2237 draw_view_line(struct view *view, unsigned int lineno)
2239         struct line *line;
2240         bool selected = (view->offset + lineno == view->lineno);
2242         assert(view_is_displayed(view));
2244         if (view->offset + lineno >= view->lines)
2245                 return FALSE;
2247         line = &view->line[view->offset + lineno];
2249         wmove(view->win, lineno, 0);
2250         if (line->cleareol)
2251                 wclrtoeol(view->win);
2252         view->col = 0;
2253         view->curline = line;
2254         view->curtype = LINE_NONE;
2255         line->selected = FALSE;
2256         line->dirty = line->cleareol = 0;
2258         if (selected) {
2259                 set_view_attr(view, LINE_CURSOR);
2260                 line->selected = TRUE;
2261                 view->ops->select(view, line);
2262         }
2264         return view->ops->draw(view, line, lineno);
2267 static void
2268 redraw_view_dirty(struct view *view)
2270         bool dirty = FALSE;
2271         int lineno;
2273         for (lineno = 0; lineno < view->height; lineno++) {
2274                 if (view->offset + lineno >= view->lines)
2275                         break;
2276                 if (!view->line[view->offset + lineno].dirty)
2277                         continue;
2278                 dirty = TRUE;
2279                 if (!draw_view_line(view, lineno))
2280                         break;
2281         }
2283         if (!dirty)
2284                 return;
2285         wnoutrefresh(view->win);
2288 static void
2289 redraw_view_from(struct view *view, int lineno)
2291         assert(0 <= lineno && lineno < view->height);
2293         for (; lineno < view->height; lineno++) {
2294                 if (!draw_view_line(view, lineno))
2295                         break;
2296         }
2298         wnoutrefresh(view->win);
2301 static void
2302 redraw_view(struct view *view)
2304         werase(view->win);
2305         redraw_view_from(view, 0);
2309 static void
2310 update_view_title(struct view *view)
2312         char buf[SIZEOF_STR];
2313         char state[SIZEOF_STR];
2314         size_t bufpos = 0, statelen = 0;
2316         assert(view_is_displayed(view));
2318         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2319                 unsigned int view_lines = view->offset + view->height;
2320                 unsigned int lines = view->lines
2321                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2322                                    : 0;
2324                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2325                                    view->ops->type,
2326                                    view->lineno + 1,
2327                                    view->lines,
2328                                    lines);
2330         }
2332         if (view->pipe) {
2333                 time_t secs = time(NULL) - view->start_time;
2335                 /* Three git seconds are a long time ... */
2336                 if (secs > 2)
2337                         string_format_from(state, &statelen, " loading %lds", secs);
2338         }
2340         string_format_from(buf, &bufpos, "[%s]", view->name);
2341         if (*view->ref && bufpos < view->width) {
2342                 size_t refsize = strlen(view->ref);
2343                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2345                 if (minsize < view->width)
2346                         refsize = view->width - minsize + 7;
2347                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2348         }
2350         if (statelen && bufpos < view->width) {
2351                 string_format_from(buf, &bufpos, "%s", state);
2352         }
2354         if (view == display[current_view])
2355                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2356         else
2357                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2359         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2360         wclrtoeol(view->title);
2361         wnoutrefresh(view->title);
2364 static int
2365 apply_step(double step, int value)
2367         if (step >= 1)
2368                 return (int) step;
2369         value *= step + 0.01;
2370         return value ? value : 1;
2373 static void
2374 resize_display(void)
2376         int offset, i;
2377         struct view *base = display[0];
2378         struct view *view = display[1] ? display[1] : display[0];
2380         /* Setup window dimensions */
2382         getmaxyx(stdscr, base->height, base->width);
2384         /* Make room for the status window. */
2385         base->height -= 1;
2387         if (view != base) {
2388                 /* Horizontal split. */
2389                 view->width   = base->width;
2390                 view->height  = apply_step(opt_scale_split_view, base->height);
2391                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2392                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2393                 base->height -= view->height;
2395                 /* Make room for the title bar. */
2396                 view->height -= 1;
2397         }
2399         /* Make room for the title bar. */
2400         base->height -= 1;
2402         offset = 0;
2404         foreach_displayed_view (view, i) {
2405                 if (!view->win) {
2406                         view->win = newwin(view->height, 0, offset, 0);
2407                         if (!view->win)
2408                                 die("Failed to create %s view", view->name);
2410                         scrollok(view->win, FALSE);
2412                         view->title = newwin(1, 0, offset + view->height, 0);
2413                         if (!view->title)
2414                                 die("Failed to create title window");
2416                 } else {
2417                         wresize(view->win, view->height, view->width);
2418                         mvwin(view->win,   offset, 0);
2419                         mvwin(view->title, offset + view->height, 0);
2420                 }
2422                 offset += view->height + 1;
2423         }
2426 static void
2427 redraw_display(bool clear)
2429         struct view *view;
2430         int i;
2432         foreach_displayed_view (view, i) {
2433                 if (clear)
2434                         wclear(view->win);
2435                 redraw_view(view);
2436                 update_view_title(view);
2437         }
2440 static void
2441 toggle_date_option(enum date *date)
2443         static const char *help[] = {
2444                 "no",
2445                 "default",
2446                 "relative",
2447                 "short"
2448         };
2450         opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2451         redraw_display(FALSE);
2452         report("Displaying %s dates", help[opt_date]);
2455 static void
2456 toggle_view_option(bool *option, const char *help)
2458         *option = !*option;
2459         redraw_display(FALSE);
2460         report("%sabling %s", *option ? "En" : "Dis", help);
2463 static void
2464 open_option_menu(void)
2466         const struct menu_item menu[] = {
2467                 { '.', "line numbers", &opt_line_number },
2468                 { 'D', "date display", &opt_date },
2469                 { 'A', "author display", &opt_author },
2470                 { 'g', "revision graph display", &opt_rev_graph },
2471                 { 'F', "reference display", &opt_show_refs },
2472                 { 0 }
2473         };
2474         int selected = 0;
2476         if (prompt_menu("Toggle option", menu, &selected)) {
2477                 if (menu[selected].data == &opt_date)
2478                         toggle_date_option(menu[selected].data);
2479                 else
2480                         toggle_view_option(menu[selected].data, menu[selected].text);
2481         }
2484 static void
2485 maximize_view(struct view *view)
2487         memset(display, 0, sizeof(display));
2488         current_view = 0;
2489         display[current_view] = view;
2490         resize_display();
2491         redraw_display(FALSE);
2492         report("");
2496 /*
2497  * Navigation
2498  */
2500 static bool
2501 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2503         if (lineno >= view->lines)
2504                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2506         if (offset > lineno || offset + view->height <= lineno) {
2507                 unsigned long half = view->height / 2;
2509                 if (lineno > half)
2510                         offset = lineno - half;
2511                 else
2512                         offset = 0;
2513         }
2515         if (offset != view->offset || lineno != view->lineno) {
2516                 view->offset = offset;
2517                 view->lineno = lineno;
2518                 return TRUE;
2519         }
2521         return FALSE;
2524 /* Scrolling backend */
2525 static void
2526 do_scroll_view(struct view *view, int lines)
2528         bool redraw_current_line = FALSE;
2530         /* The rendering expects the new offset. */
2531         view->offset += lines;
2533         assert(0 <= view->offset && view->offset < view->lines);
2534         assert(lines);
2536         /* Move current line into the view. */
2537         if (view->lineno < view->offset) {
2538                 view->lineno = view->offset;
2539                 redraw_current_line = TRUE;
2540         } else if (view->lineno >= view->offset + view->height) {
2541                 view->lineno = view->offset + view->height - 1;
2542                 redraw_current_line = TRUE;
2543         }
2545         assert(view->offset <= view->lineno && view->lineno < view->lines);
2547         /* Redraw the whole screen if scrolling is pointless. */
2548         if (view->height < ABS(lines)) {
2549                 redraw_view(view);
2551         } else {
2552                 int line = lines > 0 ? view->height - lines : 0;
2553                 int end = line + ABS(lines);
2555                 scrollok(view->win, TRUE);
2556                 wscrl(view->win, lines);
2557                 scrollok(view->win, FALSE);
2559                 while (line < end && draw_view_line(view, line))
2560                         line++;
2562                 if (redraw_current_line)
2563                         draw_view_line(view, view->lineno - view->offset);
2564                 wnoutrefresh(view->win);
2565         }
2567         view->has_scrolled = TRUE;
2568         report("");
2571 /* Scroll frontend */
2572 static void
2573 scroll_view(struct view *view, enum request request)
2575         int lines = 1;
2577         assert(view_is_displayed(view));
2579         switch (request) {
2580         case REQ_SCROLL_LEFT:
2581                 if (view->yoffset == 0) {
2582                         report("Cannot scroll beyond the first column");
2583                         return;
2584                 }
2585                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2586                         view->yoffset = 0;
2587                 else
2588                         view->yoffset -= apply_step(opt_hscroll, view->width);
2589                 redraw_view_from(view, 0);
2590                 report("");
2591                 return;
2592         case REQ_SCROLL_RIGHT:
2593                 view->yoffset += apply_step(opt_hscroll, view->width);
2594                 redraw_view(view);
2595                 report("");
2596                 return;
2597         case REQ_SCROLL_PAGE_DOWN:
2598                 lines = view->height;
2599         case REQ_SCROLL_LINE_DOWN:
2600                 if (view->offset + lines > view->lines)
2601                         lines = view->lines - view->offset;
2603                 if (lines == 0 || view->offset + view->height >= view->lines) {
2604                         report("Cannot scroll beyond the last line");
2605                         return;
2606                 }
2607                 break;
2609         case REQ_SCROLL_PAGE_UP:
2610                 lines = view->height;
2611         case REQ_SCROLL_LINE_UP:
2612                 if (lines > view->offset)
2613                         lines = view->offset;
2615                 if (lines == 0) {
2616                         report("Cannot scroll beyond the first line");
2617                         return;
2618                 }
2620                 lines = -lines;
2621                 break;
2623         default:
2624                 die("request %d not handled in switch", request);
2625         }
2627         do_scroll_view(view, lines);
2630 /* Cursor moving */
2631 static void
2632 move_view(struct view *view, enum request request)
2634         int scroll_steps = 0;
2635         int steps;
2637         switch (request) {
2638         case REQ_MOVE_FIRST_LINE:
2639                 steps = -view->lineno;
2640                 break;
2642         case REQ_MOVE_LAST_LINE:
2643                 steps = view->lines - view->lineno - 1;
2644                 break;
2646         case REQ_MOVE_PAGE_UP:
2647                 steps = view->height > view->lineno
2648                       ? -view->lineno : -view->height;
2649                 break;
2651         case REQ_MOVE_PAGE_DOWN:
2652                 steps = view->lineno + view->height >= view->lines
2653                       ? view->lines - view->lineno - 1 : view->height;
2654                 break;
2656         case REQ_MOVE_UP:
2657                 steps = -1;
2658                 break;
2660         case REQ_MOVE_DOWN:
2661                 steps = 1;
2662                 break;
2664         default:
2665                 die("request %d not handled in switch", request);
2666         }
2668         if (steps <= 0 && view->lineno == 0) {
2669                 report("Cannot move beyond the first line");
2670                 return;
2672         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2673                 report("Cannot move beyond the last line");
2674                 return;
2675         }
2677         /* Move the current line */
2678         view->lineno += steps;
2679         assert(0 <= view->lineno && view->lineno < view->lines);
2681         /* Check whether the view needs to be scrolled */
2682         if (view->lineno < view->offset ||
2683             view->lineno >= view->offset + view->height) {
2684                 scroll_steps = steps;
2685                 if (steps < 0 && -steps > view->offset) {
2686                         scroll_steps = -view->offset;
2688                 } else if (steps > 0) {
2689                         if (view->lineno == view->lines - 1 &&
2690                             view->lines > view->height) {
2691                                 scroll_steps = view->lines - view->offset - 1;
2692                                 if (scroll_steps >= view->height)
2693                                         scroll_steps -= view->height - 1;
2694                         }
2695                 }
2696         }
2698         if (!view_is_displayed(view)) {
2699                 view->offset += scroll_steps;
2700                 assert(0 <= view->offset && view->offset < view->lines);
2701                 view->ops->select(view, &view->line[view->lineno]);
2702                 return;
2703         }
2705         /* Repaint the old "current" line if we be scrolling */
2706         if (ABS(steps) < view->height)
2707                 draw_view_line(view, view->lineno - steps - view->offset);
2709         if (scroll_steps) {
2710                 do_scroll_view(view, scroll_steps);
2711                 return;
2712         }
2714         /* Draw the current line */
2715         draw_view_line(view, view->lineno - view->offset);
2717         wnoutrefresh(view->win);
2718         report("");
2722 /*
2723  * Searching
2724  */
2726 static void search_view(struct view *view, enum request request);
2728 static bool
2729 grep_text(struct view *view, const char *text[])
2731         regmatch_t pmatch;
2732         size_t i;
2734         for (i = 0; text[i]; i++)
2735                 if (*text[i] &&
2736                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2737                         return TRUE;
2738         return FALSE;
2741 static void
2742 select_view_line(struct view *view, unsigned long lineno)
2744         unsigned long old_lineno = view->lineno;
2745         unsigned long old_offset = view->offset;
2747         if (goto_view_line(view, view->offset, lineno)) {
2748                 if (view_is_displayed(view)) {
2749                         if (old_offset != view->offset) {
2750                                 redraw_view(view);
2751                         } else {
2752                                 draw_view_line(view, old_lineno - view->offset);
2753                                 draw_view_line(view, view->lineno - view->offset);
2754                                 wnoutrefresh(view->win);
2755                         }
2756                 } else {
2757                         view->ops->select(view, &view->line[view->lineno]);
2758                 }
2759         }
2762 static void
2763 find_next(struct view *view, enum request request)
2765         unsigned long lineno = view->lineno;
2766         int direction;
2768         if (!*view->grep) {
2769                 if (!*opt_search)
2770                         report("No previous search");
2771                 else
2772                         search_view(view, request);
2773                 return;
2774         }
2776         switch (request) {
2777         case REQ_SEARCH:
2778         case REQ_FIND_NEXT:
2779                 direction = 1;
2780                 break;
2782         case REQ_SEARCH_BACK:
2783         case REQ_FIND_PREV:
2784                 direction = -1;
2785                 break;
2787         default:
2788                 return;
2789         }
2791         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2792                 lineno += direction;
2794         /* Note, lineno is unsigned long so will wrap around in which case it
2795          * will become bigger than view->lines. */
2796         for (; lineno < view->lines; lineno += direction) {
2797                 if (view->ops->grep(view, &view->line[lineno])) {
2798                         select_view_line(view, lineno);
2799                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2800                         return;
2801                 }
2802         }
2804         report("No match found for '%s'", view->grep);
2807 static void
2808 search_view(struct view *view, enum request request)
2810         int regex_err;
2812         if (view->regex) {
2813                 regfree(view->regex);
2814                 *view->grep = 0;
2815         } else {
2816                 view->regex = calloc(1, sizeof(*view->regex));
2817                 if (!view->regex)
2818                         return;
2819         }
2821         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2822         if (regex_err != 0) {
2823                 char buf[SIZEOF_STR] = "unknown error";
2825                 regerror(regex_err, view->regex, buf, sizeof(buf));
2826                 report("Search failed: %s", buf);
2827                 return;
2828         }
2830         string_copy(view->grep, opt_search);
2832         find_next(view, request);
2835 /*
2836  * Incremental updating
2837  */
2839 static void
2840 reset_view(struct view *view)
2842         int i;
2844         for (i = 0; i < view->lines; i++)
2845                 free(view->line[i].data);
2846         free(view->line);
2848         view->p_offset = view->offset;
2849         view->p_yoffset = view->yoffset;
2850         view->p_lineno = view->lineno;
2852         view->line = NULL;
2853         view->offset = 0;
2854         view->yoffset = 0;
2855         view->lines  = 0;
2856         view->lineno = 0;
2857         view->vid[0] = 0;
2858         view->update_secs = 0;
2861 static void
2862 free_argv(const char *argv[])
2864         int argc;
2866         for (argc = 0; argv[argc]; argc++)
2867                 free((void *) argv[argc]);
2870 static bool
2871 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2873         char buf[SIZEOF_STR];
2874         int argc;
2875         bool noreplace = flags == FORMAT_NONE;
2877         free_argv(dst_argv);
2879         for (argc = 0; src_argv[argc]; argc++) {
2880                 const char *arg = src_argv[argc];
2881                 size_t bufpos = 0;
2883                 while (arg) {
2884                         char *next = strstr(arg, "%(");
2885                         int len = next - arg;
2886                         const char *value;
2888                         if (!next || noreplace) {
2889                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2890                                         noreplace = TRUE;
2891                                 len = strlen(arg);
2892                                 value = "";
2894                         } else if (!prefixcmp(next, "%(directory)")) {
2895                                 value = opt_path;
2897                         } else if (!prefixcmp(next, "%(file)")) {
2898                                 value = opt_file;
2900                         } else if (!prefixcmp(next, "%(ref)")) {
2901                                 value = *opt_ref ? opt_ref : "HEAD";
2903                         } else if (!prefixcmp(next, "%(head)")) {
2904                                 value = ref_head;
2906                         } else if (!prefixcmp(next, "%(commit)")) {
2907                                 value = ref_commit;
2909                         } else if (!prefixcmp(next, "%(blob)")) {
2910                                 value = ref_blob;
2912                         } else {
2913                                 report("Unknown replacement: `%s`", next);
2914                                 return FALSE;
2915                         }
2917                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2918                                 return FALSE;
2920                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2921                 }
2923                 dst_argv[argc] = strdup(buf);
2924                 if (!dst_argv[argc])
2925                         break;
2926         }
2928         dst_argv[argc] = NULL;
2930         return src_argv[argc] == NULL;
2933 static bool
2934 restore_view_position(struct view *view)
2936         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2937                 return FALSE;
2939         /* Changing the view position cancels the restoring. */
2940         /* FIXME: Changing back to the first line is not detected. */
2941         if (view->offset != 0 || view->lineno != 0) {
2942                 view->p_restore = FALSE;
2943                 return FALSE;
2944         }
2946         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2947             view_is_displayed(view))
2948                 werase(view->win);
2950         view->yoffset = view->p_yoffset;
2951         view->p_restore = FALSE;
2953         return TRUE;
2956 static void
2957 end_update(struct view *view, bool force)
2959         if (!view->pipe)
2960                 return;
2961         while (!view->ops->read(view, NULL))
2962                 if (!force)
2963                         return;
2964         set_nonblocking_input(FALSE);
2965         if (force)
2966                 kill_io(view->pipe);
2967         done_io(view->pipe);
2968         view->pipe = NULL;
2971 static void
2972 setup_update(struct view *view, const char *vid)
2974         set_nonblocking_input(TRUE);
2975         reset_view(view);
2976         string_copy_rev(view->vid, vid);
2977         view->pipe = &view->io;
2978         view->start_time = time(NULL);
2981 static bool
2982 prepare_update(struct view *view, const char *argv[], const char *dir,
2983                enum format_flags flags)
2985         if (view->pipe)
2986                 end_update(view, TRUE);
2987         return init_io_rd(&view->io, argv, dir, flags);
2990 static bool
2991 prepare_update_file(struct view *view, const char *name)
2993         if (view->pipe)
2994                 end_update(view, TRUE);
2995         return io_open(&view->io, name);
2998 static bool
2999 begin_update(struct view *view, bool refresh)
3001         if (view->pipe)
3002                 end_update(view, TRUE);
3004         if (refresh) {
3005                 if (!start_io(&view->io))
3006                         return FALSE;
3008         } else {
3009                 if (view->ops->prepare) {
3010                         if (!view->ops->prepare(view))
3011                                 return FALSE;
3012                 } else if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL)) {
3013                         return FALSE;
3014                 }
3016                 /* Put the current ref_* value to the view title ref
3017                  * member. This is needed by the blob view. Most other
3018                  * views sets it automatically after loading because the
3019                  * first line is a commit line. */
3020                 string_copy_rev(view->ref, view->id);
3021         }
3023         setup_update(view, view->id);
3025         return TRUE;
3028 static bool
3029 update_view(struct view *view)
3031         char out_buffer[BUFSIZ * 2];
3032         char *line;
3033         /* Clear the view and redraw everything since the tree sorting
3034          * might have rearranged things. */
3035         bool redraw = view->lines == 0;
3036         bool can_read = TRUE;
3038         if (!view->pipe)
3039                 return TRUE;
3041         if (!io_can_read(view->pipe)) {
3042                 if (view->lines == 0 && view_is_displayed(view)) {
3043                         time_t secs = time(NULL) - view->start_time;
3045                         if (secs > 1 && secs > view->update_secs) {
3046                                 if (view->update_secs == 0)
3047                                         redraw_view(view);
3048                                 update_view_title(view);
3049                                 view->update_secs = secs;
3050                         }
3051                 }
3052                 return TRUE;
3053         }
3055         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3056                 if (opt_iconv != ICONV_NONE) {
3057                         ICONV_CONST char *inbuf = line;
3058                         size_t inlen = strlen(line) + 1;
3060                         char *outbuf = out_buffer;
3061                         size_t outlen = sizeof(out_buffer);
3063                         size_t ret;
3065                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3066                         if (ret != (size_t) -1)
3067                                 line = out_buffer;
3068                 }
3070                 if (!view->ops->read(view, line)) {
3071                         report("Allocation failure");
3072                         end_update(view, TRUE);
3073                         return FALSE;
3074                 }
3075         }
3077         {
3078                 unsigned long lines = view->lines;
3079                 int digits;
3081                 for (digits = 0; lines; digits++)
3082                         lines /= 10;
3084                 /* Keep the displayed view in sync with line number scaling. */
3085                 if (digits != view->digits) {
3086                         view->digits = digits;
3087                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3088                                 redraw = TRUE;
3089                 }
3090         }
3092         if (io_error(view->pipe)) {
3093                 report("Failed to read: %s", io_strerror(view->pipe));
3094                 end_update(view, TRUE);
3096         } else if (io_eof(view->pipe)) {
3097                 report("");
3098                 end_update(view, FALSE);
3099         }
3101         if (restore_view_position(view))
3102                 redraw = TRUE;
3104         if (!view_is_displayed(view))
3105                 return TRUE;
3107         if (redraw)
3108                 redraw_view_from(view, 0);
3109         else
3110                 redraw_view_dirty(view);
3112         /* Update the title _after_ the redraw so that if the redraw picks up a
3113          * commit reference in view->ref it'll be available here. */
3114         update_view_title(view);
3115         return TRUE;
3118 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3120 static struct line *
3121 add_line_data(struct view *view, void *data, enum line_type type)
3123         struct line *line;
3125         if (!realloc_lines(&view->line, view->lines, 1))
3126                 return NULL;
3128         line = &view->line[view->lines++];
3129         memset(line, 0, sizeof(*line));
3130         line->type = type;
3131         line->data = data;
3132         line->dirty = 1;
3134         return line;
3137 static struct line *
3138 add_line_text(struct view *view, const char *text, enum line_type type)
3140         char *data = text ? strdup(text) : NULL;
3142         return data ? add_line_data(view, data, type) : NULL;
3145 static struct line *
3146 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3148         char buf[SIZEOF_STR];
3149         va_list args;
3151         va_start(args, fmt);
3152         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3153                 buf[0] = 0;
3154         va_end(args);
3156         return buf[0] ? add_line_text(view, buf, type) : NULL;
3159 /*
3160  * View opening
3161  */
3163 enum open_flags {
3164         OPEN_DEFAULT = 0,       /* Use default view switching. */
3165         OPEN_SPLIT = 1,         /* Split current view. */
3166         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3167         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3168         OPEN_PREPARED = 32,     /* Open already prepared command. */
3169 };
3171 static void
3172 open_view(struct view *prev, enum request request, enum open_flags flags)
3174         bool split = !!(flags & OPEN_SPLIT);
3175         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3176         bool nomaximize = !!(flags & OPEN_REFRESH);
3177         struct view *view = VIEW(request);
3178         int nviews = displayed_views();
3179         struct view *base_view = display[0];
3181         if (view == prev && nviews == 1 && !reload) {
3182                 report("Already in %s view", view->name);
3183                 return;
3184         }
3186         if (view->git_dir && !opt_git_dir[0]) {
3187                 report("The %s view is disabled in pager view", view->name);
3188                 return;
3189         }
3191         if (split) {
3192                 display[1] = view;
3193                 current_view = 1;
3194         } else if (!nomaximize) {
3195                 /* Maximize the current view. */
3196                 memset(display, 0, sizeof(display));
3197                 current_view = 0;
3198                 display[current_view] = view;
3199         }
3201         /* No parent signals that this is the first loaded view. */
3202         if (prev && view != prev) {
3203                 view->parent = prev;
3204         }
3206         /* Resize the view when switching between split- and full-screen,
3207          * or when switching between two different full-screen views. */
3208         if (nviews != displayed_views() ||
3209             (nviews == 1 && base_view != display[0]))
3210                 resize_display();
3212         if (view->ops->open) {
3213                 if (view->pipe)
3214                         end_update(view, TRUE);
3215                 if (!view->ops->open(view)) {
3216                         report("Failed to load %s view", view->name);
3217                         return;
3218                 }
3219                 restore_view_position(view);
3221         } else if ((reload || strcmp(view->vid, view->id)) &&
3222                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3223                 report("Failed to load %s view", view->name);
3224                 return;
3225         }
3227         if (split && prev->lineno - prev->offset >= prev->height) {
3228                 /* Take the title line into account. */
3229                 int lines = prev->lineno - prev->offset - prev->height + 1;
3231                 /* Scroll the view that was split if the current line is
3232                  * outside the new limited view. */
3233                 do_scroll_view(prev, lines);
3234         }
3236         if (prev && view != prev) {
3237                 if (split) {
3238                         /* "Blur" the previous view. */
3239                         update_view_title(prev);
3240                 }
3241         }
3243         if (view->pipe && view->lines == 0) {
3244                 /* Clear the old view and let the incremental updating refill
3245                  * the screen. */
3246                 werase(view->win);
3247                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3248                 report("");
3249         } else if (view_is_displayed(view)) {
3250                 redraw_view(view);
3251                 report("");
3252         }
3255 static void
3256 open_external_viewer(const char *argv[], const char *dir)
3258         def_prog_mode();           /* save current tty modes */
3259         endwin();                  /* restore original tty modes */
3260         run_io_fg(argv, dir);
3261         fprintf(stderr, "Press Enter to continue");
3262         getc(opt_tty);
3263         reset_prog_mode();
3264         redraw_display(TRUE);
3267 static void
3268 open_mergetool(const char *file)
3270         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3272         open_external_viewer(mergetool_argv, opt_cdup);
3275 static void
3276 open_editor(bool from_root, const char *file)
3278         const char *editor_argv[] = { "vi", file, NULL };
3279         const char *editor;
3281         editor = getenv("GIT_EDITOR");
3282         if (!editor && *opt_editor)
3283                 editor = opt_editor;
3284         if (!editor)
3285                 editor = getenv("VISUAL");
3286         if (!editor)
3287                 editor = getenv("EDITOR");
3288         if (!editor)
3289                 editor = "vi";
3291         editor_argv[0] = editor;
3292         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3295 static void
3296 open_run_request(enum request request)
3298         struct run_request *req = get_run_request(request);
3299         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3301         if (!req) {
3302                 report("Unknown run request");
3303                 return;
3304         }
3306         if (format_argv(argv, req->argv, FORMAT_ALL))
3307                 open_external_viewer(argv, NULL);
3308         free_argv(argv);
3311 /*
3312  * User request switch noodle
3313  */
3315 static int
3316 view_driver(struct view *view, enum request request)
3318         int i;
3320         if (request == REQ_NONE)
3321                 return TRUE;
3323         if (request > REQ_NONE) {
3324                 open_run_request(request);
3325                 /* FIXME: When all views can refresh always do this. */
3326                 if (view == VIEW(REQ_VIEW_STATUS) ||
3327                     view == VIEW(REQ_VIEW_MAIN) ||
3328                     view == VIEW(REQ_VIEW_LOG) ||
3329                     view == VIEW(REQ_VIEW_BRANCH) ||
3330                     view == VIEW(REQ_VIEW_STAGE))
3331                         request = REQ_REFRESH;
3332                 else
3333                         return TRUE;
3334         }
3336         if (view && view->lines) {
3337                 request = view->ops->request(view, request, &view->line[view->lineno]);
3338                 if (request == REQ_NONE)
3339                         return TRUE;
3340         }
3342         switch (request) {
3343         case REQ_MOVE_UP:
3344         case REQ_MOVE_DOWN:
3345         case REQ_MOVE_PAGE_UP:
3346         case REQ_MOVE_PAGE_DOWN:
3347         case REQ_MOVE_FIRST_LINE:
3348         case REQ_MOVE_LAST_LINE:
3349                 move_view(view, request);
3350                 break;
3352         case REQ_SCROLL_LEFT:
3353         case REQ_SCROLL_RIGHT:
3354         case REQ_SCROLL_LINE_DOWN:
3355         case REQ_SCROLL_LINE_UP:
3356         case REQ_SCROLL_PAGE_DOWN:
3357         case REQ_SCROLL_PAGE_UP:
3358                 scroll_view(view, request);
3359                 break;
3361         case REQ_VIEW_BLAME:
3362                 if (!opt_file[0]) {
3363                         report("No file chosen, press %s to open tree view",
3364                                get_key(view->keymap, REQ_VIEW_TREE));
3365                         break;
3366                 }
3367                 open_view(view, request, OPEN_DEFAULT);
3368                 break;
3370         case REQ_VIEW_BLOB:
3371                 if (!ref_blob[0]) {
3372                         report("No file chosen, press %s to open tree view",
3373                                get_key(view->keymap, REQ_VIEW_TREE));
3374                         break;
3375                 }
3376                 open_view(view, request, OPEN_DEFAULT);
3377                 break;
3379         case REQ_VIEW_PAGER:
3380                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3381                         report("No pager content, press %s to run command from prompt",
3382                                get_key(view->keymap, REQ_PROMPT));
3383                         break;
3384                 }
3385                 open_view(view, request, OPEN_DEFAULT);
3386                 break;
3388         case REQ_VIEW_STAGE:
3389                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3390                         report("No stage content, press %s to open the status view and choose file",
3391                                get_key(view->keymap, REQ_VIEW_STATUS));
3392                         break;
3393                 }
3394                 open_view(view, request, OPEN_DEFAULT);
3395                 break;
3397         case REQ_VIEW_STATUS:
3398                 if (opt_is_inside_work_tree == FALSE) {
3399                         report("The status view requires a working tree");
3400                         break;
3401                 }
3402                 open_view(view, request, OPEN_DEFAULT);
3403                 break;
3405         case REQ_VIEW_MAIN:
3406         case REQ_VIEW_DIFF:
3407         case REQ_VIEW_LOG:
3408         case REQ_VIEW_TREE:
3409         case REQ_VIEW_HELP:
3410         case REQ_VIEW_BRANCH:
3411                 open_view(view, request, OPEN_DEFAULT);
3412                 break;
3414         case REQ_NEXT:
3415         case REQ_PREVIOUS:
3416                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3418                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3419                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3420                    (view == VIEW(REQ_VIEW_DIFF) &&
3421                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3422                    (view == VIEW(REQ_VIEW_STAGE) &&
3423                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3424                    (view == VIEW(REQ_VIEW_BLOB) &&
3425                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3426                    (view == VIEW(REQ_VIEW_MAIN) &&
3427                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3428                         int line;
3430                         view = view->parent;
3431                         line = view->lineno;
3432                         move_view(view, request);
3433                         if (view_is_displayed(view))
3434                                 update_view_title(view);
3435                         if (line != view->lineno)
3436                                 view->ops->request(view, REQ_ENTER,
3437                                                    &view->line[view->lineno]);
3439                 } else {
3440                         move_view(view, request);
3441                 }
3442                 break;
3444         case REQ_VIEW_NEXT:
3445         {
3446                 int nviews = displayed_views();
3447                 int next_view = (current_view + 1) % nviews;
3449                 if (next_view == current_view) {
3450                         report("Only one view is displayed");
3451                         break;
3452                 }
3454                 current_view = next_view;
3455                 /* Blur out the title of the previous view. */
3456                 update_view_title(view);
3457                 report("");
3458                 break;
3459         }
3460         case REQ_REFRESH:
3461                 report("Refreshing is not yet supported for the %s view", view->name);
3462                 break;
3464         case REQ_MAXIMIZE:
3465                 if (displayed_views() == 2)
3466                         maximize_view(view);
3467                 break;
3469         case REQ_OPTIONS:
3470                 open_option_menu();
3471                 break;
3473         case REQ_TOGGLE_LINENO:
3474                 toggle_view_option(&opt_line_number, "line numbers");
3475                 break;
3477         case REQ_TOGGLE_DATE:
3478                 toggle_date_option(&opt_date);
3479                 break;
3481         case REQ_TOGGLE_AUTHOR:
3482                 toggle_view_option(&opt_author, "author display");
3483                 break;
3485         case REQ_TOGGLE_REV_GRAPH:
3486                 toggle_view_option(&opt_rev_graph, "revision graph display");
3487                 break;
3489         case REQ_TOGGLE_REFS:
3490                 toggle_view_option(&opt_show_refs, "reference display");
3491                 break;
3493         case REQ_TOGGLE_SORT_FIELD:
3494         case REQ_TOGGLE_SORT_ORDER:
3495                 report("Sorting is not yet supported for the %s view", view->name);
3496                 break;
3498         case REQ_SEARCH:
3499         case REQ_SEARCH_BACK:
3500                 search_view(view, request);
3501                 break;
3503         case REQ_FIND_NEXT:
3504         case REQ_FIND_PREV:
3505                 find_next(view, request);
3506                 break;
3508         case REQ_STOP_LOADING:
3509                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3510                         view = &views[i];
3511                         if (view->pipe)
3512                                 report("Stopped loading the %s view", view->name),
3513                         end_update(view, TRUE);
3514                 }
3515                 break;
3517         case REQ_SHOW_VERSION:
3518                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3519                 return TRUE;
3521         case REQ_SCREEN_REDRAW:
3522                 redraw_display(TRUE);
3523                 break;
3525         case REQ_EDIT:
3526                 report("Nothing to edit");
3527                 break;
3529         case REQ_ENTER:
3530                 report("Nothing to enter");
3531                 break;
3533         case REQ_VIEW_CLOSE:
3534                 /* XXX: Mark closed views by letting view->parent point to the
3535                  * view itself. Parents to closed view should never be
3536                  * followed. */
3537                 if (view->parent &&
3538                     view->parent->parent != view->parent) {
3539                         maximize_view(view->parent);
3540                         view->parent = view;
3541                         break;
3542                 }
3543                 /* Fall-through */
3544         case REQ_QUIT:
3545                 return FALSE;
3547         default:
3548                 report("Unknown key, press %s for help",
3549                        get_key(view->keymap, REQ_VIEW_HELP));
3550                 return TRUE;
3551         }
3553         return TRUE;
3557 /*
3558  * View backend utilities
3559  */
3561 enum sort_field {
3562         ORDERBY_NAME,
3563         ORDERBY_DATE,
3564         ORDERBY_AUTHOR,
3565 };
3567 struct sort_state {
3568         const enum sort_field *fields;
3569         size_t size, current;
3570         bool reverse;
3571 };
3573 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3574 #define get_sort_field(state) ((state).fields[(state).current])
3575 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3577 static void
3578 sort_view(struct view *view, enum request request, struct sort_state *state,
3579           int (*compare)(const void *, const void *))
3581         switch (request) {
3582         case REQ_TOGGLE_SORT_FIELD:
3583                 state->current = (state->current + 1) % state->size;
3584                 break;
3586         case REQ_TOGGLE_SORT_ORDER:
3587                 state->reverse = !state->reverse;
3588                 break;
3589         default:
3590                 die("Not a sort request");
3591         }
3593         qsort(view->line, view->lines, sizeof(*view->line), compare);
3594         redraw_view(view);
3597 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3599 /* Small author cache to reduce memory consumption. It uses binary
3600  * search to lookup or find place to position new entries. No entries
3601  * are ever freed. */
3602 static const char *
3603 get_author(const char *name)
3605         static const char **authors;
3606         static size_t authors_size;
3607         int from = 0, to = authors_size - 1;
3609         while (from <= to) {
3610                 size_t pos = (to + from) / 2;
3611                 int cmp = strcmp(name, authors[pos]);
3613                 if (!cmp)
3614                         return authors[pos];
3616                 if (cmp < 0)
3617                         to = pos - 1;
3618                 else
3619                         from = pos + 1;
3620         }
3622         if (!realloc_authors(&authors, authors_size, 1))
3623                 return NULL;
3624         name = strdup(name);
3625         if (!name)
3626                 return NULL;
3628         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3629         authors[from] = name;
3630         authors_size++;
3632         return name;
3635 static void
3636 parse_timezone(time_t *time, const char *zone)
3638         long tz;
3640         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3641         tz += ('0' - zone[2]) * 60 * 60;
3642         tz += ('0' - zone[3]) * 60;
3643         tz += ('0' - zone[4]);
3645         if (zone[0] == '-')
3646                 tz = -tz;
3648         *time -= tz;
3651 /* Parse author lines where the name may be empty:
3652  *      author  <email@address.tld> 1138474660 +0100
3653  */
3654 static void
3655 parse_author_line(char *ident, const char **author, time_t *time)
3657         char *nameend = strchr(ident, '<');
3658         char *emailend = strchr(ident, '>');
3660         if (nameend && emailend)
3661                 *nameend = *emailend = 0;
3662         ident = chomp_string(ident);
3663         if (!*ident) {
3664                 if (nameend)
3665                         ident = chomp_string(nameend + 1);
3666                 if (!*ident)
3667                         ident = "Unknown";
3668         }
3670         *author = get_author(ident);
3672         /* Parse epoch and timezone */
3673         if (emailend && emailend[1] == ' ') {
3674                 char *secs = emailend + 2;
3675                 char *zone = strchr(secs, ' ');
3677                 *time = (time_t) atol(secs);
3679                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3680                         parse_timezone(time, zone + 1);
3681         }
3684 static bool
3685 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3687         char rev[SIZEOF_REV];
3688         const char *revlist_argv[] = {
3689                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3690         };
3691         struct menu_item *items;
3692         char text[SIZEOF_STR];
3693         bool ok = TRUE;
3694         int i;
3696         items = calloc(*parents + 1, sizeof(*items));
3697         if (!items)
3698                 return FALSE;
3700         for (i = 0; i < *parents; i++) {
3701                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3702                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3703                     !(items[i].text = strdup(text))) {
3704                         ok = FALSE;
3705                         break;
3706                 }
3707         }
3709         if (ok) {
3710                 *parents = 0;
3711                 ok = prompt_menu("Select parent", items, parents);
3712         }
3713         for (i = 0; items[i].text; i++)
3714                 free((char *) items[i].text);
3715         free(items);
3716         return ok;
3719 static bool
3720 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3722         char buf[SIZEOF_STR * 4];
3723         const char *revlist_argv[] = {
3724                 "git", "log", "--no-color", "-1",
3725                         "--pretty=format:%P", id, "--", path, NULL
3726         };
3727         int parents;
3729         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3730             (parents = strlen(buf) / 40) < 0) {
3731                 report("Failed to get parent information");
3732                 return FALSE;
3734         } else if (parents == 0) {
3735                 if (path)
3736                         report("Path '%s' does not exist in the parent", path);
3737                 else
3738                         report("The selected commit has no parents");
3739                 return FALSE;
3740         }
3742         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3743                 return FALSE;
3745         string_copy_rev(rev, &buf[41 * parents]);
3746         return TRUE;
3749 /*
3750  * Pager backend
3751  */
3753 static bool
3754 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3756         char text[SIZEOF_STR];
3758         if (opt_line_number && draw_lineno(view, lineno))
3759                 return TRUE;
3761         string_expand(text, sizeof(text), line->data, opt_tab_size);
3762         draw_text(view, line->type, text, TRUE);
3763         return TRUE;
3766 static bool
3767 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3769         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3770         char ref[SIZEOF_STR];
3772         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3773                 return TRUE;
3775         /* This is the only fatal call, since it can "corrupt" the buffer. */
3776         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3777                 return FALSE;
3779         return TRUE;
3782 static void
3783 add_pager_refs(struct view *view, struct line *line)
3785         char buf[SIZEOF_STR];
3786         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3787         struct ref_list *list;
3788         size_t bufpos = 0, i;
3789         const char *sep = "Refs: ";
3790         bool is_tag = FALSE;
3792         assert(line->type == LINE_COMMIT);
3794         list = get_ref_list(commit_id);
3795         if (!list) {
3796                 if (view == VIEW(REQ_VIEW_DIFF))
3797                         goto try_add_describe_ref;
3798                 return;
3799         }
3801         for (i = 0; i < list->size; i++) {
3802                 struct ref *ref = list->refs[i];
3803                 const char *fmt = ref->tag    ? "%s[%s]" :
3804                                   ref->remote ? "%s<%s>" : "%s%s";
3806                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3807                         return;
3808                 sep = ", ";
3809                 if (ref->tag)
3810                         is_tag = TRUE;
3811         }
3813         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3814 try_add_describe_ref:
3815                 /* Add <tag>-g<commit_id> "fake" reference. */
3816                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3817                         return;
3818         }
3820         if (bufpos == 0)
3821                 return;
3823         add_line_text(view, buf, LINE_PP_REFS);
3826 static bool
3827 pager_read(struct view *view, char *data)
3829         struct line *line;
3831         if (!data)
3832                 return TRUE;
3834         line = add_line_text(view, data, get_line_type(data));
3835         if (!line)
3836                 return FALSE;
3838         if (line->type == LINE_COMMIT &&
3839             (view == VIEW(REQ_VIEW_DIFF) ||
3840              view == VIEW(REQ_VIEW_LOG)))
3841                 add_pager_refs(view, line);
3843         return TRUE;
3846 static enum request
3847 pager_request(struct view *view, enum request request, struct line *line)
3849         int split = 0;
3851         if (request != REQ_ENTER)
3852                 return request;
3854         if (line->type == LINE_COMMIT &&
3855            (view == VIEW(REQ_VIEW_LOG) ||
3856             view == VIEW(REQ_VIEW_PAGER))) {
3857                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3858                 split = 1;
3859         }
3861         /* Always scroll the view even if it was split. That way
3862          * you can use Enter to scroll through the log view and
3863          * split open each commit diff. */
3864         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3866         /* FIXME: A minor workaround. Scrolling the view will call report("")
3867          * but if we are scrolling a non-current view this won't properly
3868          * update the view title. */
3869         if (split)
3870                 update_view_title(view);
3872         return REQ_NONE;
3875 static bool
3876 pager_grep(struct view *view, struct line *line)
3878         const char *text[] = { line->data, NULL };
3880         return grep_text(view, text);
3883 static void
3884 pager_select(struct view *view, struct line *line)
3886         if (line->type == LINE_COMMIT) {
3887                 char *text = (char *)line->data + STRING_SIZE("commit ");
3889                 if (view != VIEW(REQ_VIEW_PAGER))
3890                         string_copy_rev(view->ref, text);
3891                 string_copy_rev(ref_commit, text);
3892         }
3895 static struct view_ops pager_ops = {
3896         "line",
3897         NULL,
3898         NULL,
3899         pager_read,
3900         pager_draw,
3901         pager_request,
3902         pager_grep,
3903         pager_select,
3904 };
3906 static const char *log_argv[SIZEOF_ARG] = {
3907         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3908 };
3910 static enum request
3911 log_request(struct view *view, enum request request, struct line *line)
3913         switch (request) {
3914         case REQ_REFRESH:
3915                 load_refs();
3916                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3917                 return REQ_NONE;
3918         default:
3919                 return pager_request(view, request, line);
3920         }
3923 static struct view_ops log_ops = {
3924         "line",
3925         log_argv,
3926         NULL,
3927         pager_read,
3928         pager_draw,
3929         log_request,
3930         pager_grep,
3931         pager_select,
3932 };
3934 static const char *diff_argv[SIZEOF_ARG] = {
3935         "git", "show", "--pretty=fuller", "--no-color", "--root",
3936                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3937 };
3939 static struct view_ops diff_ops = {
3940         "line",
3941         diff_argv,
3942         NULL,
3943         pager_read,
3944         pager_draw,
3945         pager_request,
3946         pager_grep,
3947         pager_select,
3948 };
3950 /*
3951  * Help backend
3952  */
3954 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3956 static char *
3957 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3959         int bufpos;
3961         for (bufpos = 0; bufpos <= namelen; bufpos++) {
3962                 buf[bufpos] = tolower(name[bufpos]);
3963                 if (buf[bufpos] == '_')
3964                         buf[bufpos] = '-';
3965         }
3967         buf[bufpos] = 0;
3968         return buf;
3971 #define help_keymap_name(buf, keymap) \
3972         help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3974 static bool
3975 help_open_keymap_title(struct view *view, enum keymap keymap)
3977         char buf[SIZEOF_STR];
3978         struct line *line;
3980         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3981                                help_keymap_hidden[keymap] ? '+' : '-',
3982                                help_keymap_name(buf, keymap));
3983         if (line)
3984                 line->other = keymap;
3986         return help_keymap_hidden[keymap];
3989 static void
3990 help_open_keymap(struct view *view, enum keymap keymap)
3992         const char *group = NULL;
3993         char buf[SIZEOF_STR];
3994         size_t bufpos;
3995         bool add_title = TRUE;
3996         int i;
3998         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3999                 const char *key = NULL;
4001                 if (req_info[i].request == REQ_NONE)
4002                         continue;
4004                 if (!req_info[i].request) {
4005                         group = req_info[i].help;
4006                         continue;
4007                 }
4009                 key = get_keys(keymap, req_info[i].request, TRUE);
4010                 if (!key || !*key)
4011                         continue;
4013                 if (add_title && help_open_keymap_title(view, keymap))
4014                         return;
4015                 add_title = false;
4017                 if (group) {
4018                         add_line_text(view, group, LINE_HELP_GROUP);
4019                         group = NULL;
4020                 }
4022                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4023                                 help_name(buf, req_info[i].name, req_info[i].namelen),
4024                                 req_info[i].help);
4025         }
4027         group = "External commands:";
4029         for (i = 0; i < run_requests; i++) {
4030                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4031                 const char *key;
4032                 int argc;
4034                 if (!req || req->keymap != keymap)
4035                         continue;
4037                 key = get_key_name(req->key);
4038                 if (!*key)
4039                         key = "(no key defined)";
4041                 if (add_title && help_open_keymap_title(view, keymap))
4042                         return;
4043                 if (group) {
4044                         add_line_text(view, group, LINE_HELP_GROUP);
4045                         group = NULL;
4046                 }
4048                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4049                         if (!string_format_from(buf, &bufpos, "%s%s",
4050                                                 argc ? " " : "", req->argv[argc]))
4051                                 return;
4053                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4054         }
4057 static bool
4058 help_open(struct view *view)
4060         enum keymap keymap;
4062         reset_view(view);
4063         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4064         add_line_text(view, "", LINE_DEFAULT);
4066         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4067                 help_open_keymap(view, keymap);
4069         return TRUE;
4072 static enum request
4073 help_request(struct view *view, enum request request, struct line *line)
4075         switch (request) {
4076         case REQ_ENTER:
4077                 if (line->type == LINE_HELP_KEYMAP) {
4078                         help_keymap_hidden[line->other] =
4079                                 !help_keymap_hidden[line->other];
4080                         view->p_restore = TRUE;
4081                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4082                 }
4084                 return REQ_NONE;
4085         default:
4086                 return pager_request(view, request, line);
4087         }
4090 static struct view_ops help_ops = {
4091         "line",
4092         NULL,
4093         help_open,
4094         NULL,
4095         pager_draw,
4096         help_request,
4097         pager_grep,
4098         pager_select,
4099 };
4102 /*
4103  * Tree backend
4104  */
4106 struct tree_stack_entry {
4107         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4108         unsigned long lineno;           /* Line number to restore */
4109         char *name;                     /* Position of name in opt_path */
4110 };
4112 /* The top of the path stack. */
4113 static struct tree_stack_entry *tree_stack = NULL;
4114 unsigned long tree_lineno = 0;
4116 static void
4117 pop_tree_stack_entry(void)
4119         struct tree_stack_entry *entry = tree_stack;
4121         tree_lineno = entry->lineno;
4122         entry->name[0] = 0;
4123         tree_stack = entry->prev;
4124         free(entry);
4127 static void
4128 push_tree_stack_entry(const char *name, unsigned long lineno)
4130         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4131         size_t pathlen = strlen(opt_path);
4133         if (!entry)
4134                 return;
4136         entry->prev = tree_stack;
4137         entry->name = opt_path + pathlen;
4138         tree_stack = entry;
4140         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4141                 pop_tree_stack_entry();
4142                 return;
4143         }
4145         /* Move the current line to the first tree entry. */
4146         tree_lineno = 1;
4147         entry->lineno = lineno;
4150 /* Parse output from git-ls-tree(1):
4151  *
4152  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4153  */
4155 #define SIZEOF_TREE_ATTR \
4156         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4158 #define SIZEOF_TREE_MODE \
4159         STRING_SIZE("100644 ")
4161 #define TREE_ID_OFFSET \
4162         STRING_SIZE("100644 blob ")
4164 struct tree_entry {
4165         char id[SIZEOF_REV];
4166         mode_t mode;
4167         time_t time;                    /* Date from the author ident. */
4168         const char *author;             /* Author of the commit. */
4169         char name[1];
4170 };
4172 static const char *
4173 tree_path(const struct line *line)
4175         return ((struct tree_entry *) line->data)->name;
4178 static int
4179 tree_compare_entry(const struct line *line1, const struct line *line2)
4181         if (line1->type != line2->type)
4182                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4183         return strcmp(tree_path(line1), tree_path(line2));
4186 static const enum sort_field tree_sort_fields[] = {
4187         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4188 };
4189 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4191 static int
4192 tree_compare(const void *l1, const void *l2)
4194         const struct line *line1 = (const struct line *) l1;
4195         const struct line *line2 = (const struct line *) l2;
4196         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4197         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4199         if (line1->type == LINE_TREE_HEAD)
4200                 return -1;
4201         if (line2->type == LINE_TREE_HEAD)
4202                 return 1;
4204         switch (get_sort_field(tree_sort_state)) {
4205         case ORDERBY_DATE:
4206                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4208         case ORDERBY_AUTHOR:
4209                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4211         case ORDERBY_NAME:
4212         default:
4213                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4214         }
4218 static struct line *
4219 tree_entry(struct view *view, enum line_type type, const char *path,
4220            const char *mode, const char *id)
4222         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4223         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4225         if (!entry || !line) {
4226                 free(entry);
4227                 return NULL;
4228         }
4230         strncpy(entry->name, path, strlen(path));
4231         if (mode)
4232                 entry->mode = strtoul(mode, NULL, 8);
4233         if (id)
4234                 string_copy_rev(entry->id, id);
4236         return line;
4239 static bool
4240 tree_read_date(struct view *view, char *text, bool *read_date)
4242         static const char *author_name;
4243         static time_t author_time;
4245         if (!text && *read_date) {
4246                 *read_date = FALSE;
4247                 return TRUE;
4249         } else if (!text) {
4250                 char *path = *opt_path ? opt_path : ".";
4251                 /* Find next entry to process */
4252                 const char *log_file[] = {
4253                         "git", "log", "--no-color", "--pretty=raw",
4254                                 "--cc", "--raw", view->id, "--", path, NULL
4255                 };
4256                 struct io io = {};
4258                 if (!view->lines) {
4259                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4260                         report("Tree is empty");
4261                         return TRUE;
4262                 }
4264                 if (!run_io_rd_dir(&io, log_file, opt_cdup, FORMAT_NONE)) {
4265                         report("Failed to load tree data");
4266                         return TRUE;
4267                 }
4269                 done_io(view->pipe);
4270                 view->io = io;
4271                 *read_date = TRUE;
4272                 return FALSE;
4274         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4275                 parse_author_line(text + STRING_SIZE("author "),
4276                                   &author_name, &author_time);
4278         } else if (*text == ':') {
4279                 char *pos;
4280                 size_t annotated = 1;
4281                 size_t i;
4283                 pos = strchr(text, '\t');
4284                 if (!pos)
4285                         return TRUE;
4286                 text = pos + 1;
4287                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4288                         text += strlen(opt_path);
4289                 pos = strchr(text, '/');
4290                 if (pos)
4291                         *pos = 0;
4293                 for (i = 1; i < view->lines; i++) {
4294                         struct line *line = &view->line[i];
4295                         struct tree_entry *entry = line->data;
4297                         annotated += !!entry->author;
4298                         if (entry->author || strcmp(entry->name, text))
4299                                 continue;
4301                         entry->author = author_name;
4302                         entry->time = author_time;
4303                         line->dirty = 1;
4304                         break;
4305                 }
4307                 if (annotated == view->lines)
4308                         kill_io(view->pipe);
4309         }
4310         return TRUE;
4313 static bool
4314 tree_read(struct view *view, char *text)
4316         static bool read_date = FALSE;
4317         struct tree_entry *data;
4318         struct line *entry, *line;
4319         enum line_type type;
4320         size_t textlen = text ? strlen(text) : 0;
4321         char *path = text + SIZEOF_TREE_ATTR;
4323         if (read_date || !text)
4324                 return tree_read_date(view, text, &read_date);
4326         if (textlen <= SIZEOF_TREE_ATTR)
4327                 return FALSE;
4328         if (view->lines == 0 &&
4329             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4330                 return FALSE;
4332         /* Strip the path part ... */
4333         if (*opt_path) {
4334                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4335                 size_t striplen = strlen(opt_path);
4337                 if (pathlen > striplen)
4338                         memmove(path, path + striplen,
4339                                 pathlen - striplen + 1);
4341                 /* Insert "link" to parent directory. */
4342                 if (view->lines == 1 &&
4343                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4344                         return FALSE;
4345         }
4347         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4348         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4349         if (!entry)
4350                 return FALSE;
4351         data = entry->data;
4353         /* Skip "Directory ..." and ".." line. */
4354         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4355                 if (tree_compare_entry(line, entry) <= 0)
4356                         continue;
4358                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4360                 line->data = data;
4361                 line->type = type;
4362                 for (; line <= entry; line++)
4363                         line->dirty = line->cleareol = 1;
4364                 return TRUE;
4365         }
4367         if (tree_lineno > view->lineno) {
4368                 view->lineno = tree_lineno;
4369                 tree_lineno = 0;
4370         }
4372         return TRUE;
4375 static bool
4376 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4378         struct tree_entry *entry = line->data;
4380         if (line->type == LINE_TREE_HEAD) {
4381                 if (draw_text(view, line->type, "Directory path /", TRUE))
4382                         return TRUE;
4383         } else {
4384                 if (draw_mode(view, entry->mode))
4385                         return TRUE;
4387                 if (opt_author && draw_author(view, entry->author))
4388                         return TRUE;
4390                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4391                         return TRUE;
4392         }
4393         if (draw_text(view, line->type, entry->name, TRUE))
4394                 return TRUE;
4395         return TRUE;
4398 static void
4399 open_blob_editor()
4401         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4402         int fd = mkstemp(file);
4404         if (fd == -1)
4405                 report("Failed to create temporary file");
4406         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4407                 report("Failed to save blob data to file");
4408         else
4409                 open_editor(FALSE, file);
4410         if (fd != -1)
4411                 unlink(file);
4414 static enum request
4415 tree_request(struct view *view, enum request request, struct line *line)
4417         enum open_flags flags;
4419         switch (request) {
4420         case REQ_VIEW_BLAME:
4421                 if (line->type != LINE_TREE_FILE) {
4422                         report("Blame only supported for files");
4423                         return REQ_NONE;
4424                 }
4426                 string_copy(opt_ref, view->vid);
4427                 return request;
4429         case REQ_EDIT:
4430                 if (line->type != LINE_TREE_FILE) {
4431                         report("Edit only supported for files");
4432                 } else if (!is_head_commit(view->vid)) {
4433                         open_blob_editor();
4434                 } else {
4435                         open_editor(TRUE, opt_file);
4436                 }
4437                 return REQ_NONE;
4439         case REQ_TOGGLE_SORT_FIELD:
4440         case REQ_TOGGLE_SORT_ORDER:
4441                 sort_view(view, request, &tree_sort_state, tree_compare);
4442                 return REQ_NONE;
4444         case REQ_PARENT:
4445                 if (!*opt_path) {
4446                         /* quit view if at top of tree */
4447                         return REQ_VIEW_CLOSE;
4448                 }
4449                 /* fake 'cd  ..' */
4450                 line = &view->line[1];
4451                 break;
4453         case REQ_ENTER:
4454                 break;
4456         default:
4457                 return request;
4458         }
4460         /* Cleanup the stack if the tree view is at a different tree. */
4461         while (!*opt_path && tree_stack)
4462                 pop_tree_stack_entry();
4464         switch (line->type) {
4465         case LINE_TREE_DIR:
4466                 /* Depending on whether it is a subdirectory or parent link
4467                  * mangle the path buffer. */
4468                 if (line == &view->line[1] && *opt_path) {
4469                         pop_tree_stack_entry();
4471                 } else {
4472                         const char *basename = tree_path(line);
4474                         push_tree_stack_entry(basename, view->lineno);
4475                 }
4477                 /* Trees and subtrees share the same ID, so they are not not
4478                  * unique like blobs. */
4479                 flags = OPEN_RELOAD;
4480                 request = REQ_VIEW_TREE;
4481                 break;
4483         case LINE_TREE_FILE:
4484                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4485                 request = REQ_VIEW_BLOB;
4486                 break;
4488         default:
4489                 return REQ_NONE;
4490         }
4492         open_view(view, request, flags);
4493         if (request == REQ_VIEW_TREE)
4494                 view->lineno = tree_lineno;
4496         return REQ_NONE;
4499 static bool
4500 tree_grep(struct view *view, struct line *line)
4502         struct tree_entry *entry = line->data;
4503         const char *text[] = {
4504                 entry->name,
4505                 opt_author ? entry->author : "",
4506                 opt_date ? mkdate(&entry->time) : "",
4507                 NULL
4508         };
4510         return grep_text(view, text);
4513 static void
4514 tree_select(struct view *view, struct line *line)
4516         struct tree_entry *entry = line->data;
4518         if (line->type == LINE_TREE_FILE) {
4519                 string_copy_rev(ref_blob, entry->id);
4520                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4522         } else if (line->type != LINE_TREE_DIR) {
4523                 return;
4524         }
4526         string_copy_rev(view->ref, entry->id);
4529 static bool
4530 tree_prepare(struct view *view)
4532         if (view->lines == 0 && opt_prefix[0]) {
4533                 char *pos = opt_prefix;
4535                 while (pos && *pos) {
4536                         char *end = strchr(pos, '/');
4538                         if (end)
4539                                 *end = 0;
4540                         push_tree_stack_entry(pos, 0);
4541                         pos = end;
4542                         if (end) {
4543                                 *end = '/';
4544                                 pos++;
4545                         }
4546                 }
4548         } else if (strcmp(view->vid, view->id)) {
4549                 opt_path[0] = 0;
4550         }
4552         return run_io_rd_dir(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4555 static const char *tree_argv[SIZEOF_ARG] = {
4556         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4557 };
4559 static struct view_ops tree_ops = {
4560         "file",
4561         tree_argv,
4562         NULL,
4563         tree_read,
4564         tree_draw,
4565         tree_request,
4566         tree_grep,
4567         tree_select,
4568         tree_prepare,
4569 };
4571 static bool
4572 blob_read(struct view *view, char *line)
4574         if (!line)
4575                 return TRUE;
4576         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4579 static enum request
4580 blob_request(struct view *view, enum request request, struct line *line)
4582         switch (request) {
4583         case REQ_EDIT:
4584                 open_blob_editor();
4585                 return REQ_NONE;
4586         default:
4587                 return pager_request(view, request, line);
4588         }
4591 static const char *blob_argv[SIZEOF_ARG] = {
4592         "git", "cat-file", "blob", "%(blob)", NULL
4593 };
4595 static struct view_ops blob_ops = {
4596         "line",
4597         blob_argv,
4598         NULL,
4599         blob_read,
4600         pager_draw,
4601         blob_request,
4602         pager_grep,
4603         pager_select,
4604 };
4606 /*
4607  * Blame backend
4608  *
4609  * Loading the blame view is a two phase job:
4610  *
4611  *  1. File content is read either using opt_file from the
4612  *     filesystem or using git-cat-file.
4613  *  2. Then blame information is incrementally added by
4614  *     reading output from git-blame.
4615  */
4617 static const char *blame_head_argv[] = {
4618         "git", "blame", "--incremental", "--", "%(file)", NULL
4619 };
4621 static const char *blame_ref_argv[] = {
4622         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4623 };
4625 static const char *blame_cat_file_argv[] = {
4626         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4627 };
4629 struct blame_commit {
4630         char id[SIZEOF_REV];            /* SHA1 ID. */
4631         char title[128];                /* First line of the commit message. */
4632         const char *author;             /* Author of the commit. */
4633         time_t time;                    /* Date from the author ident. */
4634         char filename[128];             /* Name of file. */
4635         bool has_previous;              /* Was a "previous" line detected. */
4636 };
4638 struct blame {
4639         struct blame_commit *commit;
4640         unsigned long lineno;
4641         char text[1];
4642 };
4644 static bool
4645 blame_open(struct view *view)
4647         char path[SIZEOF_STR];
4649         if (!view->parent && *opt_prefix) {
4650                 string_copy(path, opt_file);
4651                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4652                         return FALSE;
4653         }
4655         if (!string_format(path, "%s%s", opt_cdup, opt_file))
4656                 return FALSE;
4658         if (*opt_ref || !io_open(&view->io, path)) {
4659                 if (!run_io_rd_dir(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4660                         return FALSE;
4661         }
4663         setup_update(view, opt_file);
4664         string_format(view->ref, "%s ...", opt_file);
4666         return TRUE;
4669 static struct blame_commit *
4670 get_blame_commit(struct view *view, const char *id)
4672         size_t i;
4674         for (i = 0; i < view->lines; i++) {
4675                 struct blame *blame = view->line[i].data;
4677                 if (!blame->commit)
4678                         continue;
4680                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4681                         return blame->commit;
4682         }
4684         {
4685                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4687                 if (commit)
4688                         string_ncopy(commit->id, id, SIZEOF_REV);
4689                 return commit;
4690         }
4693 static bool
4694 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4696         const char *pos = *posref;
4698         *posref = NULL;
4699         pos = strchr(pos + 1, ' ');
4700         if (!pos || !isdigit(pos[1]))
4701                 return FALSE;
4702         *number = atoi(pos + 1);
4703         if (*number < min || *number > max)
4704                 return FALSE;
4706         *posref = pos;
4707         return TRUE;
4710 static struct blame_commit *
4711 parse_blame_commit(struct view *view, const char *text, int *blamed)
4713         struct blame_commit *commit;
4714         struct blame *blame;
4715         const char *pos = text + SIZEOF_REV - 2;
4716         size_t orig_lineno = 0;
4717         size_t lineno;
4718         size_t group;
4720         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4721                 return NULL;
4723         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4724             !parse_number(&pos, &lineno, 1, view->lines) ||
4725             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4726                 return NULL;
4728         commit = get_blame_commit(view, text);
4729         if (!commit)
4730                 return NULL;
4732         *blamed += group;
4733         while (group--) {
4734                 struct line *line = &view->line[lineno + group - 1];
4736                 blame = line->data;
4737                 blame->commit = commit;
4738                 blame->lineno = orig_lineno + group - 1;
4739                 line->dirty = 1;
4740         }
4742         return commit;
4745 static bool
4746 blame_read_file(struct view *view, const char *line, bool *read_file)
4748         if (!line) {
4749                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4750                 struct io io = {};
4752                 if (view->lines == 0 && !view->parent)
4753                         die("No blame exist for %s", view->vid);
4755                 if (view->lines == 0 || !run_io_rd_dir(&io, argv, opt_cdup, FORMAT_ALL)) {
4756                         report("Failed to load blame data");
4757                         return TRUE;
4758                 }
4760                 done_io(view->pipe);
4761                 view->io = io;
4762                 *read_file = FALSE;
4763                 return FALSE;
4765         } else {
4766                 size_t linelen = strlen(line);
4767                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4769                 if (!blame)
4770                         return FALSE;
4772                 blame->commit = NULL;
4773                 strncpy(blame->text, line, linelen);
4774                 blame->text[linelen] = 0;
4775                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4776         }
4779 static bool
4780 match_blame_header(const char *name, char **line)
4782         size_t namelen = strlen(name);
4783         bool matched = !strncmp(name, *line, namelen);
4785         if (matched)
4786                 *line += namelen;
4788         return matched;
4791 static bool
4792 blame_read(struct view *view, char *line)
4794         static struct blame_commit *commit = NULL;
4795         static int blamed = 0;
4796         static bool read_file = TRUE;
4798         if (read_file)
4799                 return blame_read_file(view, line, &read_file);
4801         if (!line) {
4802                 /* Reset all! */
4803                 commit = NULL;
4804                 blamed = 0;
4805                 read_file = TRUE;
4806                 string_format(view->ref, "%s", view->vid);
4807                 if (view_is_displayed(view)) {
4808                         update_view_title(view);
4809                         redraw_view_from(view, 0);
4810                 }
4811                 return TRUE;
4812         }
4814         if (!commit) {
4815                 commit = parse_blame_commit(view, line, &blamed);
4816                 string_format(view->ref, "%s %2d%%", view->vid,
4817                               view->lines ? blamed * 100 / view->lines : 0);
4819         } else if (match_blame_header("author ", &line)) {
4820                 commit->author = get_author(line);
4822         } else if (match_blame_header("author-time ", &line)) {
4823                 commit->time = (time_t) atol(line);
4825         } else if (match_blame_header("author-tz ", &line)) {
4826                 parse_timezone(&commit->time, line);
4828         } else if (match_blame_header("summary ", &line)) {
4829                 string_ncopy(commit->title, line, strlen(line));
4831         } else if (match_blame_header("previous ", &line)) {
4832                 commit->has_previous = TRUE;
4834         } else if (match_blame_header("filename ", &line)) {
4835                 string_ncopy(commit->filename, line, strlen(line));
4836                 commit = NULL;
4837         }
4839         return TRUE;
4842 static bool
4843 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4845         struct blame *blame = line->data;
4846         time_t *time = NULL;
4847         const char *id = NULL, *author = NULL;
4848         char text[SIZEOF_STR];
4850         if (blame->commit && *blame->commit->filename) {
4851                 id = blame->commit->id;
4852                 author = blame->commit->author;
4853                 time = &blame->commit->time;
4854         }
4856         if (opt_date && draw_date(view, time))
4857                 return TRUE;
4859         if (opt_author && draw_author(view, author))
4860                 return TRUE;
4862         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4863                 return TRUE;
4865         if (draw_lineno(view, lineno))
4866                 return TRUE;
4868         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4869         draw_text(view, LINE_DEFAULT, text, TRUE);
4870         return TRUE;
4873 static bool
4874 check_blame_commit(struct blame *blame, bool check_null_id)
4876         if (!blame->commit)
4877                 report("Commit data not loaded yet");
4878         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4879                 report("No commit exist for the selected line");
4880         else
4881                 return TRUE;
4882         return FALSE;
4885 static void
4886 setup_blame_parent_line(struct view *view, struct blame *blame)
4888         const char *diff_tree_argv[] = {
4889                 "git", "diff-tree", "-U0", blame->commit->id,
4890                         "--", blame->commit->filename, NULL
4891         };
4892         struct io io = {};
4893         int parent_lineno = -1;
4894         int blamed_lineno = -1;
4895         char *line;
4897         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4898                 return;
4900         while ((line = io_get(&io, '\n', TRUE))) {
4901                 if (*line == '@') {
4902                         char *pos = strchr(line, '+');
4904                         parent_lineno = atoi(line + 4);
4905                         if (pos)
4906                                 blamed_lineno = atoi(pos + 1);
4908                 } else if (*line == '+' && parent_lineno != -1) {
4909                         if (blame->lineno == blamed_lineno - 1 &&
4910                             !strcmp(blame->text, line + 1)) {
4911                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4912                                 break;
4913                         }
4914                         blamed_lineno++;
4915                 }
4916         }
4918         done_io(&io);
4921 static enum request
4922 blame_request(struct view *view, enum request request, struct line *line)
4924         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4925         struct blame *blame = line->data;
4927         switch (request) {
4928         case REQ_VIEW_BLAME:
4929                 if (check_blame_commit(blame, TRUE)) {
4930                         string_copy(opt_ref, blame->commit->id);
4931                         string_copy(opt_file, blame->commit->filename);
4932                         if (blame->lineno)
4933                                 view->lineno = blame->lineno;
4934                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4935                 }
4936                 break;
4938         case REQ_PARENT:
4939                 if (check_blame_commit(blame, TRUE) &&
4940                     select_commit_parent(blame->commit->id, opt_ref,
4941                                          blame->commit->filename)) {
4942                         string_copy(opt_file, blame->commit->filename);
4943                         setup_blame_parent_line(view, blame);
4944                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4945                 }
4946                 break;
4948         case REQ_ENTER:
4949                 if (!check_blame_commit(blame, FALSE))
4950                         break;
4952                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4953                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4954                         break;
4956                 if (!strcmp(blame->commit->id, NULL_ID)) {
4957                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4958                         const char *diff_index_argv[] = {
4959                                 "git", "diff-index", "--root", "--patch-with-stat",
4960                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4961                         };
4963                         if (!blame->commit->has_previous) {
4964                                 diff_index_argv[1] = "diff";
4965                                 diff_index_argv[2] = "--no-color";
4966                                 diff_index_argv[6] = "--";
4967                                 diff_index_argv[7] = "/dev/null";
4968                         }
4970                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4971                                 report("Failed to allocate diff command");
4972                                 break;
4973                         }
4974                         flags |= OPEN_PREPARED;
4975                 }
4977                 open_view(view, REQ_VIEW_DIFF, flags);
4978                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4979                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4980                 break;
4982         default:
4983                 return request;
4984         }
4986         return REQ_NONE;
4989 static bool
4990 blame_grep(struct view *view, struct line *line)
4992         struct blame *blame = line->data;
4993         struct blame_commit *commit = blame->commit;
4994         const char *text[] = {
4995                 blame->text,
4996                 commit ? commit->title : "",
4997                 commit ? commit->id : "",
4998                 commit && opt_author ? commit->author : "",
4999                 commit && opt_date ? mkdate(&commit->time) : "",
5000                 NULL
5001         };
5003         return grep_text(view, text);
5006 static void
5007 blame_select(struct view *view, struct line *line)
5009         struct blame *blame = line->data;
5010         struct blame_commit *commit = blame->commit;
5012         if (!commit)
5013                 return;
5015         if (!strcmp(commit->id, NULL_ID))
5016                 string_ncopy(ref_commit, "HEAD", 4);
5017         else
5018                 string_copy_rev(ref_commit, commit->id);
5021 static struct view_ops blame_ops = {
5022         "line",
5023         NULL,
5024         blame_open,
5025         blame_read,
5026         blame_draw,
5027         blame_request,
5028         blame_grep,
5029         blame_select,
5030 };
5032 /*
5033  * Branch backend
5034  */
5036 struct branch {
5037         const char *author;             /* Author of the last commit. */
5038         time_t time;                    /* Date of the last activity. */
5039         struct ref *ref;                /* Name and commit ID information. */
5040 };
5042 static const enum sort_field branch_sort_fields[] = {
5043         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5044 };
5045 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5047 static int
5048 branch_compare(const void *l1, const void *l2)
5050         const struct branch *branch1 = ((const struct line *) l1)->data;
5051         const struct branch *branch2 = ((const struct line *) l2)->data;
5053         switch (get_sort_field(branch_sort_state)) {
5054         case ORDERBY_DATE:
5055                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5057         case ORDERBY_AUTHOR:
5058                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5060         case ORDERBY_NAME:
5061         default:
5062                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5063         }
5066 static bool
5067 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5069         struct branch *branch = line->data;
5070         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5072         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5073                 return TRUE;
5075         if (opt_author && draw_author(view, branch->author))
5076                 return TRUE;
5078         draw_text(view, type, branch->ref->name, TRUE);
5079         return TRUE;
5082 static enum request
5083 branch_request(struct view *view, enum request request, struct line *line)
5085         switch (request) {
5086         case REQ_REFRESH:
5087                 load_refs();
5088                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5089                 return REQ_NONE;
5091         case REQ_TOGGLE_SORT_FIELD:
5092         case REQ_TOGGLE_SORT_ORDER:
5093                 sort_view(view, request, &branch_sort_state, branch_compare);
5094                 return REQ_NONE;
5096         case REQ_ENTER:
5097                 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5098                 return REQ_NONE;
5100         default:
5101                 return request;
5102         }
5105 static bool
5106 branch_read(struct view *view, char *line)
5108         static char id[SIZEOF_REV];
5109         struct branch *reference;
5110         size_t i;
5112         if (!line)
5113                 return TRUE;
5115         switch (get_line_type(line)) {
5116         case LINE_COMMIT:
5117                 string_copy_rev(id, line + STRING_SIZE("commit "));
5118                 return TRUE;
5120         case LINE_AUTHOR:
5121                 for (i = 0, reference = NULL; i < view->lines; i++) {
5122                         struct branch *branch = view->line[i].data;
5124                         if (strcmp(branch->ref->id, id))
5125                                 continue;
5127                         view->line[i].dirty = TRUE;
5128                         if (reference) {
5129                                 branch->author = reference->author;
5130                                 branch->time = reference->time;
5131                                 continue;
5132                         }
5134                         parse_author_line(line + STRING_SIZE("author "),
5135                                           &branch->author, &branch->time);
5136                         reference = branch;
5137                 }
5138                 return TRUE;
5140         default:
5141                 return TRUE;
5142         }
5146 static bool
5147 branch_open_visitor(void *data, struct ref *ref)
5149         struct view *view = data;
5150         struct branch *branch;
5152         if (ref->tag || ref->ltag || ref->remote)
5153                 return TRUE;
5155         branch = calloc(1, sizeof(*branch));
5156         if (!branch)
5157                 return FALSE;
5159         branch->ref = ref;
5160         return !!add_line_data(view, branch, LINE_DEFAULT);
5163 static bool
5164 branch_open(struct view *view)
5166         const char *branch_log[] = {
5167                 "git", "log", "--no-color", "--pretty=raw",
5168                         "--simplify-by-decoration", "--all", NULL
5169         };
5171         if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
5172                 report("Failed to load branch data");
5173                 return TRUE;
5174         }
5176         setup_update(view, view->id);
5177         foreach_ref(branch_open_visitor, view);
5178         view->p_restore = TRUE;
5180         return TRUE;
5183 static bool
5184 branch_grep(struct view *view, struct line *line)
5186         struct branch *branch = line->data;
5187         const char *text[] = {
5188                 branch->ref->name,
5189                 branch->author,
5190                 NULL
5191         };
5193         return grep_text(view, text);
5196 static void
5197 branch_select(struct view *view, struct line *line)
5199         struct branch *branch = line->data;
5201         string_copy_rev(view->ref, branch->ref->id);
5202         string_copy_rev(ref_commit, branch->ref->id);
5203         string_copy_rev(ref_head, branch->ref->id);
5206 static struct view_ops branch_ops = {
5207         "branch",
5208         NULL,
5209         branch_open,
5210         branch_read,
5211         branch_draw,
5212         branch_request,
5213         branch_grep,
5214         branch_select,
5215 };
5217 /*
5218  * Status backend
5219  */
5221 struct status {
5222         char status;
5223         struct {
5224                 mode_t mode;
5225                 char rev[SIZEOF_REV];
5226                 char name[SIZEOF_STR];
5227         } old;
5228         struct {
5229                 mode_t mode;
5230                 char rev[SIZEOF_REV];
5231                 char name[SIZEOF_STR];
5232         } new;
5233 };
5235 static char status_onbranch[SIZEOF_STR];
5236 static struct status stage_status;
5237 static enum line_type stage_line_type;
5238 static size_t stage_chunks;
5239 static int *stage_chunk;
5241 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5243 /* This should work even for the "On branch" line. */
5244 static inline bool
5245 status_has_none(struct view *view, struct line *line)
5247         return line < view->line + view->lines && !line[1].data;
5250 /* Get fields from the diff line:
5251  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5252  */
5253 static inline bool
5254 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5256         const char *old_mode = buf +  1;
5257         const char *new_mode = buf +  8;
5258         const char *old_rev  = buf + 15;
5259         const char *new_rev  = buf + 56;
5260         const char *status   = buf + 97;
5262         if (bufsize < 98 ||
5263             old_mode[-1] != ':' ||
5264             new_mode[-1] != ' ' ||
5265             old_rev[-1]  != ' ' ||
5266             new_rev[-1]  != ' ' ||
5267             status[-1]   != ' ')
5268                 return FALSE;
5270         file->status = *status;
5272         string_copy_rev(file->old.rev, old_rev);
5273         string_copy_rev(file->new.rev, new_rev);
5275         file->old.mode = strtoul(old_mode, NULL, 8);
5276         file->new.mode = strtoul(new_mode, NULL, 8);
5278         file->old.name[0] = file->new.name[0] = 0;
5280         return TRUE;
5283 static bool
5284 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5286         struct status *unmerged = NULL;
5287         char *buf;
5288         struct io io = {};
5290         if (!run_io(&io, argv, NULL, IO_RD))
5291                 return FALSE;
5293         add_line_data(view, NULL, type);
5295         while ((buf = io_get(&io, 0, TRUE))) {
5296                 struct status *file = unmerged;
5298                 if (!file) {
5299                         file = calloc(1, sizeof(*file));
5300                         if (!file || !add_line_data(view, file, type))
5301                                 goto error_out;
5302                 }
5304                 /* Parse diff info part. */
5305                 if (status) {
5306                         file->status = status;
5307                         if (status == 'A')
5308                                 string_copy(file->old.rev, NULL_ID);
5310                 } else if (!file->status || file == unmerged) {
5311                         if (!status_get_diff(file, buf, strlen(buf)))
5312                                 goto error_out;
5314                         buf = io_get(&io, 0, TRUE);
5315                         if (!buf)
5316                                 break;
5318                         /* Collapse all modified entries that follow an
5319                          * associated unmerged entry. */
5320                         if (unmerged == file) {
5321                                 unmerged->status = 'U';
5322                                 unmerged = NULL;
5323                         } else if (file->status == 'U') {
5324                                 unmerged = file;
5325                         }
5326                 }
5328                 /* Grab the old name for rename/copy. */
5329                 if (!*file->old.name &&
5330                     (file->status == 'R' || file->status == 'C')) {
5331                         string_ncopy(file->old.name, buf, strlen(buf));
5333                         buf = io_get(&io, 0, TRUE);
5334                         if (!buf)
5335                                 break;
5336                 }
5338                 /* git-ls-files just delivers a NUL separated list of
5339                  * file names similar to the second half of the
5340                  * git-diff-* output. */
5341                 string_ncopy(file->new.name, buf, strlen(buf));
5342                 if (!*file->old.name)
5343                         string_copy(file->old.name, file->new.name);
5344                 file = NULL;
5345         }
5347         if (io_error(&io)) {
5348 error_out:
5349                 done_io(&io);
5350                 return FALSE;
5351         }
5353         if (!view->line[view->lines - 1].data)
5354                 add_line_data(view, NULL, LINE_STAT_NONE);
5356         done_io(&io);
5357         return TRUE;
5360 /* Don't show unmerged entries in the staged section. */
5361 static const char *status_diff_index_argv[] = {
5362         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5363                              "--cached", "-M", "HEAD", NULL
5364 };
5366 static const char *status_diff_files_argv[] = {
5367         "git", "diff-files", "-z", NULL
5368 };
5370 static const char *status_list_other_argv[] = {
5371         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5372 };
5374 static const char *status_list_no_head_argv[] = {
5375         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5376 };
5378 static const char *update_index_argv[] = {
5379         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5380 };
5382 /* Restore the previous line number to stay in the context or select a
5383  * line with something that can be updated. */
5384 static void
5385 status_restore(struct view *view)
5387         if (view->p_lineno >= view->lines)
5388                 view->p_lineno = view->lines - 1;
5389         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5390                 view->p_lineno++;
5391         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5392                 view->p_lineno--;
5394         /* If the above fails, always skip the "On branch" line. */
5395         if (view->p_lineno < view->lines)
5396                 view->lineno = view->p_lineno;
5397         else
5398                 view->lineno = 1;
5400         if (view->lineno < view->offset)
5401                 view->offset = view->lineno;
5402         else if (view->offset + view->height <= view->lineno)
5403                 view->offset = view->lineno - view->height + 1;
5405         view->p_restore = FALSE;
5408 static void
5409 status_update_onbranch(void)
5411         static const char *paths[][2] = {
5412                 { "rebase-apply/rebasing",      "Rebasing" },
5413                 { "rebase-apply/applying",      "Applying mailbox" },
5414                 { "rebase-apply/",              "Rebasing mailbox" },
5415                 { "rebase-merge/interactive",   "Interactive rebase" },
5416                 { "rebase-merge/",              "Rebase merge" },
5417                 { "MERGE_HEAD",                 "Merging" },
5418                 { "BISECT_LOG",                 "Bisecting" },
5419                 { "HEAD",                       "On branch" },
5420         };
5421         char buf[SIZEOF_STR];
5422         struct stat stat;
5423         int i;
5425         if (is_initial_commit()) {
5426                 string_copy(status_onbranch, "Initial commit");
5427                 return;
5428         }
5430         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5431                 char *head = opt_head;
5433                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5434                     lstat(buf, &stat) < 0)
5435                         continue;
5437                 if (!*opt_head) {
5438                         struct io io = {};
5440                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5441                             io_open(&io, buf) &&
5442                             io_read_buf(&io, buf, sizeof(buf))) {
5443                                 head = buf;
5444                                 if (!prefixcmp(head, "refs/heads/"))
5445                                         head += STRING_SIZE("refs/heads/");
5446                         }
5447                 }
5449                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5450                         string_copy(status_onbranch, opt_head);
5451                 return;
5452         }
5454         string_copy(status_onbranch, "Not currently on any branch");
5457 /* First parse staged info using git-diff-index(1), then parse unstaged
5458  * info using git-diff-files(1), and finally untracked files using
5459  * git-ls-files(1). */
5460 static bool
5461 status_open(struct view *view)
5463         reset_view(view);
5465         add_line_data(view, NULL, LINE_STAT_HEAD);
5466         status_update_onbranch();
5468         run_io_bg(update_index_argv);
5470         if (is_initial_commit()) {
5471                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5472                         return FALSE;
5473         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5474                 return FALSE;
5475         }
5477         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5478             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5479                 return FALSE;
5481         /* Restore the exact position or use the specialized restore
5482          * mode? */
5483         if (!view->p_restore)
5484                 status_restore(view);
5485         return TRUE;
5488 static bool
5489 status_draw(struct view *view, struct line *line, unsigned int lineno)
5491         struct status *status = line->data;
5492         enum line_type type;
5493         const char *text;
5495         if (!status) {
5496                 switch (line->type) {
5497                 case LINE_STAT_STAGED:
5498                         type = LINE_STAT_SECTION;
5499                         text = "Changes to be committed:";
5500                         break;
5502                 case LINE_STAT_UNSTAGED:
5503                         type = LINE_STAT_SECTION;
5504                         text = "Changed but not updated:";
5505                         break;
5507                 case LINE_STAT_UNTRACKED:
5508                         type = LINE_STAT_SECTION;
5509                         text = "Untracked files:";
5510                         break;
5512                 case LINE_STAT_NONE:
5513                         type = LINE_DEFAULT;
5514                         text = "  (no files)";
5515                         break;
5517                 case LINE_STAT_HEAD:
5518                         type = LINE_STAT_HEAD;
5519                         text = status_onbranch;
5520                         break;
5522                 default:
5523                         return FALSE;
5524                 }
5525         } else {
5526                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5528                 buf[0] = status->status;
5529                 if (draw_text(view, line->type, buf, TRUE))
5530                         return TRUE;
5531                 type = LINE_DEFAULT;
5532                 text = status->new.name;
5533         }
5535         draw_text(view, type, text, TRUE);
5536         return TRUE;
5539 static enum request
5540 status_load_error(struct view *view, struct view *stage, const char *path)
5542         if (displayed_views() == 2 || display[current_view] != view)
5543                 maximize_view(view);
5544         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5545         return REQ_NONE;
5548 static enum request
5549 status_enter(struct view *view, struct line *line)
5551         struct status *status = line->data;
5552         const char *oldpath = status ? status->old.name : NULL;
5553         /* Diffs for unmerged entries are empty when passing the new
5554          * path, so leave it empty. */
5555         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5556         const char *info;
5557         enum open_flags split;
5558         struct view *stage = VIEW(REQ_VIEW_STAGE);
5560         if (line->type == LINE_STAT_NONE ||
5561             (!status && line[1].type == LINE_STAT_NONE)) {
5562                 report("No file to diff");
5563                 return REQ_NONE;
5564         }
5566         switch (line->type) {
5567         case LINE_STAT_STAGED:
5568                 if (is_initial_commit()) {
5569                         const char *no_head_diff_argv[] = {
5570                                 "git", "diff", "--no-color", "--patch-with-stat",
5571                                         "--", "/dev/null", newpath, NULL
5572                         };
5574                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5575                                 return status_load_error(view, stage, newpath);
5576                 } else {
5577                         const char *index_show_argv[] = {
5578                                 "git", "diff-index", "--root", "--patch-with-stat",
5579                                         "-C", "-M", "--cached", "HEAD", "--",
5580                                         oldpath, newpath, NULL
5581                         };
5583                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5584                                 return status_load_error(view, stage, newpath);
5585                 }
5587                 if (status)
5588                         info = "Staged changes to %s";
5589                 else
5590                         info = "Staged changes";
5591                 break;
5593         case LINE_STAT_UNSTAGED:
5594         {
5595                 const char *files_show_argv[] = {
5596                         "git", "diff-files", "--root", "--patch-with-stat",
5597                                 "-C", "-M", "--", oldpath, newpath, NULL
5598                 };
5600                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5601                         return status_load_error(view, stage, newpath);
5602                 if (status)
5603                         info = "Unstaged changes to %s";
5604                 else
5605                         info = "Unstaged changes";
5606                 break;
5607         }
5608         case LINE_STAT_UNTRACKED:
5609                 if (!newpath) {
5610                         report("No file to show");
5611                         return REQ_NONE;
5612                 }
5614                 if (!suffixcmp(status->new.name, -1, "/")) {
5615                         report("Cannot display a directory");
5616                         return REQ_NONE;
5617                 }
5619                 if (!prepare_update_file(stage, newpath))
5620                         return status_load_error(view, stage, newpath);
5621                 info = "Untracked file %s";
5622                 break;
5624         case LINE_STAT_HEAD:
5625                 return REQ_NONE;
5627         default:
5628                 die("line type %d not handled in switch", line->type);
5629         }
5631         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5632         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5633         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5634                 if (status) {
5635                         stage_status = *status;
5636                 } else {
5637                         memset(&stage_status, 0, sizeof(stage_status));
5638                 }
5640                 stage_line_type = line->type;
5641                 stage_chunks = 0;
5642                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5643         }
5645         return REQ_NONE;
5648 static bool
5649 status_exists(struct status *status, enum line_type type)
5651         struct view *view = VIEW(REQ_VIEW_STATUS);
5652         unsigned long lineno;
5654         for (lineno = 0; lineno < view->lines; lineno++) {
5655                 struct line *line = &view->line[lineno];
5656                 struct status *pos = line->data;
5658                 if (line->type != type)
5659                         continue;
5660                 if (!pos && (!status || !status->status) && line[1].data) {
5661                         select_view_line(view, lineno);
5662                         return TRUE;
5663                 }
5664                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5665                         select_view_line(view, lineno);
5666                         return TRUE;
5667                 }
5668         }
5670         return FALSE;
5674 static bool
5675 status_update_prepare(struct io *io, enum line_type type)
5677         const char *staged_argv[] = {
5678                 "git", "update-index", "-z", "--index-info", NULL
5679         };
5680         const char *others_argv[] = {
5681                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5682         };
5684         switch (type) {
5685         case LINE_STAT_STAGED:
5686                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5688         case LINE_STAT_UNSTAGED:
5689                 return run_io(io, others_argv, opt_cdup, IO_WR);
5691         case LINE_STAT_UNTRACKED:
5692                 return run_io(io, others_argv, NULL, IO_WR);
5694         default:
5695                 die("line type %d not handled in switch", type);
5696                 return FALSE;
5697         }
5700 static bool
5701 status_update_write(struct io *io, struct status *status, enum line_type type)
5703         char buf[SIZEOF_STR];
5704         size_t bufsize = 0;
5706         switch (type) {
5707         case LINE_STAT_STAGED:
5708                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5709                                         status->old.mode,
5710                                         status->old.rev,
5711                                         status->old.name, 0))
5712                         return FALSE;
5713                 break;
5715         case LINE_STAT_UNSTAGED:
5716         case LINE_STAT_UNTRACKED:
5717                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5718                         return FALSE;
5719                 break;
5721         default:
5722                 die("line type %d not handled in switch", type);
5723         }
5725         return io_write(io, buf, bufsize);
5728 static bool
5729 status_update_file(struct status *status, enum line_type type)
5731         struct io io = {};
5732         bool result;
5734         if (!status_update_prepare(&io, type))
5735                 return FALSE;
5737         result = status_update_write(&io, status, type);
5738         return done_io(&io) && result;
5741 static bool
5742 status_update_files(struct view *view, struct line *line)
5744         char buf[sizeof(view->ref)];
5745         struct io io = {};
5746         bool result = TRUE;
5747         struct line *pos = view->line + view->lines;
5748         int files = 0;
5749         int file, done;
5750         int cursor_y = -1, cursor_x = -1;
5752         if (!status_update_prepare(&io, line->type))
5753                 return FALSE;
5755         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5756                 files++;
5758         string_copy(buf, view->ref);
5759         getsyx(cursor_y, cursor_x);
5760         for (file = 0, done = 5; result && file < files; line++, file++) {
5761                 int almost_done = file * 100 / files;
5763                 if (almost_done > done) {
5764                         done = almost_done;
5765                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5766                                       file, files, done);
5767                         update_view_title(view);
5768                         setsyx(cursor_y, cursor_x);
5769                         doupdate();
5770                 }
5771                 result = status_update_write(&io, line->data, line->type);
5772         }
5773         string_copy(view->ref, buf);
5775         return done_io(&io) && result;
5778 static bool
5779 status_update(struct view *view)
5781         struct line *line = &view->line[view->lineno];
5783         assert(view->lines);
5785         if (!line->data) {
5786                 /* This should work even for the "On branch" line. */
5787                 if (line < view->line + view->lines && !line[1].data) {
5788                         report("Nothing to update");
5789                         return FALSE;
5790                 }
5792                 if (!status_update_files(view, line + 1)) {
5793                         report("Failed to update file status");
5794                         return FALSE;
5795                 }
5797         } else if (!status_update_file(line->data, line->type)) {
5798                 report("Failed to update file status");
5799                 return FALSE;
5800         }
5802         return TRUE;
5805 static bool
5806 status_revert(struct status *status, enum line_type type, bool has_none)
5808         if (!status || type != LINE_STAT_UNSTAGED) {
5809                 if (type == LINE_STAT_STAGED) {
5810                         report("Cannot revert changes to staged files");
5811                 } else if (type == LINE_STAT_UNTRACKED) {
5812                         report("Cannot revert changes to untracked files");
5813                 } else if (has_none) {
5814                         report("Nothing to revert");
5815                 } else {
5816                         report("Cannot revert changes to multiple files");
5817                 }
5818                 return FALSE;
5820         } else {
5821                 char mode[10] = "100644";
5822                 const char *reset_argv[] = {
5823                         "git", "update-index", "--cacheinfo", mode,
5824                                 status->old.rev, status->old.name, NULL
5825                 };
5826                 const char *checkout_argv[] = {
5827                         "git", "checkout", "--", status->old.name, NULL
5828                 };
5830                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5831                         return FALSE;
5832                 string_format(mode, "%o", status->old.mode);
5833                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5834                         run_io_fg(checkout_argv, opt_cdup);
5835         }
5838 static enum request
5839 status_request(struct view *view, enum request request, struct line *line)
5841         struct status *status = line->data;
5843         switch (request) {
5844         case REQ_STATUS_UPDATE:
5845                 if (!status_update(view))
5846                         return REQ_NONE;
5847                 break;
5849         case REQ_STATUS_REVERT:
5850                 if (!status_revert(status, line->type, status_has_none(view, line)))
5851                         return REQ_NONE;
5852                 break;
5854         case REQ_STATUS_MERGE:
5855                 if (!status || status->status != 'U') {
5856                         report("Merging only possible for files with unmerged status ('U').");
5857                         return REQ_NONE;
5858                 }
5859                 open_mergetool(status->new.name);
5860                 break;
5862         case REQ_EDIT:
5863                 if (!status)
5864                         return request;
5865                 if (status->status == 'D') {
5866                         report("File has been deleted.");
5867                         return REQ_NONE;
5868                 }
5870                 open_editor(status->status != '?', status->new.name);
5871                 break;
5873         case REQ_VIEW_BLAME:
5874                 if (status) {
5875                         string_copy(opt_file, status->new.name);
5876                         opt_ref[0] = 0;
5877                 }
5878                 return request;
5880         case REQ_ENTER:
5881                 /* After returning the status view has been split to
5882                  * show the stage view. No further reloading is
5883                  * necessary. */
5884                 return status_enter(view, line);
5886         case REQ_REFRESH:
5887                 /* Simply reload the view. */
5888                 break;
5890         default:
5891                 return request;
5892         }
5894         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5896         return REQ_NONE;
5899 static void
5900 status_select(struct view *view, struct line *line)
5902         struct status *status = line->data;
5903         char file[SIZEOF_STR] = "all files";
5904         const char *text;
5905         const char *key;
5907         if (status && !string_format(file, "'%s'", status->new.name))
5908                 return;
5910         if (!status && line[1].type == LINE_STAT_NONE)
5911                 line++;
5913         switch (line->type) {
5914         case LINE_STAT_STAGED:
5915                 text = "Press %s to unstage %s for commit";
5916                 break;
5918         case LINE_STAT_UNSTAGED:
5919                 text = "Press %s to stage %s for commit";
5920                 break;
5922         case LINE_STAT_UNTRACKED:
5923                 text = "Press %s to stage %s for addition";
5924                 break;
5926         case LINE_STAT_HEAD:
5927         case LINE_STAT_NONE:
5928                 text = "Nothing to update";
5929                 break;
5931         default:
5932                 die("line type %d not handled in switch", line->type);
5933         }
5935         if (status && status->status == 'U') {
5936                 text = "Press %s to resolve conflict in %s";
5937                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5939         } else {
5940                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5941         }
5943         string_format(view->ref, text, key, file);
5946 static bool
5947 status_grep(struct view *view, struct line *line)
5949         struct status *status = line->data;
5951         if (status) {
5952                 const char buf[2] = { status->status, 0 };
5953                 const char *text[] = { status->new.name, buf, NULL };
5955                 return grep_text(view, text);
5956         }
5958         return FALSE;
5961 static struct view_ops status_ops = {
5962         "file",
5963         NULL,
5964         status_open,
5965         NULL,
5966         status_draw,
5967         status_request,
5968         status_grep,
5969         status_select,
5970 };
5973 static bool
5974 stage_diff_write(struct io *io, struct line *line, struct line *end)
5976         while (line < end) {
5977                 if (!io_write(io, line->data, strlen(line->data)) ||
5978                     !io_write(io, "\n", 1))
5979                         return FALSE;
5980                 line++;
5981                 if (line->type == LINE_DIFF_CHUNK ||
5982                     line->type == LINE_DIFF_HEADER)
5983                         break;
5984         }
5986         return TRUE;
5989 static struct line *
5990 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5992         for (; view->line < line; line--)
5993                 if (line->type == type)
5994                         return line;
5996         return NULL;
5999 static bool
6000 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6002         const char *apply_argv[SIZEOF_ARG] = {
6003                 "git", "apply", "--whitespace=nowarn", NULL
6004         };
6005         struct line *diff_hdr;
6006         struct io io = {};
6007         int argc = 3;
6009         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6010         if (!diff_hdr)
6011                 return FALSE;
6013         if (!revert)
6014                 apply_argv[argc++] = "--cached";
6015         if (revert || stage_line_type == LINE_STAT_STAGED)
6016                 apply_argv[argc++] = "-R";
6017         apply_argv[argc++] = "-";
6018         apply_argv[argc++] = NULL;
6019         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6020                 return FALSE;
6022         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6023             !stage_diff_write(&io, chunk, view->line + view->lines))
6024                 chunk = NULL;
6026         done_io(&io);
6027         run_io_bg(update_index_argv);
6029         return chunk ? TRUE : FALSE;
6032 static bool
6033 stage_update(struct view *view, struct line *line)
6035         struct line *chunk = NULL;
6037         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6038                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6040         if (chunk) {
6041                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6042                         report("Failed to apply chunk");
6043                         return FALSE;
6044                 }
6046         } else if (!stage_status.status) {
6047                 view = VIEW(REQ_VIEW_STATUS);
6049                 for (line = view->line; line < view->line + view->lines; line++)
6050                         if (line->type == stage_line_type)
6051                                 break;
6053                 if (!status_update_files(view, line + 1)) {
6054                         report("Failed to update files");
6055                         return FALSE;
6056                 }
6058         } else if (!status_update_file(&stage_status, stage_line_type)) {
6059                 report("Failed to update file");
6060                 return FALSE;
6061         }
6063         return TRUE;
6066 static bool
6067 stage_revert(struct view *view, struct line *line)
6069         struct line *chunk = NULL;
6071         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6072                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6074         if (chunk) {
6075                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6076                         return FALSE;
6078                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6079                         report("Failed to revert chunk");
6080                         return FALSE;
6081                 }
6082                 return TRUE;
6084         } else {
6085                 return status_revert(stage_status.status ? &stage_status : NULL,
6086                                      stage_line_type, FALSE);
6087         }
6091 static void
6092 stage_next(struct view *view, struct line *line)
6094         int i;
6096         if (!stage_chunks) {
6097                 for (line = view->line; line < view->line + view->lines; line++) {
6098                         if (line->type != LINE_DIFF_CHUNK)
6099                                 continue;
6101                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6102                                 report("Allocation failure");
6103                                 return;
6104                         }
6106                         stage_chunk[stage_chunks++] = line - view->line;
6107                 }
6108         }
6110         for (i = 0; i < stage_chunks; i++) {
6111                 if (stage_chunk[i] > view->lineno) {
6112                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6113                         report("Chunk %d of %d", i + 1, stage_chunks);
6114                         return;
6115                 }
6116         }
6118         report("No next chunk found");
6121 static enum request
6122 stage_request(struct view *view, enum request request, struct line *line)
6124         switch (request) {
6125         case REQ_STATUS_UPDATE:
6126                 if (!stage_update(view, line))
6127                         return REQ_NONE;
6128                 break;
6130         case REQ_STATUS_REVERT:
6131                 if (!stage_revert(view, line))
6132                         return REQ_NONE;
6133                 break;
6135         case REQ_STAGE_NEXT:
6136                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6137                         report("File is untracked; press %s to add",
6138                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6139                         return REQ_NONE;
6140                 }
6141                 stage_next(view, line);
6142                 return REQ_NONE;
6144         case REQ_EDIT:
6145                 if (!stage_status.new.name[0])
6146                         return request;
6147                 if (stage_status.status == 'D') {
6148                         report("File has been deleted.");
6149                         return REQ_NONE;
6150                 }
6152                 open_editor(stage_status.status != '?', stage_status.new.name);
6153                 break;
6155         case REQ_REFRESH:
6156                 /* Reload everything ... */
6157                 break;
6159         case REQ_VIEW_BLAME:
6160                 if (stage_status.new.name[0]) {
6161                         string_copy(opt_file, stage_status.new.name);
6162                         opt_ref[0] = 0;
6163                 }
6164                 return request;
6166         case REQ_ENTER:
6167                 return pager_request(view, request, line);
6169         default:
6170                 return request;
6171         }
6173         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6174         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6176         /* Check whether the staged entry still exists, and close the
6177          * stage view if it doesn't. */
6178         if (!status_exists(&stage_status, stage_line_type)) {
6179                 status_restore(VIEW(REQ_VIEW_STATUS));
6180                 return REQ_VIEW_CLOSE;
6181         }
6183         if (stage_line_type == LINE_STAT_UNTRACKED) {
6184                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6185                         report("Cannot display a directory");
6186                         return REQ_NONE;
6187                 }
6189                 if (!prepare_update_file(view, stage_status.new.name)) {
6190                         report("Failed to open file: %s", strerror(errno));
6191                         return REQ_NONE;
6192                 }
6193         }
6194         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6196         return REQ_NONE;
6199 static struct view_ops stage_ops = {
6200         "line",
6201         NULL,
6202         NULL,
6203         pager_read,
6204         pager_draw,
6205         stage_request,
6206         pager_grep,
6207         pager_select,
6208 };
6211 /*
6212  * Revision graph
6213  */
6215 struct commit {
6216         char id[SIZEOF_REV];            /* SHA1 ID. */
6217         char title[128];                /* First line of the commit message. */
6218         const char *author;             /* Author of the commit. */
6219         time_t time;                    /* Date from the author ident. */
6220         struct ref_list *refs;          /* Repository references. */
6221         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6222         size_t graph_size;              /* The width of the graph array. */
6223         bool has_parents;               /* Rewritten --parents seen. */
6224 };
6226 /* Size of rev graph with no  "padding" columns */
6227 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6229 struct rev_graph {
6230         struct rev_graph *prev, *next, *parents;
6231         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6232         size_t size;
6233         struct commit *commit;
6234         size_t pos;
6235         unsigned int boundary:1;
6236 };
6238 /* Parents of the commit being visualized. */
6239 static struct rev_graph graph_parents[4];
6241 /* The current stack of revisions on the graph. */
6242 static struct rev_graph graph_stacks[4] = {
6243         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6244         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6245         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6246         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6247 };
6249 static inline bool
6250 graph_parent_is_merge(struct rev_graph *graph)
6252         return graph->parents->size > 1;
6255 static inline void
6256 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6258         struct commit *commit = graph->commit;
6260         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6261                 commit->graph[commit->graph_size++] = symbol;
6264 static void
6265 clear_rev_graph(struct rev_graph *graph)
6267         graph->boundary = 0;
6268         graph->size = graph->pos = 0;
6269         graph->commit = NULL;
6270         memset(graph->parents, 0, sizeof(*graph->parents));
6273 static void
6274 done_rev_graph(struct rev_graph *graph)
6276         if (graph_parent_is_merge(graph) &&
6277             graph->pos < graph->size - 1 &&
6278             graph->next->size == graph->size + graph->parents->size - 1) {
6279                 size_t i = graph->pos + graph->parents->size - 1;
6281                 graph->commit->graph_size = i * 2;
6282                 while (i < graph->next->size - 1) {
6283                         append_to_rev_graph(graph, ' ');
6284                         append_to_rev_graph(graph, '\\');
6285                         i++;
6286                 }
6287         }
6289         clear_rev_graph(graph);
6292 static void
6293 push_rev_graph(struct rev_graph *graph, const char *parent)
6295         int i;
6297         /* "Collapse" duplicate parents lines.
6298          *
6299          * FIXME: This needs to also update update the drawn graph but
6300          * for now it just serves as a method for pruning graph lines. */
6301         for (i = 0; i < graph->size; i++)
6302                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6303                         return;
6305         if (graph->size < SIZEOF_REVITEMS) {
6306                 string_copy_rev(graph->rev[graph->size++], parent);
6307         }
6310 static chtype
6311 get_rev_graph_symbol(struct rev_graph *graph)
6313         chtype symbol;
6315         if (graph->boundary)
6316                 symbol = REVGRAPH_BOUND;
6317         else if (graph->parents->size == 0)
6318                 symbol = REVGRAPH_INIT;
6319         else if (graph_parent_is_merge(graph))
6320                 symbol = REVGRAPH_MERGE;
6321         else if (graph->pos >= graph->size)
6322                 symbol = REVGRAPH_BRANCH;
6323         else
6324                 symbol = REVGRAPH_COMMIT;
6326         return symbol;
6329 static void
6330 draw_rev_graph(struct rev_graph *graph)
6332         struct rev_filler {
6333                 chtype separator, line;
6334         };
6335         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6336         static struct rev_filler fillers[] = {
6337                 { ' ',  '|' },
6338                 { '`',  '.' },
6339                 { '\'', ' ' },
6340                 { '/',  ' ' },
6341         };
6342         chtype symbol = get_rev_graph_symbol(graph);
6343         struct rev_filler *filler;
6344         size_t i;
6346         if (opt_line_graphics)
6347                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6349         filler = &fillers[DEFAULT];
6351         for (i = 0; i < graph->pos; i++) {
6352                 append_to_rev_graph(graph, filler->line);
6353                 if (graph_parent_is_merge(graph->prev) &&
6354                     graph->prev->pos == i)
6355                         filler = &fillers[RSHARP];
6357                 append_to_rev_graph(graph, filler->separator);
6358         }
6360         /* Place the symbol for this revision. */
6361         append_to_rev_graph(graph, symbol);
6363         if (graph->prev->size > graph->size)
6364                 filler = &fillers[RDIAG];
6365         else
6366                 filler = &fillers[DEFAULT];
6368         i++;
6370         for (; i < graph->size; i++) {
6371                 append_to_rev_graph(graph, filler->separator);
6372                 append_to_rev_graph(graph, filler->line);
6373                 if (graph_parent_is_merge(graph->prev) &&
6374                     i < graph->prev->pos + graph->parents->size)
6375                         filler = &fillers[RSHARP];
6376                 if (graph->prev->size > graph->size)
6377                         filler = &fillers[LDIAG];
6378         }
6380         if (graph->prev->size > graph->size) {
6381                 append_to_rev_graph(graph, filler->separator);
6382                 if (filler->line != ' ')
6383                         append_to_rev_graph(graph, filler->line);
6384         }
6387 /* Prepare the next rev graph */
6388 static void
6389 prepare_rev_graph(struct rev_graph *graph)
6391         size_t i;
6393         /* First, traverse all lines of revisions up to the active one. */
6394         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6395                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6396                         break;
6398                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6399         }
6401         /* Interleave the new revision parent(s). */
6402         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6403                 push_rev_graph(graph->next, graph->parents->rev[i]);
6405         /* Lastly, put any remaining revisions. */
6406         for (i = graph->pos + 1; i < graph->size; i++)
6407                 push_rev_graph(graph->next, graph->rev[i]);
6410 static void
6411 update_rev_graph(struct view *view, struct rev_graph *graph)
6413         /* If this is the finalizing update ... */
6414         if (graph->commit)
6415                 prepare_rev_graph(graph);
6417         /* Graph visualization needs a one rev look-ahead,
6418          * so the first update doesn't visualize anything. */
6419         if (!graph->prev->commit)
6420                 return;
6422         if (view->lines > 2)
6423                 view->line[view->lines - 3].dirty = 1;
6424         if (view->lines > 1)
6425                 view->line[view->lines - 2].dirty = 1;
6426         draw_rev_graph(graph->prev);
6427         done_rev_graph(graph->prev->prev);
6431 /*
6432  * Main view backend
6433  */
6435 static const char *main_argv[SIZEOF_ARG] = {
6436         "git", "log", "--no-color", "--pretty=raw", "--parents",
6437                       "--topo-order", "%(head)", NULL
6438 };
6440 static bool
6441 main_draw(struct view *view, struct line *line, unsigned int lineno)
6443         struct commit *commit = line->data;
6445         if (!commit->author)
6446                 return FALSE;
6448         if (opt_date && draw_date(view, &commit->time))
6449                 return TRUE;
6451         if (opt_author && draw_author(view, commit->author))
6452                 return TRUE;
6454         if (opt_rev_graph && commit->graph_size &&
6455             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6456                 return TRUE;
6458         if (opt_show_refs && commit->refs) {
6459                 size_t i;
6461                 for (i = 0; i < commit->refs->size; i++) {
6462                         struct ref *ref = commit->refs->refs[i];
6463                         enum line_type type;
6465                         if (ref->head)
6466                                 type = LINE_MAIN_HEAD;
6467                         else if (ref->ltag)
6468                                 type = LINE_MAIN_LOCAL_TAG;
6469                         else if (ref->tag)
6470                                 type = LINE_MAIN_TAG;
6471                         else if (ref->tracked)
6472                                 type = LINE_MAIN_TRACKED;
6473                         else if (ref->remote)
6474                                 type = LINE_MAIN_REMOTE;
6475                         else
6476                                 type = LINE_MAIN_REF;
6478                         if (draw_text(view, type, "[", TRUE) ||
6479                             draw_text(view, type, ref->name, TRUE) ||
6480                             draw_text(view, type, "]", TRUE))
6481                                 return TRUE;
6483                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6484                                 return TRUE;
6485                 }
6486         }
6488         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6489         return TRUE;
6492 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6493 static bool
6494 main_read(struct view *view, char *line)
6496         static struct rev_graph *graph = graph_stacks;
6497         enum line_type type;
6498         struct commit *commit;
6500         if (!line) {
6501                 int i;
6503                 if (!view->lines && !view->parent)
6504                         die("No revisions match the given arguments.");
6505                 if (view->lines > 0) {
6506                         commit = view->line[view->lines - 1].data;
6507                         view->line[view->lines - 1].dirty = 1;
6508                         if (!commit->author) {
6509                                 view->lines--;
6510                                 free(commit);
6511                                 graph->commit = NULL;
6512                         }
6513                 }
6514                 update_rev_graph(view, graph);
6516                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6517                         clear_rev_graph(&graph_stacks[i]);
6518                 return TRUE;
6519         }
6521         type = get_line_type(line);
6522         if (type == LINE_COMMIT) {
6523                 commit = calloc(1, sizeof(struct commit));
6524                 if (!commit)
6525                         return FALSE;
6527                 line += STRING_SIZE("commit ");
6528                 if (*line == '-') {
6529                         graph->boundary = 1;
6530                         line++;
6531                 }
6533                 string_copy_rev(commit->id, line);
6534                 commit->refs = get_ref_list(commit->id);
6535                 graph->commit = commit;
6536                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6538                 while ((line = strchr(line, ' '))) {
6539                         line++;
6540                         push_rev_graph(graph->parents, line);
6541                         commit->has_parents = TRUE;
6542                 }
6543                 return TRUE;
6544         }
6546         if (!view->lines)
6547                 return TRUE;
6548         commit = view->line[view->lines - 1].data;
6550         switch (type) {
6551         case LINE_PARENT:
6552                 if (commit->has_parents)
6553                         break;
6554                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6555                 break;
6557         case LINE_AUTHOR:
6558                 parse_author_line(line + STRING_SIZE("author "),
6559                                   &commit->author, &commit->time);
6560                 update_rev_graph(view, graph);
6561                 graph = graph->next;
6562                 break;
6564         default:
6565                 /* Fill in the commit title if it has not already been set. */
6566                 if (commit->title[0])
6567                         break;
6569                 /* Require titles to start with a non-space character at the
6570                  * offset used by git log. */
6571                 if (strncmp(line, "    ", 4))
6572                         break;
6573                 line += 4;
6574                 /* Well, if the title starts with a whitespace character,
6575                  * try to be forgiving.  Otherwise we end up with no title. */
6576                 while (isspace(*line))
6577                         line++;
6578                 if (*line == '\0')
6579                         break;
6580                 /* FIXME: More graceful handling of titles; append "..." to
6581                  * shortened titles, etc. */
6583                 string_expand(commit->title, sizeof(commit->title), line, 1);
6584                 view->line[view->lines - 1].dirty = 1;
6585         }
6587         return TRUE;
6590 static enum request
6591 main_request(struct view *view, enum request request, struct line *line)
6593         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6595         switch (request) {
6596         case REQ_ENTER:
6597                 open_view(view, REQ_VIEW_DIFF, flags);
6598                 break;
6599         case REQ_REFRESH:
6600                 load_refs();
6601                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6602                 break;
6603         default:
6604                 return request;
6605         }
6607         return REQ_NONE;
6610 static bool
6611 grep_refs(struct ref_list *list, regex_t *regex)
6613         regmatch_t pmatch;
6614         size_t i;
6616         if (!opt_show_refs || !list)
6617                 return FALSE;
6619         for (i = 0; i < list->size; i++) {
6620                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6621                         return TRUE;
6622         }
6624         return FALSE;
6627 static bool
6628 main_grep(struct view *view, struct line *line)
6630         struct commit *commit = line->data;
6631         const char *text[] = {
6632                 commit->title,
6633                 opt_author ? commit->author : "",
6634                 opt_date ? mkdate(&commit->time) : "",
6635                 NULL
6636         };
6638         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6641 static void
6642 main_select(struct view *view, struct line *line)
6644         struct commit *commit = line->data;
6646         string_copy_rev(view->ref, commit->id);
6647         string_copy_rev(ref_commit, view->ref);
6650 static struct view_ops main_ops = {
6651         "commit",
6652         main_argv,
6653         NULL,
6654         main_read,
6655         main_draw,
6656         main_request,
6657         main_grep,
6658         main_select,
6659 };
6662 /*
6663  * Unicode / UTF-8 handling
6664  *
6665  * NOTE: Much of the following code for dealing with Unicode is derived from
6666  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6667  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6668  */
6670 static inline int
6671 unicode_width(unsigned long c)
6673         if (c >= 0x1100 &&
6674            (c <= 0x115f                         /* Hangul Jamo */
6675             || c == 0x2329
6676             || c == 0x232a
6677             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6678                                                 /* CJK ... Yi */
6679             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6680             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6681             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6682             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6683             || (c >= 0xffe0  && c <= 0xffe6)
6684             || (c >= 0x20000 && c <= 0x2fffd)
6685             || (c >= 0x30000 && c <= 0x3fffd)))
6686                 return 2;
6688         if (c == '\t')
6689                 return opt_tab_size;
6691         return 1;
6694 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6695  * Illegal bytes are set one. */
6696 static const unsigned char utf8_bytes[256] = {
6697         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,
6698         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,
6699         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,
6700         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,
6701         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,
6702         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,
6703         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,
6704         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,
6705 };
6707 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6708 static inline unsigned long
6709 utf8_to_unicode(const char *string, size_t length)
6711         unsigned long unicode;
6713         switch (length) {
6714         case 1:
6715                 unicode  =   string[0];
6716                 break;
6717         case 2:
6718                 unicode  =  (string[0] & 0x1f) << 6;
6719                 unicode +=  (string[1] & 0x3f);
6720                 break;
6721         case 3:
6722                 unicode  =  (string[0] & 0x0f) << 12;
6723                 unicode += ((string[1] & 0x3f) << 6);
6724                 unicode +=  (string[2] & 0x3f);
6725                 break;
6726         case 4:
6727                 unicode  =  (string[0] & 0x0f) << 18;
6728                 unicode += ((string[1] & 0x3f) << 12);
6729                 unicode += ((string[2] & 0x3f) << 6);
6730                 unicode +=  (string[3] & 0x3f);
6731                 break;
6732         case 5:
6733                 unicode  =  (string[0] & 0x0f) << 24;
6734                 unicode += ((string[1] & 0x3f) << 18);
6735                 unicode += ((string[2] & 0x3f) << 12);
6736                 unicode += ((string[3] & 0x3f) << 6);
6737                 unicode +=  (string[4] & 0x3f);
6738                 break;
6739         case 6:
6740                 unicode  =  (string[0] & 0x01) << 30;
6741                 unicode += ((string[1] & 0x3f) << 24);
6742                 unicode += ((string[2] & 0x3f) << 18);
6743                 unicode += ((string[3] & 0x3f) << 12);
6744                 unicode += ((string[4] & 0x3f) << 6);
6745                 unicode +=  (string[5] & 0x3f);
6746                 break;
6747         default:
6748                 die("Invalid Unicode length");
6749         }
6751         /* Invalid characters could return the special 0xfffd value but NUL
6752          * should be just as good. */
6753         return unicode > 0xffff ? 0 : unicode;
6756 /* Calculates how much of string can be shown within the given maximum width
6757  * and sets trimmed parameter to non-zero value if all of string could not be
6758  * shown. If the reserve flag is TRUE, it will reserve at least one
6759  * trailing character, which can be useful when drawing a delimiter.
6760  *
6761  * Returns the number of bytes to output from string to satisfy max_width. */
6762 static size_t
6763 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6765         const char *string = *start;
6766         const char *end = strchr(string, '\0');
6767         unsigned char last_bytes = 0;
6768         size_t last_ucwidth = 0;
6770         *width = 0;
6771         *trimmed = 0;
6773         while (string < end) {
6774                 int c = *(unsigned char *) string;
6775                 unsigned char bytes = utf8_bytes[c];
6776                 size_t ucwidth;
6777                 unsigned long unicode;
6779                 if (string + bytes > end)
6780                         break;
6782                 /* Change representation to figure out whether
6783                  * it is a single- or double-width character. */
6785                 unicode = utf8_to_unicode(string, bytes);
6786                 /* FIXME: Graceful handling of invalid Unicode character. */
6787                 if (!unicode)
6788                         break;
6790                 ucwidth = unicode_width(unicode);
6791                 if (skip > 0) {
6792                         skip -= ucwidth <= skip ? ucwidth : skip;
6793                         *start += bytes;
6794                 }
6795                 *width  += ucwidth;
6796                 if (*width > max_width) {
6797                         *trimmed = 1;
6798                         *width -= ucwidth;
6799                         if (reserve && *width == max_width) {
6800                                 string -= last_bytes;
6801                                 *width -= last_ucwidth;
6802                         }
6803                         break;
6804                 }
6806                 string  += bytes;
6807                 last_bytes = ucwidth ? bytes : 0;
6808                 last_ucwidth = ucwidth;
6809         }
6811         return string - *start;
6815 /*
6816  * Status management
6817  */
6819 /* Whether or not the curses interface has been initialized. */
6820 static bool cursed = FALSE;
6822 /* Terminal hacks and workarounds. */
6823 static bool use_scroll_redrawwin;
6824 static bool use_scroll_status_wclear;
6826 /* The status window is used for polling keystrokes. */
6827 static WINDOW *status_win;
6829 /* Reading from the prompt? */
6830 static bool input_mode = FALSE;
6832 static bool status_empty = FALSE;
6834 /* Update status and title window. */
6835 static void
6836 report(const char *msg, ...)
6838         struct view *view = display[current_view];
6840         if (input_mode)
6841                 return;
6843         if (!view) {
6844                 char buf[SIZEOF_STR];
6845                 va_list args;
6847                 va_start(args, msg);
6848                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6849                         buf[sizeof(buf) - 1] = 0;
6850                         buf[sizeof(buf) - 2] = '.';
6851                         buf[sizeof(buf) - 3] = '.';
6852                         buf[sizeof(buf) - 4] = '.';
6853                 }
6854                 va_end(args);
6855                 die("%s", buf);
6856         }
6858         if (!status_empty || *msg) {
6859                 va_list args;
6861                 va_start(args, msg);
6863                 wmove(status_win, 0, 0);
6864                 if (view->has_scrolled && use_scroll_status_wclear)
6865                         wclear(status_win);
6866                 if (*msg) {
6867                         vwprintw(status_win, msg, args);
6868                         status_empty = FALSE;
6869                 } else {
6870                         status_empty = TRUE;
6871                 }
6872                 wclrtoeol(status_win);
6873                 wnoutrefresh(status_win);
6875                 va_end(args);
6876         }
6878         update_view_title(view);
6881 /* Controls when nodelay should be in effect when polling user input. */
6882 static void
6883 set_nonblocking_input(bool loading)
6885         static unsigned int loading_views;
6887         if ((loading == FALSE && loading_views-- == 1) ||
6888             (loading == TRUE  && loading_views++ == 0))
6889                 nodelay(status_win, loading);
6892 static void
6893 init_display(void)
6895         const char *term;
6896         int x, y;
6898         /* Initialize the curses library */
6899         if (isatty(STDIN_FILENO)) {
6900                 cursed = !!initscr();
6901                 opt_tty = stdin;
6902         } else {
6903                 /* Leave stdin and stdout alone when acting as a pager. */
6904                 opt_tty = fopen("/dev/tty", "r+");
6905                 if (!opt_tty)
6906                         die("Failed to open /dev/tty");
6907                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6908         }
6910         if (!cursed)
6911                 die("Failed to initialize curses");
6913         nonl();         /* Disable conversion and detect newlines from input. */
6914         cbreak();       /* Take input chars one at a time, no wait for \n */
6915         noecho();       /* Don't echo input */
6916         leaveok(stdscr, FALSE);
6918         if (has_colors())
6919                 init_colors();
6921         getmaxyx(stdscr, y, x);
6922         status_win = newwin(1, 0, y - 1, 0);
6923         if (!status_win)
6924                 die("Failed to create status window");
6926         /* Enable keyboard mapping */
6927         keypad(status_win, TRUE);
6928         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6930         TABSIZE = opt_tab_size;
6931         if (opt_line_graphics) {
6932                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6933         }
6935         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6936         if (term && !strcmp(term, "gnome-terminal")) {
6937                 /* In the gnome-terminal-emulator, the message from
6938                  * scrolling up one line when impossible followed by
6939                  * scrolling down one line causes corruption of the
6940                  * status line. This is fixed by calling wclear. */
6941                 use_scroll_status_wclear = TRUE;
6942                 use_scroll_redrawwin = FALSE;
6944         } else if (term && !strcmp(term, "xrvt-xpm")) {
6945                 /* No problems with full optimizations in xrvt-(unicode)
6946                  * and aterm. */
6947                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6949         } else {
6950                 /* When scrolling in (u)xterm the last line in the
6951                  * scrolling direction will update slowly. */
6952                 use_scroll_redrawwin = TRUE;
6953                 use_scroll_status_wclear = FALSE;
6954         }
6957 static int
6958 get_input(int prompt_position)
6960         struct view *view;
6961         int i, key, cursor_y, cursor_x;
6963         if (prompt_position)
6964                 input_mode = TRUE;
6966         while (TRUE) {
6967                 foreach_view (view, i) {
6968                         update_view(view);
6969                         if (view_is_displayed(view) && view->has_scrolled &&
6970                             use_scroll_redrawwin)
6971                                 redrawwin(view->win);
6972                         view->has_scrolled = FALSE;
6973                 }
6975                 /* Update the cursor position. */
6976                 if (prompt_position) {
6977                         getbegyx(status_win, cursor_y, cursor_x);
6978                         cursor_x = prompt_position;
6979                 } else {
6980                         view = display[current_view];
6981                         getbegyx(view->win, cursor_y, cursor_x);
6982                         cursor_x = view->width - 1;
6983                         cursor_y += view->lineno - view->offset;
6984                 }
6985                 setsyx(cursor_y, cursor_x);
6987                 /* Refresh, accept single keystroke of input */
6988                 doupdate();
6989                 key = wgetch(status_win);
6991                 /* wgetch() with nodelay() enabled returns ERR when
6992                  * there's no input. */
6993                 if (key == ERR) {
6995                 } else if (key == KEY_RESIZE) {
6996                         int height, width;
6998                         getmaxyx(stdscr, height, width);
7000                         wresize(status_win, 1, width);
7001                         mvwin(status_win, height - 1, 0);
7002                         wnoutrefresh(status_win);
7003                         resize_display();
7004                         redraw_display(TRUE);
7006                 } else {
7007                         input_mode = FALSE;
7008                         return key;
7009                 }
7010         }
7013 static char *
7014 prompt_input(const char *prompt, input_handler handler, void *data)
7016         enum input_status status = INPUT_OK;
7017         static char buf[SIZEOF_STR];
7018         size_t pos = 0;
7020         buf[pos] = 0;
7022         while (status == INPUT_OK || status == INPUT_SKIP) {
7023                 int key;
7025                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7026                 wclrtoeol(status_win);
7028                 key = get_input(pos + 1);
7029                 switch (key) {
7030                 case KEY_RETURN:
7031                 case KEY_ENTER:
7032                 case '\n':
7033                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7034                         break;
7036                 case KEY_BACKSPACE:
7037                         if (pos > 0)
7038                                 buf[--pos] = 0;
7039                         else
7040                                 status = INPUT_CANCEL;
7041                         break;
7043                 case KEY_ESC:
7044                         status = INPUT_CANCEL;
7045                         break;
7047                 default:
7048                         if (pos >= sizeof(buf)) {
7049                                 report("Input string too long");
7050                                 return NULL;
7051                         }
7053                         status = handler(data, buf, key);
7054                         if (status == INPUT_OK)
7055                                 buf[pos++] = (char) key;
7056                 }
7057         }
7059         /* Clear the status window */
7060         status_empty = FALSE;
7061         report("");
7063         if (status == INPUT_CANCEL)
7064                 return NULL;
7066         buf[pos++] = 0;
7068         return buf;
7071 static enum input_status
7072 prompt_yesno_handler(void *data, char *buf, int c)
7074         if (c == 'y' || c == 'Y')
7075                 return INPUT_STOP;
7076         if (c == 'n' || c == 'N')
7077                 return INPUT_CANCEL;
7078         return INPUT_SKIP;
7081 static bool
7082 prompt_yesno(const char *prompt)
7084         char prompt2[SIZEOF_STR];
7086         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7087                 return FALSE;
7089         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7092 static enum input_status
7093 read_prompt_handler(void *data, char *buf, int c)
7095         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7098 static char *
7099 read_prompt(const char *prompt)
7101         return prompt_input(prompt, read_prompt_handler, NULL);
7104 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7106         enum input_status status = INPUT_OK;
7107         int size = 0;
7109         while (items[size].text)
7110                 size++;
7112         while (status == INPUT_OK) {
7113                 const struct menu_item *item = &items[*selected];
7114                 int key;
7115                 int i;
7117                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7118                           prompt, *selected + 1, size);
7119                 if (item->hotkey)
7120                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7121                 wprintw(status_win, "%s", item->text);
7122                 wclrtoeol(status_win);
7124                 key = get_input(COLS - 1);
7125                 switch (key) {
7126                 case KEY_RETURN:
7127                 case KEY_ENTER:
7128                 case '\n':
7129                         status = INPUT_STOP;
7130                         break;
7132                 case KEY_LEFT:
7133                 case KEY_UP:
7134                         *selected = *selected - 1;
7135                         if (*selected < 0)
7136                                 *selected = size - 1;
7137                         break;
7139                 case KEY_RIGHT:
7140                 case KEY_DOWN:
7141                         *selected = (*selected + 1) % size;
7142                         break;
7144                 case KEY_ESC:
7145                         status = INPUT_CANCEL;
7146                         break;
7148                 default:
7149                         for (i = 0; items[i].text; i++)
7150                                 if (items[i].hotkey == key) {
7151                                         *selected = i;
7152                                         status = INPUT_STOP;
7153                                         break;
7154                                 }
7155                 }
7156         }
7158         /* Clear the status window */
7159         status_empty = FALSE;
7160         report("");
7162         return status != INPUT_CANCEL;
7165 /*
7166  * Repository properties
7167  */
7169 static struct ref **refs = NULL;
7170 static size_t refs_size = 0;
7172 static struct ref_list **ref_lists = NULL;
7173 static size_t ref_lists_size = 0;
7175 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7176 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7177 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7179 static int
7180 compare_refs(const void *ref1_, const void *ref2_)
7182         const struct ref *ref1 = *(const struct ref **)ref1_;
7183         const struct ref *ref2 = *(const struct ref **)ref2_;
7185         if (ref1->tag != ref2->tag)
7186                 return ref2->tag - ref1->tag;
7187         if (ref1->ltag != ref2->ltag)
7188                 return ref2->ltag - ref2->ltag;
7189         if (ref1->head != ref2->head)
7190                 return ref2->head - ref1->head;
7191         if (ref1->tracked != ref2->tracked)
7192                 return ref2->tracked - ref1->tracked;
7193         if (ref1->remote != ref2->remote)
7194                 return ref2->remote - ref1->remote;
7195         return strcmp(ref1->name, ref2->name);
7198 static void
7199 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7201         size_t i;
7203         for (i = 0; i < refs_size; i++)
7204                 if (!visitor(data, refs[i]))
7205                         break;
7208 static struct ref_list *
7209 get_ref_list(const char *id)
7211         struct ref_list *list;
7212         size_t i;
7214         for (i = 0; i < ref_lists_size; i++)
7215                 if (!strcmp(id, ref_lists[i]->id))
7216                         return ref_lists[i];
7218         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7219                 return NULL;
7220         list = calloc(1, sizeof(*list));
7221         if (!list)
7222                 return NULL;
7224         for (i = 0; i < refs_size; i++) {
7225                 if (!strcmp(id, refs[i]->id) &&
7226                     realloc_refs_list(&list->refs, list->size, 1))
7227                         list->refs[list->size++] = refs[i];
7228         }
7230         if (!list->refs) {
7231                 free(list);
7232                 return NULL;
7233         }
7235         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7236         ref_lists[ref_lists_size++] = list;
7237         return list;
7240 static int
7241 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7243         struct ref *ref = NULL;
7244         bool tag = FALSE;
7245         bool ltag = FALSE;
7246         bool remote = FALSE;
7247         bool tracked = FALSE;
7248         bool head = FALSE;
7249         int from = 0, to = refs_size - 1;
7251         if (!prefixcmp(name, "refs/tags/")) {
7252                 if (!suffixcmp(name, namelen, "^{}")) {
7253                         namelen -= 3;
7254                         name[namelen] = 0;
7255                 } else {
7256                         ltag = TRUE;
7257                 }
7259                 tag = TRUE;
7260                 namelen -= STRING_SIZE("refs/tags/");
7261                 name    += STRING_SIZE("refs/tags/");
7263         } else if (!prefixcmp(name, "refs/remotes/")) {
7264                 remote = TRUE;
7265                 namelen -= STRING_SIZE("refs/remotes/");
7266                 name    += STRING_SIZE("refs/remotes/");
7267                 tracked  = !strcmp(opt_remote, name);
7269         } else if (!prefixcmp(name, "refs/heads/")) {
7270                 namelen -= STRING_SIZE("refs/heads/");
7271                 name    += STRING_SIZE("refs/heads/");
7272                 head     = !strncmp(opt_head, name, namelen);
7274         } else if (!strcmp(name, "HEAD")) {
7275                 string_ncopy(opt_head_rev, id, idlen);
7276                 return OK;
7277         }
7279         /* If we are reloading or it's an annotated tag, replace the
7280          * previous SHA1 with the resolved commit id; relies on the fact
7281          * git-ls-remote lists the commit id of an annotated tag right
7282          * before the commit id it points to. */
7283         while (from <= to) {
7284                 size_t pos = (to + from) / 2;
7285                 int cmp = strcmp(name, refs[pos]->name);
7287                 if (!cmp) {
7288                         ref = refs[pos];
7289                         break;
7290                 }
7292                 if (cmp < 0)
7293                         to = pos - 1;
7294                 else
7295                         from = pos + 1;
7296         }
7298         if (!ref) {
7299                 if (!realloc_refs(&refs, refs_size, 1))
7300                         return ERR;
7301                 ref = calloc(1, sizeof(*ref) + namelen);
7302                 if (!ref)
7303                         return ERR;
7304                 memmove(refs + from + 1, refs + from,
7305                         (refs_size - from) * sizeof(*refs));
7306                 refs[from] = ref;
7307                 strncpy(ref->name, name, namelen);
7308                 refs_size++;
7309         }
7311         ref->head = head;
7312         ref->tag = tag;
7313         ref->ltag = ltag;
7314         ref->remote = remote;
7315         ref->tracked = tracked;
7316         string_copy_rev(ref->id, id);
7318         return OK;
7321 static int
7322 load_refs(void)
7324         const char *head_argv[] = {
7325                 "git", "symbolic-ref", "HEAD", NULL
7326         };
7327         static const char *ls_remote_argv[SIZEOF_ARG] = {
7328                 "git", "ls-remote", opt_git_dir, NULL
7329         };
7330         static bool init = FALSE;
7331         size_t i;
7333         if (!init) {
7334                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7335                 init = TRUE;
7336         }
7338         if (!*opt_git_dir)
7339                 return OK;
7341         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7342             !prefixcmp(opt_head, "refs/heads/")) {
7343                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7345                 memmove(opt_head, offset, strlen(offset) + 1);
7346         }
7348         for (i = 0; i < refs_size; i++)
7349                 refs[i]->id[0] = 0;
7351         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7352                 return ERR;
7354         /* Update the ref lists to reflect changes. */
7355         for (i = 0; i < ref_lists_size; i++) {
7356                 struct ref_list *list = ref_lists[i];
7357                 size_t old, new;
7359                 for (old = new = 0; old < list->size; old++)
7360                         if (!strcmp(list->id, list->refs[old]->id))
7361                                 list->refs[new++] = list->refs[old];
7362                 list->size = new;
7363         }
7365         return OK;
7368 static void
7369 set_remote_branch(const char *name, const char *value, size_t valuelen)
7371         if (!strcmp(name, ".remote")) {
7372                 string_ncopy(opt_remote, value, valuelen);
7374         } else if (*opt_remote && !strcmp(name, ".merge")) {
7375                 size_t from = strlen(opt_remote);
7377                 if (!prefixcmp(value, "refs/heads/"))
7378                         value += STRING_SIZE("refs/heads/");
7380                 if (!string_format_from(opt_remote, &from, "/%s", value))
7381                         opt_remote[0] = 0;
7382         }
7385 static void
7386 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7388         const char *argv[SIZEOF_ARG] = { name, "=" };
7389         int argc = 1 + (cmd == option_set_command);
7390         int error = ERR;
7392         if (!argv_from_string(argv, &argc, value))
7393                 config_msg = "Too many option arguments";
7394         else
7395                 error = cmd(argc, argv);
7397         if (error == ERR)
7398                 warn("Option 'tig.%s': %s", name, config_msg);
7401 static bool
7402 set_environment_variable(const char *name, const char *value)
7404         size_t len = strlen(name) + 1 + strlen(value) + 1;
7405         char *env = malloc(len);
7407         if (env &&
7408             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7409             putenv(env) == 0)
7410                 return TRUE;
7411         free(env);
7412         return FALSE;
7415 static void
7416 set_work_tree(const char *value)
7418         char cwd[SIZEOF_STR];
7420         if (!getcwd(cwd, sizeof(cwd)))
7421                 die("Failed to get cwd path: %s", strerror(errno));
7422         if (chdir(opt_git_dir) < 0)
7423                 die("Failed to chdir(%s): %s", strerror(errno));
7424         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7425                 die("Failed to get git path: %s", strerror(errno));
7426         if (chdir(cwd) < 0)
7427                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7428         if (chdir(value) < 0)
7429                 die("Failed to chdir(%s): %s", value, strerror(errno));
7430         if (!getcwd(cwd, sizeof(cwd)))
7431                 die("Failed to get cwd path: %s", strerror(errno));
7432         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7433                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7434         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7435                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7436         opt_is_inside_work_tree = TRUE;
7439 static int
7440 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7442         if (!strcmp(name, "i18n.commitencoding"))
7443                 string_ncopy(opt_encoding, value, valuelen);
7445         else if (!strcmp(name, "core.editor"))
7446                 string_ncopy(opt_editor, value, valuelen);
7448         else if (!strcmp(name, "core.worktree"))
7449                 set_work_tree(value);
7451         else if (!prefixcmp(name, "tig.color."))
7452                 set_repo_config_option(name + 10, value, option_color_command);
7454         else if (!prefixcmp(name, "tig.bind."))
7455                 set_repo_config_option(name + 9, value, option_bind_command);
7457         else if (!prefixcmp(name, "tig."))
7458                 set_repo_config_option(name + 4, value, option_set_command);
7460         else if (*opt_head && !prefixcmp(name, "branch.") &&
7461                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7462                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7464         return OK;
7467 static int
7468 load_git_config(void)
7470         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7472         return run_io_load(config_list_argv, "=", read_repo_config_option);
7475 static int
7476 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7478         if (!opt_git_dir[0]) {
7479                 string_ncopy(opt_git_dir, name, namelen);
7481         } else if (opt_is_inside_work_tree == -1) {
7482                 /* This can be 3 different values depending on the
7483                  * version of git being used. If git-rev-parse does not
7484                  * understand --is-inside-work-tree it will simply echo
7485                  * the option else either "true" or "false" is printed.
7486                  * Default to true for the unknown case. */
7487                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7489         } else if (*name == '.') {
7490                 string_ncopy(opt_cdup, name, namelen);
7492         } else {
7493                 string_ncopy(opt_prefix, name, namelen);
7494         }
7496         return OK;
7499 static int
7500 load_repo_info(void)
7502         const char *rev_parse_argv[] = {
7503                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7504                         "--show-cdup", "--show-prefix", NULL
7505         };
7507         return run_io_load(rev_parse_argv, "=", read_repo_info);
7511 /*
7512  * Main
7513  */
7515 static const char usage[] =
7516 "tig " TIG_VERSION " (" __DATE__ ")\n"
7517 "\n"
7518 "Usage: tig        [options] [revs] [--] [paths]\n"
7519 "   or: tig show   [options] [revs] [--] [paths]\n"
7520 "   or: tig blame  [rev] path\n"
7521 "   or: tig status\n"
7522 "   or: tig <      [git command output]\n"
7523 "\n"
7524 "Options:\n"
7525 "  -v, --version   Show version and exit\n"
7526 "  -h, --help      Show help message and exit";
7528 static void __NORETURN
7529 quit(int sig)
7531         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7532         if (cursed)
7533                 endwin();
7534         exit(0);
7537 static void __NORETURN
7538 die(const char *err, ...)
7540         va_list args;
7542         endwin();
7544         va_start(args, err);
7545         fputs("tig: ", stderr);
7546         vfprintf(stderr, err, args);
7547         fputs("\n", stderr);
7548         va_end(args);
7550         exit(1);
7553 static void
7554 warn(const char *msg, ...)
7556         va_list args;
7558         va_start(args, msg);
7559         fputs("tig warning: ", stderr);
7560         vfprintf(stderr, msg, args);
7561         fputs("\n", stderr);
7562         va_end(args);
7565 static enum request
7566 parse_options(int argc, const char *argv[])
7568         enum request request = REQ_VIEW_MAIN;
7569         const char *subcommand;
7570         bool seen_dashdash = FALSE;
7571         /* XXX: This is vulnerable to the user overriding options
7572          * required for the main view parser. */
7573         const char *custom_argv[SIZEOF_ARG] = {
7574                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7575                         "--topo-order", NULL
7576         };
7577         int i, j = 6;
7579         if (!isatty(STDIN_FILENO)) {
7580                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7581                 return REQ_VIEW_PAGER;
7582         }
7584         if (argc <= 1)
7585                 return REQ_NONE;
7587         subcommand = argv[1];
7588         if (!strcmp(subcommand, "status")) {
7589                 if (argc > 2)
7590                         warn("ignoring arguments after `%s'", subcommand);
7591                 return REQ_VIEW_STATUS;
7593         } else if (!strcmp(subcommand, "blame")) {
7594                 if (argc <= 2 || argc > 4)
7595                         die("invalid number of options to blame\n\n%s", usage);
7597                 i = 2;
7598                 if (argc == 4) {
7599                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7600                         i++;
7601                 }
7603                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7604                 return REQ_VIEW_BLAME;
7606         } else if (!strcmp(subcommand, "show")) {
7607                 request = REQ_VIEW_DIFF;
7609         } else {
7610                 subcommand = NULL;
7611         }
7613         if (subcommand) {
7614                 custom_argv[1] = subcommand;
7615                 j = 2;
7616         }
7618         for (i = 1 + !!subcommand; i < argc; i++) {
7619                 const char *opt = argv[i];
7621                 if (seen_dashdash || !strcmp(opt, "--")) {
7622                         seen_dashdash = TRUE;
7624                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7625                         printf("tig version %s\n", TIG_VERSION);
7626                         quit(0);
7628                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7629                         printf("%s\n", usage);
7630                         quit(0);
7631                 }
7633                 custom_argv[j++] = opt;
7634                 if (j >= ARRAY_SIZE(custom_argv))
7635                         die("command too long");
7636         }
7638         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7639                 die("Failed to format arguments");
7641         return request;
7644 int
7645 main(int argc, const char *argv[])
7647         enum request request = parse_options(argc, argv);
7648         struct view *view;
7649         size_t i;
7651         signal(SIGINT, quit);
7652         signal(SIGPIPE, SIG_IGN);
7654         if (setlocale(LC_ALL, "")) {
7655                 char *codeset = nl_langinfo(CODESET);
7657                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7658         }
7660         if (load_repo_info() == ERR)
7661                 die("Failed to load repo info.");
7663         if (load_options() == ERR)
7664                 die("Failed to load user config.");
7666         if (load_git_config() == ERR)
7667                 die("Failed to load repo config.");
7669         /* Require a git repository unless when running in pager mode. */
7670         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7671                 die("Not a git repository");
7673         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7674                 opt_utf8 = FALSE;
7676         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7677                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7678                 if (opt_iconv == ICONV_NONE)
7679                         die("Failed to initialize character set conversion");
7680         }
7682         if (load_refs() == ERR)
7683                 die("Failed to load refs.");
7685         foreach_view (view, i)
7686                 argv_from_env(view->ops->argv, view->cmd_env);
7688         init_display();
7690         if (request != REQ_NONE)
7691                 open_view(NULL, request, OPEN_PREPARED);
7692         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7694         while (view_driver(display[current_view], request)) {
7695                 int key = get_input(0);
7697                 view = display[current_view];
7698                 request = get_keybinding(view->keymap, key);
7700                 /* Some low-level request handling. This keeps access to
7701                  * status_win restricted. */
7702                 switch (request) {
7703                 case REQ_PROMPT:
7704                 {
7705                         char *cmd = read_prompt(":");
7707                         if (cmd && isdigit(*cmd)) {
7708                                 int lineno = view->lineno + 1;
7710                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7711                                         select_view_line(view, lineno - 1);
7712                                         report("");
7713                                 } else {
7714                                         report("Unable to parse '%s' as a line number", cmd);
7715                                 }
7717                         } else if (cmd) {
7718                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7719                                 const char *argv[SIZEOF_ARG] = { "git" };
7720                                 int argc = 1;
7722                                 /* When running random commands, initially show the
7723                                  * command in the title. However, it maybe later be
7724                                  * overwritten if a commit line is selected. */
7725                                 string_ncopy(next->ref, cmd, strlen(cmd));
7727                                 if (!argv_from_string(argv, &argc, cmd)) {
7728                                         report("Too many arguments");
7729                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7730                                         report("Failed to format command");
7731                                 } else {
7732                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7733                                 }
7734                         }
7736                         request = REQ_NONE;
7737                         break;
7738                 }
7739                 case REQ_SEARCH:
7740                 case REQ_SEARCH_BACK:
7741                 {
7742                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7743                         char *search = read_prompt(prompt);
7745                         if (search)
7746                                 string_ncopy(opt_search, search, strlen(search));
7747                         else if (*opt_search)
7748                                 request = request == REQ_SEARCH ?
7749                                         REQ_FIND_NEXT :
7750                                         REQ_FIND_PREV;
7751                         else
7752                                 request = REQ_NONE;
7753                         break;
7754                 }
7755                 default:
7756                         break;
7757                 }
7758         }
7760         quit(0);
7762         return 0;