Code

86c82db53ed679a284c8b385cd566f963036cea5
[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 /* Some ASCII-shorthands fitted into the ncurses namespace. */
118 #define KEY_TAB         '\t'
119 #define KEY_RETURN      '\r'
120 #define KEY_ESC         27
123 struct ref {
124         char id[SIZEOF_REV];    /* Commit SHA1 ID */
125         unsigned int head:1;    /* Is it the current HEAD? */
126         unsigned int tag:1;     /* Is it a tag? */
127         unsigned int ltag:1;    /* If so, is the tag local? */
128         unsigned int remote:1;  /* Is it a remote ref? */
129         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130         char name[1];           /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134         char id[SIZEOF_REV];    /* Commit SHA1 ID */
135         size_t size;            /* Number of refs. */
136         struct ref **refs;      /* References for this ID. */
137 };
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152         INPUT_OK,
153         INPUT_SKIP,
154         INPUT_STOP,
155         INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164         int hotkey;
165         const char *text;
166         void *data;
167 };
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 /*
172  * Allocation helpers ... Entering macro hell to never be seen again.
173  */
175 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
176 static type *                                                                   \
177 name(type **mem, size_t size, size_t increase)                                  \
178 {                                                                               \
179         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
180         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181         type *tmp = *mem;                                                       \
182                                                                                 \
183         if (mem == NULL || num_chunks != num_chunks_new) {                      \
184                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185                 if (tmp)                                                        \
186                         *mem = tmp;                                             \
187         }                                                                       \
188                                                                                 \
189         return tmp;                                                             \
192 /*
193  * String helpers
194  */
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199         if (srclen > dstlen - 1)
200                 srclen = dstlen - 1;
202         strncpy(dst, src, srclen);
203         dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212         string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223         size_t size, pos;
225         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226                 if (src[pos] == '\t') {
227                         size_t expanded = tabsize - (size % tabsize);
229                         if (expanded + size >= dstlen - 1)
230                                 expanded = dstlen - size - 1;
231                         memcpy(dst + size, "        ", expanded);
232                         size += expanded;
233                 } else {
234                         dst[size++] = src[pos];
235                 }
236         }
238         dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244         int namelen;
246         while (isspace(*name))
247                 name++;
249         namelen = strlen(name) - 1;
250         while (namelen > 0 && isspace(name[namelen]))
251                 name[namelen--] = 0;
253         return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259         va_list args;
260         size_t pos = bufpos ? *bufpos : 0;
262         va_start(args, fmt);
263         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264         va_end(args);
266         if (bufpos)
267                 *bufpos = pos;
269         return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273         string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276         string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281         size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285         /* Diff-Header == DIFF_HEADER */
286         for (i = 0; i < len; i++) {
287                 if (toupper(str1[i]) == toupper(str2[i]))
288                         continue;
290                 if (string_enum_sep(str1[i]) &&
291                     string_enum_sep(str2[i]))
292                         continue;
294                 return str1[i] - str2[i];
295         }
297         return 0;
300 struct enum_map {
301         const char *name;
302         int namelen;
303         int value;
304 };
306 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
308 static bool
309 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
311         size_t namelen = strlen(name);
312         int i;
314         for (i = 0; i < map_size; i++)
315                 if (namelen == map[i].namelen &&
316                     !string_enum_compare(name, map[i].name, namelen)) {
317                         *value = map[i].value;
318                         return TRUE;
319                 }
321         return FALSE;
324 #define map_enum(attr, map, name) \
325         map_enum_do(map, ARRAY_SIZE(map), attr, name)
327 #define prefixcmp(str1, str2) \
328         strncmp(str1, str2, STRING_SIZE(str2))
330 static inline int
331 suffixcmp(const char *str, int slen, const char *suffix)
333         size_t len = slen >= 0 ? slen : strlen(str);
334         size_t suffixlen = strlen(suffix);
336         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
340 /*
341  * What value of "tz" was in effect back then at "time" in the
342  * local timezone?
343  */
344 static int local_tzoffset(time_t time)
346         time_t t, t_local;
347         struct tm tm;
348         int offset, eastwest; 
350         t = time;
351         localtime_r(&t, &tm);
352         t_local = mktime(&tm);
354         if (t_local < t) {
355                 eastwest = -1;
356                 offset = t - t_local;
357         } else {
358                 eastwest = 1;
359                 offset = t_local - t;
360         }
361         offset /= 60; /* in minutes */
362         offset = (offset % 60) + ((offset / 60) * 100);
363         return offset * eastwest;
366 enum date {
367         DATE_NONE = 0,
368         DATE_DEFAULT,
369         DATE_RELATIVE,
370         DATE_SHORT
371 };
373 static char *
374 string_date(const time_t *time, enum date date)
376         static char buf[DATE_COLS + 1];
377         static const struct enum_map reldate[] = {
378                 { "second", 1,                  60 * 2 },
379                 { "minute", 60,                 60 * 60 * 2 },
380                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
381                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
382                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
383                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
384         };
385         struct tm tm;
387         if (date == DATE_RELATIVE) {
388                 struct timeval now;
389                 time_t date = *time + local_tzoffset(*time);
390                 time_t seconds;
391                 int i;
393                 gettimeofday(&now, NULL);
394                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
395                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
396                         if (seconds >= reldate[i].value)
397                                 continue;
399                         seconds /= reldate[i].namelen;
400                         if (!string_format(buf, "%ld %s%s %s",
401                                            seconds, reldate[i].name,
402                                            seconds > 1 ? "s" : "",
403                                            now.tv_sec >= date ? "ago" : "ahead"))
404                                 break;
405                         return buf;
406                 }
407         }
409         gmtime_r(time, &tm);
410         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
414 static bool
415 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
417         int valuelen;
419         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
420                 bool advance = cmd[valuelen] != 0;
422                 cmd[valuelen] = 0;
423                 argv[(*argc)++] = chomp_string(cmd);
424                 cmd = chomp_string(cmd + valuelen + advance);
425         }
427         if (*argc < SIZEOF_ARG)
428                 argv[*argc] = NULL;
429         return *argc < SIZEOF_ARG;
432 static void
433 argv_from_env(const char **argv, const char *name)
435         char *env = argv ? getenv(name) : NULL;
436         int argc = 0;
438         if (env && *env)
439                 env = strdup(env);
440         if (env && !argv_from_string(argv, &argc, env))
441                 die("Too many arguments in the `%s` environment variable", name);
445 /*
446  * Executing external commands.
447  */
449 enum io_type {
450         IO_FD,                  /* File descriptor based IO. */
451         IO_BG,                  /* Execute command in the background. */
452         IO_FG,                  /* Execute command with same std{in,out,err}. */
453         IO_RD,                  /* Read only fork+exec IO. */
454         IO_WR,                  /* Write only fork+exec IO. */
455         IO_AP,                  /* Append fork+exec output to file. */
456 };
458 struct io {
459         enum io_type type;      /* The requested type of pipe. */
460         const char *dir;        /* Directory from which to execute. */
461         pid_t pid;              /* Pipe for reading or writing. */
462         int pipe;               /* Pipe end for reading or writing. */
463         int error;              /* Error status. */
464         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
465         char *buf;              /* Read buffer. */
466         size_t bufalloc;        /* Allocated buffer size. */
467         size_t bufsize;         /* Buffer content size. */
468         char *bufpos;           /* Current buffer position. */
469         unsigned int eof:1;     /* Has end of file been reached. */
470 };
472 static void
473 reset_io(struct io *io)
475         io->pipe = -1;
476         io->pid = 0;
477         io->buf = io->bufpos = NULL;
478         io->bufalloc = io->bufsize = 0;
479         io->error = 0;
480         io->eof = 0;
483 static void
484 init_io(struct io *io, const char *dir, enum io_type type)
486         reset_io(io);
487         io->type = type;
488         io->dir = dir;
491 static bool
492 init_io_rd(struct io *io, const char *argv[], const char *dir,
493                 enum format_flags flags)
495         init_io(io, dir, IO_RD);
496         return format_argv(io->argv, argv, flags);
499 static bool
500 io_open(struct io *io, const char *fmt, ...)
502         char name[SIZEOF_STR] = "";
503         bool fits;
504         va_list args;
506         init_io(io, NULL, IO_FD);
508         va_start(args, fmt);
509         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
510         va_end(args);
512         if (!fits) {
513                 io->error = ENAMETOOLONG;
514                 return FALSE;
515         }
516         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
517         if (io->pipe == -1)
518                 io->error = errno;
519         return io->pipe != -1;
522 static bool
523 kill_io(struct io *io)
525         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
528 static bool
529 done_io(struct io *io)
531         pid_t pid = io->pid;
533         if (io->pipe != -1)
534                 close(io->pipe);
535         free(io->buf);
536         reset_io(io);
538         while (pid > 0) {
539                 int status;
540                 pid_t waiting = waitpid(pid, &status, 0);
542                 if (waiting < 0) {
543                         if (errno == EINTR)
544                                 continue;
545                         report("waitpid failed (%s)", strerror(errno));
546                         return FALSE;
547                 }
549                 return waiting == pid &&
550                        !WIFSIGNALED(status) &&
551                        WIFEXITED(status) &&
552                        !WEXITSTATUS(status);
553         }
555         return TRUE;
558 static bool
559 start_io(struct io *io)
561         int pipefds[2] = { -1, -1 };
563         if (io->type == IO_FD)
564                 return TRUE;
566         if ((io->type == IO_RD || io->type == IO_WR) &&
567             pipe(pipefds) < 0)
568                 return FALSE;
569         else if (io->type == IO_AP)
570                 pipefds[1] = io->pipe;
572         if ((io->pid = fork())) {
573                 if (pipefds[!(io->type == IO_WR)] != -1)
574                         close(pipefds[!(io->type == IO_WR)]);
575                 if (io->pid != -1) {
576                         io->pipe = pipefds[!!(io->type == IO_WR)];
577                         return TRUE;
578                 }
580         } else {
581                 if (io->type != IO_FG) {
582                         int devnull = open("/dev/null", O_RDWR);
583                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
584                         int writefd = (io->type == IO_RD || io->type == IO_AP)
585                                                         ? pipefds[1] : devnull;
587                         dup2(readfd,  STDIN_FILENO);
588                         dup2(writefd, STDOUT_FILENO);
589                         dup2(devnull, STDERR_FILENO);
591                         close(devnull);
592                         if (pipefds[0] != -1)
593                                 close(pipefds[0]);
594                         if (pipefds[1] != -1)
595                                 close(pipefds[1]);
596                 }
598                 if (io->dir && *io->dir && chdir(io->dir) == -1)
599                         die("Failed to change directory: %s", strerror(errno));
601                 execvp(io->argv[0], (char *const*) io->argv);
602                 die("Failed to execute program: %s", strerror(errno));
603         }
605         if (pipefds[!!(io->type == IO_WR)] != -1)
606                 close(pipefds[!!(io->type == IO_WR)]);
607         return FALSE;
610 static bool
611 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
613         init_io(io, dir, type);
614         if (!format_argv(io->argv, argv, FORMAT_NONE))
615                 return FALSE;
616         return start_io(io);
619 static int
620 run_io_do(struct io *io)
622         return start_io(io) && done_io(io);
625 static int
626 run_io_bg(const char **argv)
628         struct io io = {};
630         init_io(&io, NULL, IO_BG);
631         if (!format_argv(io.argv, argv, FORMAT_NONE))
632                 return FALSE;
633         return run_io_do(&io);
636 static bool
637 run_io_fg(const char **argv, const char *dir)
639         struct io io = {};
641         init_io(&io, dir, IO_FG);
642         if (!format_argv(io.argv, argv, FORMAT_NONE))
643                 return FALSE;
644         return run_io_do(&io);
647 static bool
648 run_io_append(const char **argv, enum format_flags flags, int fd)
650         struct io io = {};
652         init_io(&io, NULL, IO_AP);
653         io.pipe = fd;
654         if (format_argv(io.argv, argv, flags))
655                 return run_io_do(&io);
656         close(fd);
657         return FALSE;
660 static bool
661 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
663         return init_io_rd(io, argv, dir, flags) && start_io(io);
666 static bool
667 io_eof(struct io *io)
669         return io->eof;
672 static int
673 io_error(struct io *io)
675         return io->error;
678 static char *
679 io_strerror(struct io *io)
681         return strerror(io->error);
684 static bool
685 io_can_read(struct io *io)
687         struct timeval tv = { 0, 500 };
688         fd_set fds;
690         FD_ZERO(&fds);
691         FD_SET(io->pipe, &fds);
693         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
696 static ssize_t
697 io_read(struct io *io, void *buf, size_t bufsize)
699         do {
700                 ssize_t readsize = read(io->pipe, buf, bufsize);
702                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
703                         continue;
704                 else if (readsize == -1)
705                         io->error = errno;
706                 else if (readsize == 0)
707                         io->eof = 1;
708                 return readsize;
709         } while (1);
712 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
714 static char *
715 io_get(struct io *io, int c, bool can_read)
717         char *eol;
718         ssize_t readsize;
720         while (TRUE) {
721                 if (io->bufsize > 0) {
722                         eol = memchr(io->bufpos, c, io->bufsize);
723                         if (eol) {
724                                 char *line = io->bufpos;
726                                 *eol = 0;
727                                 io->bufpos = eol + 1;
728                                 io->bufsize -= io->bufpos - line;
729                                 return line;
730                         }
731                 }
733                 if (io_eof(io)) {
734                         if (io->bufsize) {
735                                 io->bufpos[io->bufsize] = 0;
736                                 io->bufsize = 0;
737                                 return io->bufpos;
738                         }
739                         return NULL;
740                 }
742                 if (!can_read)
743                         return NULL;
745                 if (io->bufsize > 0 && io->bufpos > io->buf)
746                         memmove(io->buf, io->bufpos, io->bufsize);
748                 if (io->bufalloc == io->bufsize) {
749                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
750                                 return NULL;
751                         io->bufalloc += BUFSIZ;
752                 }
754                 io->bufpos = io->buf;
755                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
756                 if (io_error(io))
757                         return NULL;
758                 io->bufsize += readsize;
759         }
762 static bool
763 io_write(struct io *io, const void *buf, size_t bufsize)
765         size_t written = 0;
767         while (!io_error(io) && written < bufsize) {
768                 ssize_t size;
770                 size = write(io->pipe, buf + written, bufsize - written);
771                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
772                         continue;
773                 else if (size == -1)
774                         io->error = errno;
775                 else
776                         written += size;
777         }
779         return written == bufsize;
782 static bool
783 io_read_buf(struct io *io, char buf[], size_t bufsize)
785         char *result = io_get(io, '\n', TRUE);
787         if (result) {
788                 result = chomp_string(result);
789                 string_ncopy_do(buf, bufsize, result, strlen(result));
790         }
792         return done_io(io) && result;
795 static bool
796 run_io_buf(const char **argv, char buf[], size_t bufsize)
798         struct io io = {};
800         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
801             && io_read_buf(&io, buf, bufsize);
804 static int
805 io_load(struct io *io, const char *separators,
806         int (*read_property)(char *, size_t, char *, size_t))
808         char *name;
809         int state = OK;
811         if (!start_io(io))
812                 return ERR;
814         while (state == OK && (name = io_get(io, '\n', TRUE))) {
815                 char *value;
816                 size_t namelen;
817                 size_t valuelen;
819                 name = chomp_string(name);
820                 namelen = strcspn(name, separators);
822                 if (name[namelen]) {
823                         name[namelen] = 0;
824                         value = chomp_string(name + namelen + 1);
825                         valuelen = strlen(value);
827                 } else {
828                         value = "";
829                         valuelen = 0;
830                 }
832                 state = read_property(name, namelen, value, valuelen);
833         }
835         if (state != ERR && io_error(io))
836                 state = ERR;
837         done_io(io);
839         return state;
842 static int
843 run_io_load(const char **argv, const char *separators,
844             int (*read_property)(char *, size_t, char *, size_t))
846         struct io io = {};
848         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
849                 ? io_load(&io, separators, read_property) : ERR;
853 /*
854  * User requests
855  */
857 #define REQ_INFO \
858         /* XXX: Keep the view request first and in sync with views[]. */ \
859         REQ_GROUP("View switching") \
860         REQ_(VIEW_MAIN,         "Show main view"), \
861         REQ_(VIEW_DIFF,         "Show diff view"), \
862         REQ_(VIEW_LOG,          "Show log view"), \
863         REQ_(VIEW_TREE,         "Show tree view"), \
864         REQ_(VIEW_BLOB,         "Show blob view"), \
865         REQ_(VIEW_BLAME,        "Show blame view"), \
866         REQ_(VIEW_BRANCH,       "Show branch view"), \
867         REQ_(VIEW_HELP,         "Show help page"), \
868         REQ_(VIEW_PAGER,        "Show pager view"), \
869         REQ_(VIEW_STATUS,       "Show status view"), \
870         REQ_(VIEW_STAGE,        "Show stage view"), \
871         \
872         REQ_GROUP("View manipulation") \
873         REQ_(ENTER,             "Enter current line and scroll"), \
874         REQ_(NEXT,              "Move to next"), \
875         REQ_(PREVIOUS,          "Move to previous"), \
876         REQ_(PARENT,            "Move to parent"), \
877         REQ_(VIEW_NEXT,         "Move focus to next view"), \
878         REQ_(REFRESH,           "Reload and refresh"), \
879         REQ_(MAXIMIZE,          "Maximize the current view"), \
880         REQ_(VIEW_CLOSE,        "Close the current view"), \
881         REQ_(QUIT,              "Close all views and quit"), \
882         \
883         REQ_GROUP("View specific requests") \
884         REQ_(STATUS_UPDATE,     "Update file status"), \
885         REQ_(STATUS_REVERT,     "Revert file changes"), \
886         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
887         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
888         \
889         REQ_GROUP("Cursor navigation") \
890         REQ_(MOVE_UP,           "Move cursor one line up"), \
891         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
892         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
893         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
894         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
895         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
896         \
897         REQ_GROUP("Scrolling") \
898         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
899         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
900         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
901         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
902         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
903         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
904         \
905         REQ_GROUP("Searching") \
906         REQ_(SEARCH,            "Search the view"), \
907         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
908         REQ_(FIND_NEXT,         "Find next search match"), \
909         REQ_(FIND_PREV,         "Find previous search match"), \
910         \
911         REQ_GROUP("Option manipulation") \
912         REQ_(OPTIONS,           "Open option menu"), \
913         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
914         REQ_(TOGGLE_DATE,       "Toggle date display"), \
915         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
916         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
917         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
918         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
919         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
920         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
921         \
922         REQ_GROUP("Misc") \
923         REQ_(PROMPT,            "Bring up the prompt"), \
924         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
925         REQ_(SHOW_VERSION,      "Show version information"), \
926         REQ_(STOP_LOADING,      "Stop all loading views"), \
927         REQ_(EDIT,              "Open in editor"), \
928         REQ_(NONE,              "Do nothing")
931 /* User action requests. */
932 enum request {
933 #define REQ_GROUP(help)
934 #define REQ_(req, help) REQ_##req
936         /* Offset all requests to avoid conflicts with ncurses getch values. */
937         REQ_OFFSET = KEY_MAX + 1,
938         REQ_INFO
940 #undef  REQ_GROUP
941 #undef  REQ_
942 };
944 struct request_info {
945         enum request request;
946         const char *name;
947         int namelen;
948         const char *help;
949 };
951 static const struct request_info req_info[] = {
952 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
953 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
954         REQ_INFO
955 #undef  REQ_GROUP
956 #undef  REQ_
957 };
959 static enum request
960 get_request(const char *name)
962         int namelen = strlen(name);
963         int i;
965         for (i = 0; i < ARRAY_SIZE(req_info); i++)
966                 if (req_info[i].namelen == namelen &&
967                     !string_enum_compare(req_info[i].name, name, namelen))
968                         return req_info[i].request;
970         return REQ_NONE;
974 /*
975  * Options
976  */
978 /* Option and state variables. */
979 static enum date opt_date               = DATE_DEFAULT;
980 static bool opt_author                  = TRUE;
981 static bool opt_line_number             = FALSE;
982 static bool opt_line_graphics           = TRUE;
983 static bool opt_rev_graph               = FALSE;
984 static bool opt_show_refs               = TRUE;
985 static int opt_num_interval             = 5;
986 static double opt_hscroll               = 0.50;
987 static double opt_scale_split_view      = 2.0 / 3.0;
988 static int opt_tab_size                 = 8;
989 static int opt_author_cols              = 19;
990 static char opt_path[SIZEOF_STR]        = "";
991 static char opt_file[SIZEOF_STR]        = "";
992 static char opt_ref[SIZEOF_REF]         = "";
993 static char opt_head[SIZEOF_REF]        = "";
994 static char opt_head_rev[SIZEOF_REV]    = "";
995 static char opt_remote[SIZEOF_REF]      = "";
996 static char opt_encoding[20]            = "UTF-8";
997 static bool opt_utf8                    = TRUE;
998 static char opt_codeset[20]             = "UTF-8";
999 static iconv_t opt_iconv                = ICONV_NONE;
1000 static char opt_search[SIZEOF_STR]      = "";
1001 static char opt_cdup[SIZEOF_STR]        = "";
1002 static char opt_prefix[SIZEOF_STR]      = "";
1003 static char opt_git_dir[SIZEOF_STR]     = "";
1004 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1005 static char opt_editor[SIZEOF_STR]      = "";
1006 static FILE *opt_tty                    = NULL;
1008 #define is_initial_commit()     (!*opt_head_rev)
1009 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1010 #define mkdate(time)            string_date(time, opt_date)
1013 /*
1014  * Line-oriented content detection.
1015  */
1017 #define LINE_INFO \
1018 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1019 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1020 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1021 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1022 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1023 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1024 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1025 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1026 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1027 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1028 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1029 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1030 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1031 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1032 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1033 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1034 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1035 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1036 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1037 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1038 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1039 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1040 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1041 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1042 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1043 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1044 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1045 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1046 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1047 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1048 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1049 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1050 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1051 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1052 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1053 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1054 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1055 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1056 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1057 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1058 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1059 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1060 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1061 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1062 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1063 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1064 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1065 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1066 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1067 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1068 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1069 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1070 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1071 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1072 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1073 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1074 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1076 enum line_type {
1077 #define LINE(type, line, fg, bg, attr) \
1078         LINE_##type
1079         LINE_INFO,
1080         LINE_NONE
1081 #undef  LINE
1082 };
1084 struct line_info {
1085         const char *name;       /* Option name. */
1086         int namelen;            /* Size of option name. */
1087         const char *line;       /* The start of line to match. */
1088         int linelen;            /* Size of string to match. */
1089         int fg, bg, attr;       /* Color and text attributes for the lines. */
1090 };
1092 static struct line_info line_info[] = {
1093 #define LINE(type, line, fg, bg, attr) \
1094         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1095         LINE_INFO
1096 #undef  LINE
1097 };
1099 static enum line_type
1100 get_line_type(const char *line)
1102         int linelen = strlen(line);
1103         enum line_type type;
1105         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1106                 /* Case insensitive search matches Signed-off-by lines better. */
1107                 if (linelen >= line_info[type].linelen &&
1108                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1109                         return type;
1111         return LINE_DEFAULT;
1114 static inline int
1115 get_line_attr(enum line_type type)
1117         assert(type < ARRAY_SIZE(line_info));
1118         return COLOR_PAIR(type) | line_info[type].attr;
1121 static struct line_info *
1122 get_line_info(const char *name)
1124         size_t namelen = strlen(name);
1125         enum line_type type;
1127         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1128                 if (namelen == line_info[type].namelen &&
1129                     !string_enum_compare(line_info[type].name, name, namelen))
1130                         return &line_info[type];
1132         return NULL;
1135 static void
1136 init_colors(void)
1138         int default_bg = line_info[LINE_DEFAULT].bg;
1139         int default_fg = line_info[LINE_DEFAULT].fg;
1140         enum line_type type;
1142         start_color();
1144         if (assume_default_colors(default_fg, default_bg) == ERR) {
1145                 default_bg = COLOR_BLACK;
1146                 default_fg = COLOR_WHITE;
1147         }
1149         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1150                 struct line_info *info = &line_info[type];
1151                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1152                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1154                 init_pair(type, fg, bg);
1155         }
1158 struct line {
1159         enum line_type type;
1161         /* State flags */
1162         unsigned int selected:1;
1163         unsigned int dirty:1;
1164         unsigned int cleareol:1;
1165         unsigned int other:16;
1167         void *data;             /* User data */
1168 };
1171 /*
1172  * Keys
1173  */
1175 struct keybinding {
1176         int alias;
1177         enum request request;
1178 };
1180 static const struct keybinding default_keybindings[] = {
1181         /* View switching */
1182         { 'm',          REQ_VIEW_MAIN },
1183         { 'd',          REQ_VIEW_DIFF },
1184         { 'l',          REQ_VIEW_LOG },
1185         { 't',          REQ_VIEW_TREE },
1186         { 'f',          REQ_VIEW_BLOB },
1187         { 'B',          REQ_VIEW_BLAME },
1188         { 'H',          REQ_VIEW_BRANCH },
1189         { 'p',          REQ_VIEW_PAGER },
1190         { 'h',          REQ_VIEW_HELP },
1191         { 'S',          REQ_VIEW_STATUS },
1192         { 'c',          REQ_VIEW_STAGE },
1194         /* View manipulation */
1195         { 'q',          REQ_VIEW_CLOSE },
1196         { KEY_TAB,      REQ_VIEW_NEXT },
1197         { KEY_RETURN,   REQ_ENTER },
1198         { KEY_UP,       REQ_PREVIOUS },
1199         { KEY_DOWN,     REQ_NEXT },
1200         { 'R',          REQ_REFRESH },
1201         { KEY_F(5),     REQ_REFRESH },
1202         { 'O',          REQ_MAXIMIZE },
1204         /* Cursor navigation */
1205         { 'k',          REQ_MOVE_UP },
1206         { 'j',          REQ_MOVE_DOWN },
1207         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1208         { KEY_END,      REQ_MOVE_LAST_LINE },
1209         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1210         { ' ',          REQ_MOVE_PAGE_DOWN },
1211         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1212         { 'b',          REQ_MOVE_PAGE_UP },
1213         { '-',          REQ_MOVE_PAGE_UP },
1215         /* Scrolling */
1216         { KEY_LEFT,     REQ_SCROLL_LEFT },
1217         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1218         { KEY_IC,       REQ_SCROLL_LINE_UP },
1219         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1220         { 'w',          REQ_SCROLL_PAGE_UP },
1221         { 's',          REQ_SCROLL_PAGE_DOWN },
1223         /* Searching */
1224         { '/',          REQ_SEARCH },
1225         { '?',          REQ_SEARCH_BACK },
1226         { 'n',          REQ_FIND_NEXT },
1227         { 'N',          REQ_FIND_PREV },
1229         /* Misc */
1230         { 'Q',          REQ_QUIT },
1231         { 'z',          REQ_STOP_LOADING },
1232         { 'v',          REQ_SHOW_VERSION },
1233         { 'r',          REQ_SCREEN_REDRAW },
1234         { 'o',          REQ_OPTIONS },
1235         { '.',          REQ_TOGGLE_LINENO },
1236         { 'D',          REQ_TOGGLE_DATE },
1237         { 'A',          REQ_TOGGLE_AUTHOR },
1238         { 'g',          REQ_TOGGLE_REV_GRAPH },
1239         { 'F',          REQ_TOGGLE_REFS },
1240         { 'I',          REQ_TOGGLE_SORT_ORDER },
1241         { 'i',          REQ_TOGGLE_SORT_FIELD },
1242         { ':',          REQ_PROMPT },
1243         { 'u',          REQ_STATUS_UPDATE },
1244         { '!',          REQ_STATUS_REVERT },
1245         { 'M',          REQ_STATUS_MERGE },
1246         { '@',          REQ_STAGE_NEXT },
1247         { ',',          REQ_PARENT },
1248         { 'e',          REQ_EDIT },
1249 };
1251 #define KEYMAP_INFO \
1252         KEYMAP_(GENERIC), \
1253         KEYMAP_(MAIN), \
1254         KEYMAP_(DIFF), \
1255         KEYMAP_(LOG), \
1256         KEYMAP_(TREE), \
1257         KEYMAP_(BLOB), \
1258         KEYMAP_(BLAME), \
1259         KEYMAP_(BRANCH), \
1260         KEYMAP_(PAGER), \
1261         KEYMAP_(HELP), \
1262         KEYMAP_(STATUS), \
1263         KEYMAP_(STAGE)
1265 enum keymap {
1266 #define KEYMAP_(name) KEYMAP_##name
1267         KEYMAP_INFO
1268 #undef  KEYMAP_
1269 };
1271 static const struct enum_map keymap_table[] = {
1272 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1273         KEYMAP_INFO
1274 #undef  KEYMAP_
1275 };
1277 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1279 struct keybinding_table {
1280         struct keybinding *data;
1281         size_t size;
1282 };
1284 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1286 static void
1287 add_keybinding(enum keymap keymap, enum request request, int key)
1289         struct keybinding_table *table = &keybindings[keymap];
1291         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1292         if (!table->data)
1293                 die("Failed to allocate keybinding");
1294         table->data[table->size].alias = key;
1295         table->data[table->size++].request = request;
1298 /* Looks for a key binding first in the given map, then in the generic map, and
1299  * lastly in the default keybindings. */
1300 static enum request
1301 get_keybinding(enum keymap keymap, int key)
1303         size_t i;
1305         for (i = 0; i < keybindings[keymap].size; i++)
1306                 if (keybindings[keymap].data[i].alias == key)
1307                         return keybindings[keymap].data[i].request;
1309         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1310                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1311                         return keybindings[KEYMAP_GENERIC].data[i].request;
1313         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1314                 if (default_keybindings[i].alias == key)
1315                         return default_keybindings[i].request;
1317         return (enum request) key;
1321 struct key {
1322         const char *name;
1323         int value;
1324 };
1326 static const struct key key_table[] = {
1327         { "Enter",      KEY_RETURN },
1328         { "Space",      ' ' },
1329         { "Backspace",  KEY_BACKSPACE },
1330         { "Tab",        KEY_TAB },
1331         { "Escape",     KEY_ESC },
1332         { "Left",       KEY_LEFT },
1333         { "Right",      KEY_RIGHT },
1334         { "Up",         KEY_UP },
1335         { "Down",       KEY_DOWN },
1336         { "Insert",     KEY_IC },
1337         { "Delete",     KEY_DC },
1338         { "Hash",       '#' },
1339         { "Home",       KEY_HOME },
1340         { "End",        KEY_END },
1341         { "PageUp",     KEY_PPAGE },
1342         { "PageDown",   KEY_NPAGE },
1343         { "F1",         KEY_F(1) },
1344         { "F2",         KEY_F(2) },
1345         { "F3",         KEY_F(3) },
1346         { "F4",         KEY_F(4) },
1347         { "F5",         KEY_F(5) },
1348         { "F6",         KEY_F(6) },
1349         { "F7",         KEY_F(7) },
1350         { "F8",         KEY_F(8) },
1351         { "F9",         KEY_F(9) },
1352         { "F10",        KEY_F(10) },
1353         { "F11",        KEY_F(11) },
1354         { "F12",        KEY_F(12) },
1355 };
1357 static int
1358 get_key_value(const char *name)
1360         int i;
1362         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1363                 if (!strcasecmp(key_table[i].name, name))
1364                         return key_table[i].value;
1366         if (strlen(name) == 1 && isprint(*name))
1367                 return (int) *name;
1369         return ERR;
1372 static const char *
1373 get_key_name(int key_value)
1375         static char key_char[] = "'X'";
1376         const char *seq = NULL;
1377         int key;
1379         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1380                 if (key_table[key].value == key_value)
1381                         seq = key_table[key].name;
1383         if (seq == NULL &&
1384             key_value < 127 &&
1385             isprint(key_value)) {
1386                 key_char[1] = (char) key_value;
1387                 seq = key_char;
1388         }
1390         return seq ? seq : "(no key)";
1393 static bool
1394 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1396         const char *sep = *pos > 0 ? ", " : "";
1397         const char *keyname = get_key_name(keybinding->alias);
1399         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1402 static bool
1403 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1404                            enum keymap keymap, bool all)
1406         int i;
1408         for (i = 0; i < keybindings[keymap].size; i++) {
1409                 if (keybindings[keymap].data[i].request == request) {
1410                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1411                                 return FALSE;
1412                         if (!all)
1413                                 break;
1414                 }
1415         }
1417         return TRUE;
1420 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1422 static const char *
1423 get_keys(enum keymap keymap, enum request request, bool all)
1425         static char buf[BUFSIZ];
1426         size_t pos = 0;
1427         int i;
1429         buf[pos] = 0;
1431         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1432                 return "Too many keybindings!";
1433         if (pos > 0 && !all)
1434                 return buf;
1436         if (keymap != KEYMAP_GENERIC) {
1437                 /* Only the generic keymap includes the default keybindings when
1438                  * listing all keys. */
1439                 if (all)
1440                         return buf;
1442                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1443                         return "Too many keybindings!";
1444                 if (pos)
1445                         return buf;
1446         }
1448         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1449                 if (default_keybindings[i].request == request) {
1450                         if (!append_key(buf, &pos, &default_keybindings[i]))
1451                                 return "Too many keybindings!";
1452                         if (!all)
1453                                 return buf;
1454                 }
1455         }
1457         return buf;
1460 struct run_request {
1461         enum keymap keymap;
1462         int key;
1463         const char *argv[SIZEOF_ARG];
1464 };
1466 static struct run_request *run_request;
1467 static size_t run_requests;
1469 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1471 static enum request
1472 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1474         struct run_request *req;
1476         if (argc >= ARRAY_SIZE(req->argv) - 1)
1477                 return REQ_NONE;
1479         if (!realloc_run_requests(&run_request, run_requests, 1))
1480                 return REQ_NONE;
1482         req = &run_request[run_requests];
1483         req->keymap = keymap;
1484         req->key = key;
1485         req->argv[0] = NULL;
1487         if (!format_argv(req->argv, argv, FORMAT_NONE))
1488                 return REQ_NONE;
1490         return REQ_NONE + ++run_requests;
1493 static struct run_request *
1494 get_run_request(enum request request)
1496         if (request <= REQ_NONE)
1497                 return NULL;
1498         return &run_request[request - REQ_NONE - 1];
1501 static void
1502 add_builtin_run_requests(void)
1504         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1505         const char *commit[] = { "git", "commit", NULL };
1506         const char *gc[] = { "git", "gc", NULL };
1507         struct {
1508                 enum keymap keymap;
1509                 int key;
1510                 int argc;
1511                 const char **argv;
1512         } reqs[] = {
1513                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1514                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1515                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1516         };
1517         int i;
1519         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1520                 enum request req;
1522                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1523                 if (req != REQ_NONE)
1524                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1525         }
1528 /*
1529  * User config file handling.
1530  */
1532 static int   config_lineno;
1533 static bool  config_errors;
1534 static const char *config_msg;
1536 static const struct enum_map color_map[] = {
1537 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1538         COLOR_MAP(DEFAULT),
1539         COLOR_MAP(BLACK),
1540         COLOR_MAP(BLUE),
1541         COLOR_MAP(CYAN),
1542         COLOR_MAP(GREEN),
1543         COLOR_MAP(MAGENTA),
1544         COLOR_MAP(RED),
1545         COLOR_MAP(WHITE),
1546         COLOR_MAP(YELLOW),
1547 };
1549 static const struct enum_map attr_map[] = {
1550 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1551         ATTR_MAP(NORMAL),
1552         ATTR_MAP(BLINK),
1553         ATTR_MAP(BOLD),
1554         ATTR_MAP(DIM),
1555         ATTR_MAP(REVERSE),
1556         ATTR_MAP(STANDOUT),
1557         ATTR_MAP(UNDERLINE),
1558 };
1560 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1562 static int parse_step(double *opt, const char *arg)
1564         *opt = atoi(arg);
1565         if (!strchr(arg, '%'))
1566                 return OK;
1568         /* "Shift down" so 100% and 1 does not conflict. */
1569         *opt = (*opt - 1) / 100;
1570         if (*opt >= 1.0) {
1571                 *opt = 0.99;
1572                 config_msg = "Step value larger than 100%";
1573                 return ERR;
1574         }
1575         if (*opt < 0.0) {
1576                 *opt = 1;
1577                 config_msg = "Invalid step value";
1578                 return ERR;
1579         }
1580         return OK;
1583 static int
1584 parse_int(int *opt, const char *arg, int min, int max)
1586         int value = atoi(arg);
1588         if (min <= value && value <= max) {
1589                 *opt = value;
1590                 return OK;
1591         }
1593         config_msg = "Integer value out of bound";
1594         return ERR;
1597 static bool
1598 set_color(int *color, const char *name)
1600         if (map_enum(color, color_map, name))
1601                 return TRUE;
1602         if (!prefixcmp(name, "color"))
1603                 return parse_int(color, name + 5, 0, 255) == OK;
1604         return FALSE;
1607 /* Wants: object fgcolor bgcolor [attribute] */
1608 static int
1609 option_color_command(int argc, const char *argv[])
1611         struct line_info *info;
1613         if (argc < 3) {
1614                 config_msg = "Wrong number of arguments given to color command";
1615                 return ERR;
1616         }
1618         info = get_line_info(argv[0]);
1619         if (!info) {
1620                 static const struct enum_map obsolete[] = {
1621                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1622                         ENUM_MAP("main-date",   LINE_DATE),
1623                         ENUM_MAP("main-author", LINE_AUTHOR),
1624                 };
1625                 int index;
1627                 if (!map_enum(&index, obsolete, argv[0])) {
1628                         config_msg = "Unknown color name";
1629                         return ERR;
1630                 }
1631                 info = &line_info[index];
1632         }
1634         if (!set_color(&info->fg, argv[1]) ||
1635             !set_color(&info->bg, argv[2])) {
1636                 config_msg = "Unknown color";
1637                 return ERR;
1638         }
1640         info->attr = 0;
1641         while (argc-- > 3) {
1642                 int attr;
1644                 if (!set_attribute(&attr, argv[argc])) {
1645                         config_msg = "Unknown attribute";
1646                         return ERR;
1647                 }
1648                 info->attr |= attr;
1649         }
1651         return OK;
1654 static int parse_bool(bool *opt, const char *arg)
1656         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1657                 ? TRUE : FALSE;
1658         return OK;
1661 static int
1662 parse_string(char *opt, const char *arg, size_t optsize)
1664         int arglen = strlen(arg);
1666         switch (arg[0]) {
1667         case '\"':
1668         case '\'':
1669                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1670                         config_msg = "Unmatched quotation";
1671                         return ERR;
1672                 }
1673                 arg += 1; arglen -= 2;
1674         default:
1675                 string_ncopy_do(opt, optsize, arg, arglen);
1676                 return OK;
1677         }
1680 /* Wants: name = value */
1681 static int
1682 option_set_command(int argc, const char *argv[])
1684         if (argc != 3) {
1685                 config_msg = "Wrong number of arguments given to set command";
1686                 return ERR;
1687         }
1689         if (strcmp(argv[1], "=")) {
1690                 config_msg = "No value assigned";
1691                 return ERR;
1692         }
1694         if (!strcmp(argv[0], "show-author"))
1695                 return parse_bool(&opt_author, argv[2]);
1697         if (!strcmp(argv[0], "show-date")) {
1698                 bool show_date;
1700                 if (!strcmp(argv[2], "relative")) {
1701                         opt_date = DATE_RELATIVE;
1702                         return OK;
1703                 } else if (!strcmp(argv[2], "short")) {
1704                         opt_date = DATE_SHORT;
1705                         return OK;
1706                 } else if (parse_bool(&show_date, argv[2])) {
1707                         opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1708                 }
1709                 return ERR;
1710         }
1712         if (!strcmp(argv[0], "show-rev-graph"))
1713                 return parse_bool(&opt_rev_graph, argv[2]);
1715         if (!strcmp(argv[0], "show-refs"))
1716                 return parse_bool(&opt_show_refs, argv[2]);
1718         if (!strcmp(argv[0], "show-line-numbers"))
1719                 return parse_bool(&opt_line_number, argv[2]);
1721         if (!strcmp(argv[0], "line-graphics"))
1722                 return parse_bool(&opt_line_graphics, argv[2]);
1724         if (!strcmp(argv[0], "line-number-interval"))
1725                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1727         if (!strcmp(argv[0], "author-width"))
1728                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1730         if (!strcmp(argv[0], "horizontal-scroll"))
1731                 return parse_step(&opt_hscroll, argv[2]);
1733         if (!strcmp(argv[0], "split-view-height"))
1734                 return parse_step(&opt_scale_split_view, argv[2]);
1736         if (!strcmp(argv[0], "tab-size"))
1737                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1739         if (!strcmp(argv[0], "commit-encoding"))
1740                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1742         config_msg = "Unknown variable name";
1743         return ERR;
1746 /* Wants: mode request key */
1747 static int
1748 option_bind_command(int argc, const char *argv[])
1750         enum request request;
1751         int keymap = -1;
1752         int key;
1754         if (argc < 3) {
1755                 config_msg = "Wrong number of arguments given to bind command";
1756                 return ERR;
1757         }
1759         if (set_keymap(&keymap, argv[0]) == ERR) {
1760                 config_msg = "Unknown key map";
1761                 return ERR;
1762         }
1764         key = get_key_value(argv[1]);
1765         if (key == ERR) {
1766                 config_msg = "Unknown key";
1767                 return ERR;
1768         }
1770         request = get_request(argv[2]);
1771         if (request == REQ_NONE) {
1772                 static const struct enum_map obsolete[] = {
1773                         ENUM_MAP("cherry-pick",         REQ_NONE),
1774                         ENUM_MAP("screen-resize",       REQ_NONE),
1775                         ENUM_MAP("tree-parent",         REQ_PARENT),
1776                 };
1777                 int alias;
1779                 if (map_enum(&alias, obsolete, argv[2])) {
1780                         if (alias != REQ_NONE)
1781                                 add_keybinding(keymap, alias, key);
1782                         config_msg = "Obsolete request name";
1783                         return ERR;
1784                 }
1785         }
1786         if (request == REQ_NONE && *argv[2]++ == '!')
1787                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1788         if (request == REQ_NONE) {
1789                 config_msg = "Unknown request name";
1790                 return ERR;
1791         }
1793         add_keybinding(keymap, request, key);
1795         return OK;
1798 static int
1799 set_option(const char *opt, char *value)
1801         const char *argv[SIZEOF_ARG];
1802         int argc = 0;
1804         if (!argv_from_string(argv, &argc, value)) {
1805                 config_msg = "Too many option arguments";
1806                 return ERR;
1807         }
1809         if (!strcmp(opt, "color"))
1810                 return option_color_command(argc, argv);
1812         if (!strcmp(opt, "set"))
1813                 return option_set_command(argc, argv);
1815         if (!strcmp(opt, "bind"))
1816                 return option_bind_command(argc, argv);
1818         config_msg = "Unknown option command";
1819         return ERR;
1822 static int
1823 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1825         int status = OK;
1827         config_lineno++;
1828         config_msg = "Internal error";
1830         /* Check for comment markers, since read_properties() will
1831          * only ensure opt and value are split at first " \t". */
1832         optlen = strcspn(opt, "#");
1833         if (optlen == 0)
1834                 return OK;
1836         if (opt[optlen] != 0) {
1837                 config_msg = "No option value";
1838                 status = ERR;
1840         }  else {
1841                 /* Look for comment endings in the value. */
1842                 size_t len = strcspn(value, "#");
1844                 if (len < valuelen) {
1845                         valuelen = len;
1846                         value[valuelen] = 0;
1847                 }
1849                 status = set_option(opt, value);
1850         }
1852         if (status == ERR) {
1853                 warn("Error on line %d, near '%.*s': %s",
1854                      config_lineno, (int) optlen, opt, config_msg);
1855                 config_errors = TRUE;
1856         }
1858         /* Always keep going if errors are encountered. */
1859         return OK;
1862 static void
1863 load_option_file(const char *path)
1865         struct io io = {};
1867         /* It's OK that the file doesn't exist. */
1868         if (!io_open(&io, "%s", path))
1869                 return;
1871         config_lineno = 0;
1872         config_errors = FALSE;
1874         if (io_load(&io, " \t", read_option) == ERR ||
1875             config_errors == TRUE)
1876                 warn("Errors while loading %s.", path);
1879 static int
1880 load_options(void)
1882         const char *home = getenv("HOME");
1883         const char *tigrc_user = getenv("TIGRC_USER");
1884         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1885         char buf[SIZEOF_STR];
1887         add_builtin_run_requests();
1889         if (!tigrc_system)
1890                 tigrc_system = SYSCONFDIR "/tigrc";
1891         load_option_file(tigrc_system);
1893         if (!tigrc_user) {
1894                 if (!home || !string_format(buf, "%s/.tigrc", home))
1895                         return ERR;
1896                 tigrc_user = buf;
1897         }
1898         load_option_file(tigrc_user);
1900         return OK;
1904 /*
1905  * The viewer
1906  */
1908 struct view;
1909 struct view_ops;
1911 /* The display array of active views and the index of the current view. */
1912 static struct view *display[2];
1913 static unsigned int current_view;
1915 #define foreach_displayed_view(view, i) \
1916         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1918 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1920 /* Current head and commit ID */
1921 static char ref_blob[SIZEOF_REF]        = "";
1922 static char ref_commit[SIZEOF_REF]      = "HEAD";
1923 static char ref_head[SIZEOF_REF]        = "HEAD";
1925 struct view {
1926         const char *name;       /* View name */
1927         const char *cmd_env;    /* Command line set via environment */
1928         const char *id;         /* Points to either of ref_{head,commit,blob} */
1930         struct view_ops *ops;   /* View operations */
1932         enum keymap keymap;     /* What keymap does this view have */
1933         bool git_dir;           /* Whether the view requires a git directory. */
1935         char ref[SIZEOF_REF];   /* Hovered commit reference */
1936         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1938         int height, width;      /* The width and height of the main window */
1939         WINDOW *win;            /* The main window */
1940         WINDOW *title;          /* The title window living below the main window */
1942         /* Navigation */
1943         unsigned long offset;   /* Offset of the window top */
1944         unsigned long yoffset;  /* Offset from the window side. */
1945         unsigned long lineno;   /* Current line number */
1946         unsigned long p_offset; /* Previous offset of the window top */
1947         unsigned long p_yoffset;/* Previous offset from the window side */
1948         unsigned long p_lineno; /* Previous current line number */
1949         bool p_restore;         /* Should the previous position be restored. */
1951         /* Searching */
1952         char grep[SIZEOF_STR];  /* Search string */
1953         regex_t *regex;         /* Pre-compiled regexp */
1955         /* If non-NULL, points to the view that opened this view. If this view
1956          * is closed tig will switch back to the parent view. */
1957         struct view *parent;
1959         /* Buffering */
1960         size_t lines;           /* Total number of lines */
1961         struct line *line;      /* Line index */
1962         unsigned int digits;    /* Number of digits in the lines member. */
1964         /* Drawing */
1965         struct line *curline;   /* Line currently being drawn. */
1966         enum line_type curtype; /* Attribute currently used for drawing. */
1967         unsigned long col;      /* Column when drawing. */
1968         bool has_scrolled;      /* View was scrolled. */
1970         /* Loading */
1971         struct io io;
1972         struct io *pipe;
1973         time_t start_time;
1974         time_t update_secs;
1975 };
1977 struct view_ops {
1978         /* What type of content being displayed. Used in the title bar. */
1979         const char *type;
1980         /* Default command arguments. */
1981         const char **argv;
1982         /* Open and reads in all view content. */
1983         bool (*open)(struct view *view);
1984         /* Read one line; updates view->line. */
1985         bool (*read)(struct view *view, char *data);
1986         /* Draw one line; @lineno must be < view->height. */
1987         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1988         /* Depending on view handle a special requests. */
1989         enum request (*request)(struct view *view, enum request request, struct line *line);
1990         /* Search for regexp in a line. */
1991         bool (*grep)(struct view *view, struct line *line);
1992         /* Select line */
1993         void (*select)(struct view *view, struct line *line);
1994         /* Prepare view for loading */
1995         bool (*prepare)(struct view *view);
1996 };
1998 static struct view_ops blame_ops;
1999 static struct view_ops blob_ops;
2000 static struct view_ops diff_ops;
2001 static struct view_ops help_ops;
2002 static struct view_ops log_ops;
2003 static struct view_ops main_ops;
2004 static struct view_ops pager_ops;
2005 static struct view_ops stage_ops;
2006 static struct view_ops status_ops;
2007 static struct view_ops tree_ops;
2008 static struct view_ops branch_ops;
2010 #define VIEW_STR(name, env, ref, ops, map, git) \
2011         { name, #env, ref, ops, map, git }
2013 #define VIEW_(id, name, ops, git, ref) \
2014         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2017 static struct view views[] = {
2018         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2019         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2020         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2021         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2022         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2023         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2024         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2025         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2026         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2027         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2028         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2029 };
2031 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2032 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2034 #define foreach_view(view, i) \
2035         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2037 #define view_is_displayed(view) \
2038         (view == display[0] || view == display[1])
2041 enum line_graphic {
2042         LINE_GRAPHIC_VLINE
2043 };
2045 static chtype line_graphics[] = {
2046         /* LINE_GRAPHIC_VLINE: */ '|'
2047 };
2049 static inline void
2050 set_view_attr(struct view *view, enum line_type type)
2052         if (!view->curline->selected && view->curtype != type) {
2053                 wattrset(view->win, get_line_attr(type));
2054                 wchgat(view->win, -1, 0, type, NULL);
2055                 view->curtype = type;
2056         }
2059 static int
2060 draw_chars(struct view *view, enum line_type type, const char *string,
2061            int max_len, bool use_tilde)
2063         int len = 0;
2064         int col = 0;
2065         int trimmed = FALSE;
2066         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2068         if (max_len <= 0)
2069                 return 0;
2071         if (opt_utf8) {
2072                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2073         } else {
2074                 col = len = strlen(string);
2075                 if (len > max_len) {
2076                         if (use_tilde) {
2077                                 max_len -= 1;
2078                         }
2079                         col = len = max_len;
2080                         trimmed = TRUE;
2081                 }
2082         }
2084         set_view_attr(view, type);
2085         if (len > 0)
2086                 waddnstr(view->win, string, len);
2087         if (trimmed && use_tilde) {
2088                 set_view_attr(view, LINE_DELIMITER);
2089                 waddch(view->win, '~');
2090                 col++;
2091         }
2093         return col;
2096 static int
2097 draw_space(struct view *view, enum line_type type, int max, int spaces)
2099         static char space[] = "                    ";
2100         int col = 0;
2102         spaces = MIN(max, spaces);
2104         while (spaces > 0) {
2105                 int len = MIN(spaces, sizeof(space) - 1);
2107                 col += draw_chars(view, type, space, len, FALSE);
2108                 spaces -= len;
2109         }
2111         return col;
2114 static bool
2115 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2117         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2118         return view->width + view->yoffset <= view->col;
2121 static bool
2122 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2124         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2125         int max = view->width + view->yoffset - view->col;
2126         int i;
2128         if (max < size)
2129                 size = max;
2131         set_view_attr(view, type);
2132         /* Using waddch() instead of waddnstr() ensures that
2133          * they'll be rendered correctly for the cursor line. */
2134         for (i = skip; i < size; i++)
2135                 waddch(view->win, graphic[i]);
2137         view->col += size;
2138         if (size < max && skip <= size)
2139                 waddch(view->win, ' ');
2140         view->col++;
2142         return view->width + view->yoffset <= view->col;
2145 static bool
2146 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2148         int max = MIN(view->width + view->yoffset - view->col, len);
2149         int col;
2151         if (text)
2152                 col = draw_chars(view, type, text, max - 1, trim);
2153         else
2154                 col = draw_space(view, type, max - 1, max - 1);
2156         view->col += col;
2157         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2158         return view->width + view->yoffset <= view->col;
2161 static bool
2162 draw_date(struct view *view, time_t *time)
2164         const char *date = time ? mkdate(time) : "";
2165         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2167         return draw_field(view, LINE_DATE, date, cols, FALSE);
2170 static bool
2171 draw_author(struct view *view, const char *author)
2173         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2175         if (!trim) {
2176                 static char initials[10];
2177                 size_t pos;
2179 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2181                 memset(initials, 0, sizeof(initials));
2182                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2183                         while (is_initial_sep(*author))
2184                                 author++;
2185                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2186                         while (*author && !is_initial_sep(author[1]))
2187                                 author++;
2188                 }
2190                 author = initials;
2191         }
2193         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2196 static bool
2197 draw_mode(struct view *view, mode_t mode)
2199         const char *str;
2201         if (S_ISDIR(mode))
2202                 str = "drwxr-xr-x";
2203         else if (S_ISLNK(mode))
2204                 str = "lrwxrwxrwx";
2205         else if (S_ISGITLINK(mode))
2206                 str = "m---------";
2207         else if (S_ISREG(mode) && mode & S_IXUSR)
2208                 str = "-rwxr-xr-x";
2209         else if (S_ISREG(mode))
2210                 str = "-rw-r--r--";
2211         else
2212                 str = "----------";
2214         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2217 static bool
2218 draw_lineno(struct view *view, unsigned int lineno)
2220         char number[10];
2221         int digits3 = view->digits < 3 ? 3 : view->digits;
2222         int max = MIN(view->width + view->yoffset - view->col, digits3);
2223         char *text = NULL;
2225         lineno += view->offset + 1;
2226         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2227                 static char fmt[] = "%1ld";
2229                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2230                 if (string_format(number, fmt, lineno))
2231                         text = number;
2232         }
2233         if (text)
2234                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2235         else
2236                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2237         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2240 static bool
2241 draw_view_line(struct view *view, unsigned int lineno)
2243         struct line *line;
2244         bool selected = (view->offset + lineno == view->lineno);
2246         assert(view_is_displayed(view));
2248         if (view->offset + lineno >= view->lines)
2249                 return FALSE;
2251         line = &view->line[view->offset + lineno];
2253         wmove(view->win, lineno, 0);
2254         if (line->cleareol)
2255                 wclrtoeol(view->win);
2256         view->col = 0;
2257         view->curline = line;
2258         view->curtype = LINE_NONE;
2259         line->selected = FALSE;
2260         line->dirty = line->cleareol = 0;
2262         if (selected) {
2263                 set_view_attr(view, LINE_CURSOR);
2264                 line->selected = TRUE;
2265                 view->ops->select(view, line);
2266         }
2268         return view->ops->draw(view, line, lineno);
2271 static void
2272 redraw_view_dirty(struct view *view)
2274         bool dirty = FALSE;
2275         int lineno;
2277         for (lineno = 0; lineno < view->height; lineno++) {
2278                 if (view->offset + lineno >= view->lines)
2279                         break;
2280                 if (!view->line[view->offset + lineno].dirty)
2281                         continue;
2282                 dirty = TRUE;
2283                 if (!draw_view_line(view, lineno))
2284                         break;
2285         }
2287         if (!dirty)
2288                 return;
2289         wnoutrefresh(view->win);
2292 static void
2293 redraw_view_from(struct view *view, int lineno)
2295         assert(0 <= lineno && lineno < view->height);
2297         for (; lineno < view->height; lineno++) {
2298                 if (!draw_view_line(view, lineno))
2299                         break;
2300         }
2302         wnoutrefresh(view->win);
2305 static void
2306 redraw_view(struct view *view)
2308         werase(view->win);
2309         redraw_view_from(view, 0);
2313 static void
2314 update_view_title(struct view *view)
2316         char buf[SIZEOF_STR];
2317         char state[SIZEOF_STR];
2318         size_t bufpos = 0, statelen = 0;
2320         assert(view_is_displayed(view));
2322         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2323                 unsigned int view_lines = view->offset + view->height;
2324                 unsigned int lines = view->lines
2325                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2326                                    : 0;
2328                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2329                                    view->ops->type,
2330                                    view->lineno + 1,
2331                                    view->lines,
2332                                    lines);
2334         }
2336         if (view->pipe) {
2337                 time_t secs = time(NULL) - view->start_time;
2339                 /* Three git seconds are a long time ... */
2340                 if (secs > 2)
2341                         string_format_from(state, &statelen, " loading %lds", secs);
2342         }
2344         string_format_from(buf, &bufpos, "[%s]", view->name);
2345         if (*view->ref && bufpos < view->width) {
2346                 size_t refsize = strlen(view->ref);
2347                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2349                 if (minsize < view->width)
2350                         refsize = view->width - minsize + 7;
2351                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2352         }
2354         if (statelen && bufpos < view->width) {
2355                 string_format_from(buf, &bufpos, "%s", state);
2356         }
2358         if (view == display[current_view])
2359                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2360         else
2361                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2363         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2364         wclrtoeol(view->title);
2365         wnoutrefresh(view->title);
2368 static int
2369 apply_step(double step, int value)
2371         if (step >= 1)
2372                 return (int) step;
2373         value *= step + 0.01;
2374         return value ? value : 1;
2377 static void
2378 resize_display(void)
2380         int offset, i;
2381         struct view *base = display[0];
2382         struct view *view = display[1] ? display[1] : display[0];
2384         /* Setup window dimensions */
2386         getmaxyx(stdscr, base->height, base->width);
2388         /* Make room for the status window. */
2389         base->height -= 1;
2391         if (view != base) {
2392                 /* Horizontal split. */
2393                 view->width   = base->width;
2394                 view->height  = apply_step(opt_scale_split_view, base->height);
2395                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2396                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2397                 base->height -= view->height;
2399                 /* Make room for the title bar. */
2400                 view->height -= 1;
2401         }
2403         /* Make room for the title bar. */
2404         base->height -= 1;
2406         offset = 0;
2408         foreach_displayed_view (view, i) {
2409                 if (!view->win) {
2410                         view->win = newwin(view->height, 0, offset, 0);
2411                         if (!view->win)
2412                                 die("Failed to create %s view", view->name);
2414                         scrollok(view->win, FALSE);
2416                         view->title = newwin(1, 0, offset + view->height, 0);
2417                         if (!view->title)
2418                                 die("Failed to create title window");
2420                 } else {
2421                         wresize(view->win, view->height, view->width);
2422                         mvwin(view->win,   offset, 0);
2423                         mvwin(view->title, offset + view->height, 0);
2424                 }
2426                 offset += view->height + 1;
2427         }
2430 static void
2431 redraw_display(bool clear)
2433         struct view *view;
2434         int i;
2436         foreach_displayed_view (view, i) {
2437                 if (clear)
2438                         wclear(view->win);
2439                 redraw_view(view);
2440                 update_view_title(view);
2441         }
2444 static void
2445 toggle_date_option(enum date *date)
2447         static const char *help[] = {
2448                 "no",
2449                 "default",
2450                 "relative",
2451                 "short"
2452         };
2454         opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2455         redraw_display(FALSE);
2456         report("Displaying %s dates", help[opt_date]);
2459 static void
2460 toggle_view_option(bool *option, const char *help)
2462         *option = !*option;
2463         redraw_display(FALSE);
2464         report("%sabling %s", *option ? "En" : "Dis", help);
2467 static void
2468 open_option_menu(void)
2470         const struct menu_item menu[] = {
2471                 { '.', "line numbers", &opt_line_number },
2472                 { 'D', "date display", &opt_date },
2473                 { 'A', "author display", &opt_author },
2474                 { 'g', "revision graph display", &opt_rev_graph },
2475                 { 'F', "reference display", &opt_show_refs },
2476                 { 0 }
2477         };
2478         int selected = 0;
2480         if (prompt_menu("Toggle option", menu, &selected)) {
2481                 if (menu[selected].data == &opt_date)
2482                         toggle_date_option(menu[selected].data);
2483                 else
2484                         toggle_view_option(menu[selected].data, menu[selected].text);
2485         }
2488 static void
2489 maximize_view(struct view *view)
2491         memset(display, 0, sizeof(display));
2492         current_view = 0;
2493         display[current_view] = view;
2494         resize_display();
2495         redraw_display(FALSE);
2496         report("");
2500 /*
2501  * Navigation
2502  */
2504 static bool
2505 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2507         if (lineno >= view->lines)
2508                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2510         if (offset > lineno || offset + view->height <= lineno) {
2511                 unsigned long half = view->height / 2;
2513                 if (lineno > half)
2514                         offset = lineno - half;
2515                 else
2516                         offset = 0;
2517         }
2519         if (offset != view->offset || lineno != view->lineno) {
2520                 view->offset = offset;
2521                 view->lineno = lineno;
2522                 return TRUE;
2523         }
2525         return FALSE;
2528 /* Scrolling backend */
2529 static void
2530 do_scroll_view(struct view *view, int lines)
2532         bool redraw_current_line = FALSE;
2534         /* The rendering expects the new offset. */
2535         view->offset += lines;
2537         assert(0 <= view->offset && view->offset < view->lines);
2538         assert(lines);
2540         /* Move current line into the view. */
2541         if (view->lineno < view->offset) {
2542                 view->lineno = view->offset;
2543                 redraw_current_line = TRUE;
2544         } else if (view->lineno >= view->offset + view->height) {
2545                 view->lineno = view->offset + view->height - 1;
2546                 redraw_current_line = TRUE;
2547         }
2549         assert(view->offset <= view->lineno && view->lineno < view->lines);
2551         /* Redraw the whole screen if scrolling is pointless. */
2552         if (view->height < ABS(lines)) {
2553                 redraw_view(view);
2555         } else {
2556                 int line = lines > 0 ? view->height - lines : 0;
2557                 int end = line + ABS(lines);
2559                 scrollok(view->win, TRUE);
2560                 wscrl(view->win, lines);
2561                 scrollok(view->win, FALSE);
2563                 while (line < end && draw_view_line(view, line))
2564                         line++;
2566                 if (redraw_current_line)
2567                         draw_view_line(view, view->lineno - view->offset);
2568                 wnoutrefresh(view->win);
2569         }
2571         view->has_scrolled = TRUE;
2572         report("");
2575 /* Scroll frontend */
2576 static void
2577 scroll_view(struct view *view, enum request request)
2579         int lines = 1;
2581         assert(view_is_displayed(view));
2583         switch (request) {
2584         case REQ_SCROLL_LEFT:
2585                 if (view->yoffset == 0) {
2586                         report("Cannot scroll beyond the first column");
2587                         return;
2588                 }
2589                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2590                         view->yoffset = 0;
2591                 else
2592                         view->yoffset -= apply_step(opt_hscroll, view->width);
2593                 redraw_view_from(view, 0);
2594                 report("");
2595                 return;
2596         case REQ_SCROLL_RIGHT:
2597                 view->yoffset += apply_step(opt_hscroll, view->width);
2598                 redraw_view(view);
2599                 report("");
2600                 return;
2601         case REQ_SCROLL_PAGE_DOWN:
2602                 lines = view->height;
2603         case REQ_SCROLL_LINE_DOWN:
2604                 if (view->offset + lines > view->lines)
2605                         lines = view->lines - view->offset;
2607                 if (lines == 0 || view->offset + view->height >= view->lines) {
2608                         report("Cannot scroll beyond the last line");
2609                         return;
2610                 }
2611                 break;
2613         case REQ_SCROLL_PAGE_UP:
2614                 lines = view->height;
2615         case REQ_SCROLL_LINE_UP:
2616                 if (lines > view->offset)
2617                         lines = view->offset;
2619                 if (lines == 0) {
2620                         report("Cannot scroll beyond the first line");
2621                         return;
2622                 }
2624                 lines = -lines;
2625                 break;
2627         default:
2628                 die("request %d not handled in switch", request);
2629         }
2631         do_scroll_view(view, lines);
2634 /* Cursor moving */
2635 static void
2636 move_view(struct view *view, enum request request)
2638         int scroll_steps = 0;
2639         int steps;
2641         switch (request) {
2642         case REQ_MOVE_FIRST_LINE:
2643                 steps = -view->lineno;
2644                 break;
2646         case REQ_MOVE_LAST_LINE:
2647                 steps = view->lines - view->lineno - 1;
2648                 break;
2650         case REQ_MOVE_PAGE_UP:
2651                 steps = view->height > view->lineno
2652                       ? -view->lineno : -view->height;
2653                 break;
2655         case REQ_MOVE_PAGE_DOWN:
2656                 steps = view->lineno + view->height >= view->lines
2657                       ? view->lines - view->lineno - 1 : view->height;
2658                 break;
2660         case REQ_MOVE_UP:
2661                 steps = -1;
2662                 break;
2664         case REQ_MOVE_DOWN:
2665                 steps = 1;
2666                 break;
2668         default:
2669                 die("request %d not handled in switch", request);
2670         }
2672         if (steps <= 0 && view->lineno == 0) {
2673                 report("Cannot move beyond the first line");
2674                 return;
2676         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2677                 report("Cannot move beyond the last line");
2678                 return;
2679         }
2681         /* Move the current line */
2682         view->lineno += steps;
2683         assert(0 <= view->lineno && view->lineno < view->lines);
2685         /* Check whether the view needs to be scrolled */
2686         if (view->lineno < view->offset ||
2687             view->lineno >= view->offset + view->height) {
2688                 scroll_steps = steps;
2689                 if (steps < 0 && -steps > view->offset) {
2690                         scroll_steps = -view->offset;
2692                 } else if (steps > 0) {
2693                         if (view->lineno == view->lines - 1 &&
2694                             view->lines > view->height) {
2695                                 scroll_steps = view->lines - view->offset - 1;
2696                                 if (scroll_steps >= view->height)
2697                                         scroll_steps -= view->height - 1;
2698                         }
2699                 }
2700         }
2702         if (!view_is_displayed(view)) {
2703                 view->offset += scroll_steps;
2704                 assert(0 <= view->offset && view->offset < view->lines);
2705                 view->ops->select(view, &view->line[view->lineno]);
2706                 return;
2707         }
2709         /* Repaint the old "current" line if we be scrolling */
2710         if (ABS(steps) < view->height)
2711                 draw_view_line(view, view->lineno - steps - view->offset);
2713         if (scroll_steps) {
2714                 do_scroll_view(view, scroll_steps);
2715                 return;
2716         }
2718         /* Draw the current line */
2719         draw_view_line(view, view->lineno - view->offset);
2721         wnoutrefresh(view->win);
2722         report("");
2726 /*
2727  * Searching
2728  */
2730 static void search_view(struct view *view, enum request request);
2732 static bool
2733 grep_text(struct view *view, const char *text[])
2735         regmatch_t pmatch;
2736         size_t i;
2738         for (i = 0; text[i]; i++)
2739                 if (*text[i] &&
2740                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2741                         return TRUE;
2742         return FALSE;
2745 static void
2746 select_view_line(struct view *view, unsigned long lineno)
2748         unsigned long old_lineno = view->lineno;
2749         unsigned long old_offset = view->offset;
2751         if (goto_view_line(view, view->offset, lineno)) {
2752                 if (view_is_displayed(view)) {
2753                         if (old_offset != view->offset) {
2754                                 redraw_view(view);
2755                         } else {
2756                                 draw_view_line(view, old_lineno - view->offset);
2757                                 draw_view_line(view, view->lineno - view->offset);
2758                                 wnoutrefresh(view->win);
2759                         }
2760                 } else {
2761                         view->ops->select(view, &view->line[view->lineno]);
2762                 }
2763         }
2766 static void
2767 find_next(struct view *view, enum request request)
2769         unsigned long lineno = view->lineno;
2770         int direction;
2772         if (!*view->grep) {
2773                 if (!*opt_search)
2774                         report("No previous search");
2775                 else
2776                         search_view(view, request);
2777                 return;
2778         }
2780         switch (request) {
2781         case REQ_SEARCH:
2782         case REQ_FIND_NEXT:
2783                 direction = 1;
2784                 break;
2786         case REQ_SEARCH_BACK:
2787         case REQ_FIND_PREV:
2788                 direction = -1;
2789                 break;
2791         default:
2792                 return;
2793         }
2795         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2796                 lineno += direction;
2798         /* Note, lineno is unsigned long so will wrap around in which case it
2799          * will become bigger than view->lines. */
2800         for (; lineno < view->lines; lineno += direction) {
2801                 if (view->ops->grep(view, &view->line[lineno])) {
2802                         select_view_line(view, lineno);
2803                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2804                         return;
2805                 }
2806         }
2808         report("No match found for '%s'", view->grep);
2811 static void
2812 search_view(struct view *view, enum request request)
2814         int regex_err;
2816         if (view->regex) {
2817                 regfree(view->regex);
2818                 *view->grep = 0;
2819         } else {
2820                 view->regex = calloc(1, sizeof(*view->regex));
2821                 if (!view->regex)
2822                         return;
2823         }
2825         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2826         if (regex_err != 0) {
2827                 char buf[SIZEOF_STR] = "unknown error";
2829                 regerror(regex_err, view->regex, buf, sizeof(buf));
2830                 report("Search failed: %s", buf);
2831                 return;
2832         }
2834         string_copy(view->grep, opt_search);
2836         find_next(view, request);
2839 /*
2840  * Incremental updating
2841  */
2843 static void
2844 reset_view(struct view *view)
2846         int i;
2848         for (i = 0; i < view->lines; i++)
2849                 free(view->line[i].data);
2850         free(view->line);
2852         view->p_offset = view->offset;
2853         view->p_yoffset = view->yoffset;
2854         view->p_lineno = view->lineno;
2856         view->line = NULL;
2857         view->offset = 0;
2858         view->yoffset = 0;
2859         view->lines  = 0;
2860         view->lineno = 0;
2861         view->vid[0] = 0;
2862         view->update_secs = 0;
2865 static void
2866 free_argv(const char *argv[])
2868         int argc;
2870         for (argc = 0; argv[argc]; argc++)
2871                 free((void *) argv[argc]);
2874 static bool
2875 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2877         char buf[SIZEOF_STR];
2878         int argc;
2879         bool noreplace = flags == FORMAT_NONE;
2881         free_argv(dst_argv);
2883         for (argc = 0; src_argv[argc]; argc++) {
2884                 const char *arg = src_argv[argc];
2885                 size_t bufpos = 0;
2887                 while (arg) {
2888                         char *next = strstr(arg, "%(");
2889                         int len = next - arg;
2890                         const char *value;
2892                         if (!next || noreplace) {
2893                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2894                                         noreplace = TRUE;
2895                                 len = strlen(arg);
2896                                 value = "";
2898                         } else if (!prefixcmp(next, "%(directory)")) {
2899                                 value = opt_path;
2901                         } else if (!prefixcmp(next, "%(file)")) {
2902                                 value = opt_file;
2904                         } else if (!prefixcmp(next, "%(ref)")) {
2905                                 value = *opt_ref ? opt_ref : "HEAD";
2907                         } else if (!prefixcmp(next, "%(head)")) {
2908                                 value = ref_head;
2910                         } else if (!prefixcmp(next, "%(commit)")) {
2911                                 value = ref_commit;
2913                         } else if (!prefixcmp(next, "%(blob)")) {
2914                                 value = ref_blob;
2916                         } else {
2917                                 report("Unknown replacement: `%s`", next);
2918                                 return FALSE;
2919                         }
2921                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2922                                 return FALSE;
2924                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2925                 }
2927                 dst_argv[argc] = strdup(buf);
2928                 if (!dst_argv[argc])
2929                         break;
2930         }
2932         dst_argv[argc] = NULL;
2934         return src_argv[argc] == NULL;
2937 static bool
2938 restore_view_position(struct view *view)
2940         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2941                 return FALSE;
2943         /* Changing the view position cancels the restoring. */
2944         /* FIXME: Changing back to the first line is not detected. */
2945         if (view->offset != 0 || view->lineno != 0) {
2946                 view->p_restore = FALSE;
2947                 return FALSE;
2948         }
2950         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2951             view_is_displayed(view))
2952                 werase(view->win);
2954         view->yoffset = view->p_yoffset;
2955         view->p_restore = FALSE;
2957         return TRUE;
2960 static void
2961 end_update(struct view *view, bool force)
2963         if (!view->pipe)
2964                 return;
2965         while (!view->ops->read(view, NULL))
2966                 if (!force)
2967                         return;
2968         set_nonblocking_input(FALSE);
2969         if (force)
2970                 kill_io(view->pipe);
2971         done_io(view->pipe);
2972         view->pipe = NULL;
2975 static void
2976 setup_update(struct view *view, const char *vid)
2978         set_nonblocking_input(TRUE);
2979         reset_view(view);
2980         string_copy_rev(view->vid, vid);
2981         view->pipe = &view->io;
2982         view->start_time = time(NULL);
2985 static bool
2986 prepare_update(struct view *view, const char *argv[], const char *dir,
2987                enum format_flags flags)
2989         if (view->pipe)
2990                 end_update(view, TRUE);
2991         return init_io_rd(&view->io, argv, dir, flags);
2994 static bool
2995 prepare_update_file(struct view *view, const char *name)
2997         if (view->pipe)
2998                 end_update(view, TRUE);
2999         return io_open(&view->io, "%s", name);
3002 static bool
3003 begin_update(struct view *view, bool refresh)
3005         if (view->pipe)
3006                 end_update(view, TRUE);
3008         if (!refresh) {
3009                 if (view->ops->prepare) {
3010                         if (!view->ops->prepare(view))
3011                                 return FALSE;
3012                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, 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         if (!start_io(&view->io))
3024                 return FALSE;
3026         setup_update(view, view->id);
3028         return TRUE;
3031 static bool
3032 update_view(struct view *view)
3034         char out_buffer[BUFSIZ * 2];
3035         char *line;
3036         /* Clear the view and redraw everything since the tree sorting
3037          * might have rearranged things. */
3038         bool redraw = view->lines == 0;
3039         bool can_read = TRUE;
3041         if (!view->pipe)
3042                 return TRUE;
3044         if (!io_can_read(view->pipe)) {
3045                 if (view->lines == 0 && view_is_displayed(view)) {
3046                         time_t secs = time(NULL) - view->start_time;
3048                         if (secs > 1 && secs > view->update_secs) {
3049                                 if (view->update_secs == 0)
3050                                         redraw_view(view);
3051                                 update_view_title(view);
3052                                 view->update_secs = secs;
3053                         }
3054                 }
3055                 return TRUE;
3056         }
3058         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3059                 if (opt_iconv != ICONV_NONE) {
3060                         ICONV_CONST char *inbuf = line;
3061                         size_t inlen = strlen(line) + 1;
3063                         char *outbuf = out_buffer;
3064                         size_t outlen = sizeof(out_buffer);
3066                         size_t ret;
3068                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3069                         if (ret != (size_t) -1)
3070                                 line = out_buffer;
3071                 }
3073                 if (!view->ops->read(view, line)) {
3074                         report("Allocation failure");
3075                         end_update(view, TRUE);
3076                         return FALSE;
3077                 }
3078         }
3080         {
3081                 unsigned long lines = view->lines;
3082                 int digits;
3084                 for (digits = 0; lines; digits++)
3085                         lines /= 10;
3087                 /* Keep the displayed view in sync with line number scaling. */
3088                 if (digits != view->digits) {
3089                         view->digits = digits;
3090                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3091                                 redraw = TRUE;
3092                 }
3093         }
3095         if (io_error(view->pipe)) {
3096                 report("Failed to read: %s", io_strerror(view->pipe));
3097                 end_update(view, TRUE);
3099         } else if (io_eof(view->pipe)) {
3100                 report("");
3101                 end_update(view, FALSE);
3102         }
3104         if (restore_view_position(view))
3105                 redraw = TRUE;
3107         if (!view_is_displayed(view))
3108                 return TRUE;
3110         if (redraw)
3111                 redraw_view_from(view, 0);
3112         else
3113                 redraw_view_dirty(view);
3115         /* Update the title _after_ the redraw so that if the redraw picks up a
3116          * commit reference in view->ref it'll be available here. */
3117         update_view_title(view);
3118         return TRUE;
3121 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3123 static struct line *
3124 add_line_data(struct view *view, void *data, enum line_type type)
3126         struct line *line;
3128         if (!realloc_lines(&view->line, view->lines, 1))
3129                 return NULL;
3131         line = &view->line[view->lines++];
3132         memset(line, 0, sizeof(*line));
3133         line->type = type;
3134         line->data = data;
3135         line->dirty = 1;
3137         return line;
3140 static struct line *
3141 add_line_text(struct view *view, const char *text, enum line_type type)
3143         char *data = text ? strdup(text) : NULL;
3145         return data ? add_line_data(view, data, type) : NULL;
3148 static struct line *
3149 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3151         char buf[SIZEOF_STR];
3152         va_list args;
3154         va_start(args, fmt);
3155         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3156                 buf[0] = 0;
3157         va_end(args);
3159         return buf[0] ? add_line_text(view, buf, type) : NULL;
3162 /*
3163  * View opening
3164  */
3166 enum open_flags {
3167         OPEN_DEFAULT = 0,       /* Use default view switching. */
3168         OPEN_SPLIT = 1,         /* Split current view. */
3169         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3170         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3171         OPEN_PREPARED = 32,     /* Open already prepared command. */
3172 };
3174 static void
3175 open_view(struct view *prev, enum request request, enum open_flags flags)
3177         bool split = !!(flags & OPEN_SPLIT);
3178         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3179         bool nomaximize = !!(flags & OPEN_REFRESH);
3180         struct view *view = VIEW(request);
3181         int nviews = displayed_views();
3182         struct view *base_view = display[0];
3184         if (view == prev && nviews == 1 && !reload) {
3185                 report("Already in %s view", view->name);
3186                 return;
3187         }
3189         if (view->git_dir && !opt_git_dir[0]) {
3190                 report("The %s view is disabled in pager view", view->name);
3191                 return;
3192         }
3194         if (split) {
3195                 display[1] = view;
3196                 current_view = 1;
3197         } else if (!nomaximize) {
3198                 /* Maximize the current view. */
3199                 memset(display, 0, sizeof(display));
3200                 current_view = 0;
3201                 display[current_view] = view;
3202         }
3204         /* No parent signals that this is the first loaded view. */
3205         if (prev && view != prev) {
3206                 view->parent = prev;
3207         }
3209         /* Resize the view when switching between split- and full-screen,
3210          * or when switching between two different full-screen views. */
3211         if (nviews != displayed_views() ||
3212             (nviews == 1 && base_view != display[0]))
3213                 resize_display();
3215         if (view->ops->open) {
3216                 if (view->pipe)
3217                         end_update(view, TRUE);
3218                 if (!view->ops->open(view)) {
3219                         report("Failed to load %s view", view->name);
3220                         return;
3221                 }
3222                 restore_view_position(view);
3224         } else if ((reload || strcmp(view->vid, view->id)) &&
3225                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3226                 report("Failed to load %s view", view->name);
3227                 return;
3228         }
3230         if (split && prev->lineno - prev->offset >= prev->height) {
3231                 /* Take the title line into account. */
3232                 int lines = prev->lineno - prev->offset - prev->height + 1;
3234                 /* Scroll the view that was split if the current line is
3235                  * outside the new limited view. */
3236                 do_scroll_view(prev, lines);
3237         }
3239         if (prev && view != prev && split && view_is_displayed(prev)) {
3240                 /* "Blur" the previous view. */
3241                 update_view_title(prev);
3242         }
3244         if (view->pipe && view->lines == 0) {
3245                 /* Clear the old view and let the incremental updating refill
3246                  * the screen. */
3247                 werase(view->win);
3248                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3249                 report("");
3250         } else if (view_is_displayed(view)) {
3251                 redraw_view(view);
3252                 report("");
3253         }
3256 static void
3257 open_external_viewer(const char *argv[], const char *dir)
3259         def_prog_mode();           /* save current tty modes */
3260         endwin();                  /* restore original tty modes */
3261         run_io_fg(argv, dir);
3262         fprintf(stderr, "Press Enter to continue");
3263         getc(opt_tty);
3264         reset_prog_mode();
3265         redraw_display(TRUE);
3268 static void
3269 open_mergetool(const char *file)
3271         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3273         open_external_viewer(mergetool_argv, opt_cdup);
3276 static void
3277 open_editor(bool from_root, const char *file)
3279         const char *editor_argv[] = { "vi", file, NULL };
3280         const char *editor;
3282         editor = getenv("GIT_EDITOR");
3283         if (!editor && *opt_editor)
3284                 editor = opt_editor;
3285         if (!editor)
3286                 editor = getenv("VISUAL");
3287         if (!editor)
3288                 editor = getenv("EDITOR");
3289         if (!editor)
3290                 editor = "vi";
3292         editor_argv[0] = editor;
3293         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3296 static void
3297 open_run_request(enum request request)
3299         struct run_request *req = get_run_request(request);
3300         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3302         if (!req) {
3303                 report("Unknown run request");
3304                 return;
3305         }
3307         if (format_argv(argv, req->argv, FORMAT_ALL))
3308                 open_external_viewer(argv, NULL);
3309         free_argv(argv);
3312 /*
3313  * User request switch noodle
3314  */
3316 static int
3317 view_driver(struct view *view, enum request request)
3319         int i;
3321         if (request == REQ_NONE)
3322                 return TRUE;
3324         if (request > REQ_NONE) {
3325                 open_run_request(request);
3326                 /* FIXME: When all views can refresh always do this. */
3327                 if (view == VIEW(REQ_VIEW_STATUS) ||
3328                     view == VIEW(REQ_VIEW_MAIN) ||
3329                     view == VIEW(REQ_VIEW_LOG) ||
3330                     view == VIEW(REQ_VIEW_BRANCH) ||
3331                     view == VIEW(REQ_VIEW_STAGE))
3332                         request = REQ_REFRESH;
3333                 else
3334                         return TRUE;
3335         }
3337         if (view && view->lines) {
3338                 request = view->ops->request(view, request, &view->line[view->lineno]);
3339                 if (request == REQ_NONE)
3340                         return TRUE;
3341         }
3343         switch (request) {
3344         case REQ_MOVE_UP:
3345         case REQ_MOVE_DOWN:
3346         case REQ_MOVE_PAGE_UP:
3347         case REQ_MOVE_PAGE_DOWN:
3348         case REQ_MOVE_FIRST_LINE:
3349         case REQ_MOVE_LAST_LINE:
3350                 move_view(view, request);
3351                 break;
3353         case REQ_SCROLL_LEFT:
3354         case REQ_SCROLL_RIGHT:
3355         case REQ_SCROLL_LINE_DOWN:
3356         case REQ_SCROLL_LINE_UP:
3357         case REQ_SCROLL_PAGE_DOWN:
3358         case REQ_SCROLL_PAGE_UP:
3359                 scroll_view(view, request);
3360                 break;
3362         case REQ_VIEW_BLAME:
3363                 if (!opt_file[0]) {
3364                         report("No file chosen, press %s to open tree view",
3365                                get_key(view->keymap, REQ_VIEW_TREE));
3366                         break;
3367                 }
3368                 open_view(view, request, OPEN_DEFAULT);
3369                 break;
3371         case REQ_VIEW_BLOB:
3372                 if (!ref_blob[0]) {
3373                         report("No file chosen, press %s to open tree view",
3374                                get_key(view->keymap, REQ_VIEW_TREE));
3375                         break;
3376                 }
3377                 open_view(view, request, OPEN_DEFAULT);
3378                 break;
3380         case REQ_VIEW_PAGER:
3381                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3382                         report("No pager content, press %s to run command from prompt",
3383                                get_key(view->keymap, REQ_PROMPT));
3384                         break;
3385                 }
3386                 open_view(view, request, OPEN_DEFAULT);
3387                 break;
3389         case REQ_VIEW_STAGE:
3390                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3391                         report("No stage content, press %s to open the status view and choose file",
3392                                get_key(view->keymap, REQ_VIEW_STATUS));
3393                         break;
3394                 }
3395                 open_view(view, request, OPEN_DEFAULT);
3396                 break;
3398         case REQ_VIEW_STATUS:
3399                 if (opt_is_inside_work_tree == FALSE) {
3400                         report("The status view requires a working tree");
3401                         break;
3402                 }
3403                 open_view(view, request, OPEN_DEFAULT);
3404                 break;
3406         case REQ_VIEW_MAIN:
3407         case REQ_VIEW_DIFF:
3408         case REQ_VIEW_LOG:
3409         case REQ_VIEW_TREE:
3410         case REQ_VIEW_HELP:
3411         case REQ_VIEW_BRANCH:
3412                 open_view(view, request, OPEN_DEFAULT);
3413                 break;
3415         case REQ_NEXT:
3416         case REQ_PREVIOUS:
3417                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3419                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3420                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3421                    (view == VIEW(REQ_VIEW_DIFF) &&
3422                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3423                    (view == VIEW(REQ_VIEW_STAGE) &&
3424                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3425                    (view == VIEW(REQ_VIEW_BLOB) &&
3426                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3427                    (view == VIEW(REQ_VIEW_MAIN) &&
3428                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3429                         int line;
3431                         view = view->parent;
3432                         line = view->lineno;
3433                         move_view(view, request);
3434                         if (view_is_displayed(view))
3435                                 update_view_title(view);
3436                         if (line != view->lineno)
3437                                 view->ops->request(view, REQ_ENTER,
3438                                                    &view->line[view->lineno]);
3440                 } else {
3441                         move_view(view, request);
3442                 }
3443                 break;
3445         case REQ_VIEW_NEXT:
3446         {
3447                 int nviews = displayed_views();
3448                 int next_view = (current_view + 1) % nviews;
3450                 if (next_view == current_view) {
3451                         report("Only one view is displayed");
3452                         break;
3453                 }
3455                 current_view = next_view;
3456                 /* Blur out the title of the previous view. */
3457                 update_view_title(view);
3458                 report("");
3459                 break;
3460         }
3461         case REQ_REFRESH:
3462                 report("Refreshing is not yet supported for the %s view", view->name);
3463                 break;
3465         case REQ_MAXIMIZE:
3466                 if (displayed_views() == 2)
3467                         maximize_view(view);
3468                 break;
3470         case REQ_OPTIONS:
3471                 open_option_menu();
3472                 break;
3474         case REQ_TOGGLE_LINENO:
3475                 toggle_view_option(&opt_line_number, "line numbers");
3476                 break;
3478         case REQ_TOGGLE_DATE:
3479                 toggle_date_option(&opt_date);
3480                 break;
3482         case REQ_TOGGLE_AUTHOR:
3483                 toggle_view_option(&opt_author, "author display");
3484                 break;
3486         case REQ_TOGGLE_REV_GRAPH:
3487                 toggle_view_option(&opt_rev_graph, "revision graph display");
3488                 break;
3490         case REQ_TOGGLE_REFS:
3491                 toggle_view_option(&opt_show_refs, "reference display");
3492                 break;
3494         case REQ_TOGGLE_SORT_FIELD:
3495         case REQ_TOGGLE_SORT_ORDER:
3496                 report("Sorting is not yet supported for the %s view", view->name);
3497                 break;
3499         case REQ_SEARCH:
3500         case REQ_SEARCH_BACK:
3501                 search_view(view, request);
3502                 break;
3504         case REQ_FIND_NEXT:
3505         case REQ_FIND_PREV:
3506                 find_next(view, request);
3507                 break;
3509         case REQ_STOP_LOADING:
3510                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3511                         view = &views[i];
3512                         if (view->pipe)
3513                                 report("Stopped loading the %s view", view->name),
3514                         end_update(view, TRUE);
3515                 }
3516                 break;
3518         case REQ_SHOW_VERSION:
3519                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3520                 return TRUE;
3522         case REQ_SCREEN_REDRAW:
3523                 redraw_display(TRUE);
3524                 break;
3526         case REQ_EDIT:
3527                 report("Nothing to edit");
3528                 break;
3530         case REQ_ENTER:
3531                 report("Nothing to enter");
3532                 break;
3534         case REQ_VIEW_CLOSE:
3535                 /* XXX: Mark closed views by letting view->parent point to the
3536                  * view itself. Parents to closed view should never be
3537                  * followed. */
3538                 if (view->parent &&
3539                     view->parent->parent != view->parent) {
3540                         maximize_view(view->parent);
3541                         view->parent = view;
3542                         break;
3543                 }
3544                 /* Fall-through */
3545         case REQ_QUIT:
3546                 return FALSE;
3548         default:
3549                 report("Unknown key, press %s for help",
3550                        get_key(view->keymap, REQ_VIEW_HELP));
3551                 return TRUE;
3552         }
3554         return TRUE;
3558 /*
3559  * View backend utilities
3560  */
3562 enum sort_field {
3563         ORDERBY_NAME,
3564         ORDERBY_DATE,
3565         ORDERBY_AUTHOR,
3566 };
3568 struct sort_state {
3569         const enum sort_field *fields;
3570         size_t size, current;
3571         bool reverse;
3572 };
3574 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3575 #define get_sort_field(state) ((state).fields[(state).current])
3576 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3578 static void
3579 sort_view(struct view *view, enum request request, struct sort_state *state,
3580           int (*compare)(const void *, const void *))
3582         switch (request) {
3583         case REQ_TOGGLE_SORT_FIELD:
3584                 state->current = (state->current + 1) % state->size;
3585                 break;
3587         case REQ_TOGGLE_SORT_ORDER:
3588                 state->reverse = !state->reverse;
3589                 break;
3590         default:
3591                 die("Not a sort request");
3592         }
3594         qsort(view->line, view->lines, sizeof(*view->line), compare);
3595         redraw_view(view);
3598 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3600 /* Small author cache to reduce memory consumption. It uses binary
3601  * search to lookup or find place to position new entries. No entries
3602  * are ever freed. */
3603 static const char *
3604 get_author(const char *name)
3606         static const char **authors;
3607         static size_t authors_size;
3608         int from = 0, to = authors_size - 1;
3610         while (from <= to) {
3611                 size_t pos = (to + from) / 2;
3612                 int cmp = strcmp(name, authors[pos]);
3614                 if (!cmp)
3615                         return authors[pos];
3617                 if (cmp < 0)
3618                         to = pos - 1;
3619                 else
3620                         from = pos + 1;
3621         }
3623         if (!realloc_authors(&authors, authors_size, 1))
3624                 return NULL;
3625         name = strdup(name);
3626         if (!name)
3627                 return NULL;
3629         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3630         authors[from] = name;
3631         authors_size++;
3633         return name;
3636 static void
3637 parse_timezone(time_t *time, const char *zone)
3639         long tz;
3641         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3642         tz += ('0' - zone[2]) * 60 * 60;
3643         tz += ('0' - zone[3]) * 60;
3644         tz += ('0' - zone[4]);
3646         if (zone[0] == '-')
3647                 tz = -tz;
3649         *time -= tz;
3652 /* Parse author lines where the name may be empty:
3653  *      author  <email@address.tld> 1138474660 +0100
3654  */
3655 static void
3656 parse_author_line(char *ident, const char **author, time_t *time)
3658         char *nameend = strchr(ident, '<');
3659         char *emailend = strchr(ident, '>');
3661         if (nameend && emailend)
3662                 *nameend = *emailend = 0;
3663         ident = chomp_string(ident);
3664         if (!*ident) {
3665                 if (nameend)
3666                         ident = chomp_string(nameend + 1);
3667                 if (!*ident)
3668                         ident = "Unknown";
3669         }
3671         *author = get_author(ident);
3673         /* Parse epoch and timezone */
3674         if (emailend && emailend[1] == ' ') {
3675                 char *secs = emailend + 2;
3676                 char *zone = strchr(secs, ' ');
3678                 *time = (time_t) atol(secs);
3680                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3681                         parse_timezone(time, zone + 1);
3682         }
3685 static bool
3686 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3688         char rev[SIZEOF_REV];
3689         const char *revlist_argv[] = {
3690                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3691         };
3692         struct menu_item *items;
3693         char text[SIZEOF_STR];
3694         bool ok = TRUE;
3695         int i;
3697         items = calloc(*parents + 1, sizeof(*items));
3698         if (!items)
3699                 return FALSE;
3701         for (i = 0; i < *parents; i++) {
3702                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3703                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3704                     !(items[i].text = strdup(text))) {
3705                         ok = FALSE;
3706                         break;
3707                 }
3708         }
3710         if (ok) {
3711                 *parents = 0;
3712                 ok = prompt_menu("Select parent", items, parents);
3713         }
3714         for (i = 0; items[i].text; i++)
3715                 free((char *) items[i].text);
3716         free(items);
3717         return ok;
3720 static bool
3721 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3723         char buf[SIZEOF_STR * 4];
3724         const char *revlist_argv[] = {
3725                 "git", "log", "--no-color", "-1",
3726                         "--pretty=format:%P", id, "--", path, NULL
3727         };
3728         int parents;
3730         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3731             (parents = strlen(buf) / 40) < 0) {
3732                 report("Failed to get parent information");
3733                 return FALSE;
3735         } else if (parents == 0) {
3736                 if (path)
3737                         report("Path '%s' does not exist in the parent", path);
3738                 else
3739                         report("The selected commit has no parents");
3740                 return FALSE;
3741         }
3743         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3744                 return FALSE;
3746         string_copy_rev(rev, &buf[41 * parents]);
3747         return TRUE;
3750 /*
3751  * Pager backend
3752  */
3754 static bool
3755 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3757         char text[SIZEOF_STR];
3759         if (opt_line_number && draw_lineno(view, lineno))
3760                 return TRUE;
3762         string_expand(text, sizeof(text), line->data, opt_tab_size);
3763         draw_text(view, line->type, text, TRUE);
3764         return TRUE;
3767 static bool
3768 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3770         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3771         char ref[SIZEOF_STR];
3773         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3774                 return TRUE;
3776         /* This is the only fatal call, since it can "corrupt" the buffer. */
3777         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3778                 return FALSE;
3780         return TRUE;
3783 static void
3784 add_pager_refs(struct view *view, struct line *line)
3786         char buf[SIZEOF_STR];
3787         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3788         struct ref_list *list;
3789         size_t bufpos = 0, i;
3790         const char *sep = "Refs: ";
3791         bool is_tag = FALSE;
3793         assert(line->type == LINE_COMMIT);
3795         list = get_ref_list(commit_id);
3796         if (!list) {
3797                 if (view == VIEW(REQ_VIEW_DIFF))
3798                         goto try_add_describe_ref;
3799                 return;
3800         }
3802         for (i = 0; i < list->size; i++) {
3803                 struct ref *ref = list->refs[i];
3804                 const char *fmt = ref->tag    ? "%s[%s]" :
3805                                   ref->remote ? "%s<%s>" : "%s%s";
3807                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3808                         return;
3809                 sep = ", ";
3810                 if (ref->tag)
3811                         is_tag = TRUE;
3812         }
3814         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3815 try_add_describe_ref:
3816                 /* Add <tag>-g<commit_id> "fake" reference. */
3817                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3818                         return;
3819         }
3821         if (bufpos == 0)
3822                 return;
3824         add_line_text(view, buf, LINE_PP_REFS);
3827 static bool
3828 pager_read(struct view *view, char *data)
3830         struct line *line;
3832         if (!data)
3833                 return TRUE;
3835         line = add_line_text(view, data, get_line_type(data));
3836         if (!line)
3837                 return FALSE;
3839         if (line->type == LINE_COMMIT &&
3840             (view == VIEW(REQ_VIEW_DIFF) ||
3841              view == VIEW(REQ_VIEW_LOG)))
3842                 add_pager_refs(view, line);
3844         return TRUE;
3847 static enum request
3848 pager_request(struct view *view, enum request request, struct line *line)
3850         int split = 0;
3852         if (request != REQ_ENTER)
3853                 return request;
3855         if (line->type == LINE_COMMIT &&
3856            (view == VIEW(REQ_VIEW_LOG) ||
3857             view == VIEW(REQ_VIEW_PAGER))) {
3858                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3859                 split = 1;
3860         }
3862         /* Always scroll the view even if it was split. That way
3863          * you can use Enter to scroll through the log view and
3864          * split open each commit diff. */
3865         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3867         /* FIXME: A minor workaround. Scrolling the view will call report("")
3868          * but if we are scrolling a non-current view this won't properly
3869          * update the view title. */
3870         if (split)
3871                 update_view_title(view);
3873         return REQ_NONE;
3876 static bool
3877 pager_grep(struct view *view, struct line *line)
3879         const char *text[] = { line->data, NULL };
3881         return grep_text(view, text);
3884 static void
3885 pager_select(struct view *view, struct line *line)
3887         if (line->type == LINE_COMMIT) {
3888                 char *text = (char *)line->data + STRING_SIZE("commit ");
3890                 if (view != VIEW(REQ_VIEW_PAGER))
3891                         string_copy_rev(view->ref, text);
3892                 string_copy_rev(ref_commit, text);
3893         }
3896 static struct view_ops pager_ops = {
3897         "line",
3898         NULL,
3899         NULL,
3900         pager_read,
3901         pager_draw,
3902         pager_request,
3903         pager_grep,
3904         pager_select,
3905 };
3907 static const char *log_argv[SIZEOF_ARG] = {
3908         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3909 };
3911 static enum request
3912 log_request(struct view *view, enum request request, struct line *line)
3914         switch (request) {
3915         case REQ_REFRESH:
3916                 load_refs();
3917                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3918                 return REQ_NONE;
3919         default:
3920                 return pager_request(view, request, line);
3921         }
3924 static struct view_ops log_ops = {
3925         "line",
3926         log_argv,
3927         NULL,
3928         pager_read,
3929         pager_draw,
3930         log_request,
3931         pager_grep,
3932         pager_select,
3933 };
3935 static const char *diff_argv[SIZEOF_ARG] = {
3936         "git", "show", "--pretty=fuller", "--no-color", "--root",
3937                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3938 };
3940 static struct view_ops diff_ops = {
3941         "line",
3942         diff_argv,
3943         NULL,
3944         pager_read,
3945         pager_draw,
3946         pager_request,
3947         pager_grep,
3948         pager_select,
3949 };
3951 /*
3952  * Help backend
3953  */
3955 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3957 static char *
3958 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3960         int bufpos;
3962         for (bufpos = 0; bufpos <= namelen; bufpos++) {
3963                 buf[bufpos] = tolower(name[bufpos]);
3964                 if (buf[bufpos] == '_')
3965                         buf[bufpos] = '-';
3966         }
3968         buf[bufpos] = 0;
3969         return buf;
3972 #define help_keymap_name(buf, keymap) \
3973         help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3975 static bool
3976 help_open_keymap_title(struct view *view, enum keymap keymap)
3978         char buf[SIZEOF_STR];
3979         struct line *line;
3981         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3982                                help_keymap_hidden[keymap] ? '+' : '-',
3983                                help_keymap_name(buf, keymap));
3984         if (line)
3985                 line->other = keymap;
3987         return help_keymap_hidden[keymap];
3990 static void
3991 help_open_keymap(struct view *view, enum keymap keymap)
3993         const char *group = NULL;
3994         char buf[SIZEOF_STR];
3995         size_t bufpos;
3996         bool add_title = TRUE;
3997         int i;
3999         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4000                 const char *key = NULL;
4002                 if (req_info[i].request == REQ_NONE)
4003                         continue;
4005                 if (!req_info[i].request) {
4006                         group = req_info[i].help;
4007                         continue;
4008                 }
4010                 key = get_keys(keymap, req_info[i].request, TRUE);
4011                 if (!key || !*key)
4012                         continue;
4014                 if (add_title && help_open_keymap_title(view, keymap))
4015                         return;
4016                 add_title = false;
4018                 if (group) {
4019                         add_line_text(view, group, LINE_HELP_GROUP);
4020                         group = NULL;
4021                 }
4023                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4024                                 help_name(buf, req_info[i].name, req_info[i].namelen),
4025                                 req_info[i].help);
4026         }
4028         group = "External commands:";
4030         for (i = 0; i < run_requests; i++) {
4031                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4032                 const char *key;
4033                 int argc;
4035                 if (!req || req->keymap != keymap)
4036                         continue;
4038                 key = get_key_name(req->key);
4039                 if (!*key)
4040                         key = "(no key defined)";
4042                 if (add_title && help_open_keymap_title(view, keymap))
4043                         return;
4044                 if (group) {
4045                         add_line_text(view, group, LINE_HELP_GROUP);
4046                         group = NULL;
4047                 }
4049                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4050                         if (!string_format_from(buf, &bufpos, "%s%s",
4051                                                 argc ? " " : "", req->argv[argc]))
4052                                 return;
4054                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4055         }
4058 static bool
4059 help_open(struct view *view)
4061         enum keymap keymap;
4063         reset_view(view);
4064         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4065         add_line_text(view, "", LINE_DEFAULT);
4067         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4068                 help_open_keymap(view, keymap);
4070         return TRUE;
4073 static enum request
4074 help_request(struct view *view, enum request request, struct line *line)
4076         switch (request) {
4077         case REQ_ENTER:
4078                 if (line->type == LINE_HELP_KEYMAP) {
4079                         help_keymap_hidden[line->other] =
4080                                 !help_keymap_hidden[line->other];
4081                         view->p_restore = TRUE;
4082                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4083                 }
4085                 return REQ_NONE;
4086         default:
4087                 return pager_request(view, request, line);
4088         }
4091 static struct view_ops help_ops = {
4092         "line",
4093         NULL,
4094         help_open,
4095         NULL,
4096         pager_draw,
4097         help_request,
4098         pager_grep,
4099         pager_select,
4100 };
4103 /*
4104  * Tree backend
4105  */
4107 struct tree_stack_entry {
4108         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4109         unsigned long lineno;           /* Line number to restore */
4110         char *name;                     /* Position of name in opt_path */
4111 };
4113 /* The top of the path stack. */
4114 static struct tree_stack_entry *tree_stack = NULL;
4115 unsigned long tree_lineno = 0;
4117 static void
4118 pop_tree_stack_entry(void)
4120         struct tree_stack_entry *entry = tree_stack;
4122         tree_lineno = entry->lineno;
4123         entry->name[0] = 0;
4124         tree_stack = entry->prev;
4125         free(entry);
4128 static void
4129 push_tree_stack_entry(const char *name, unsigned long lineno)
4131         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4132         size_t pathlen = strlen(opt_path);
4134         if (!entry)
4135                 return;
4137         entry->prev = tree_stack;
4138         entry->name = opt_path + pathlen;
4139         tree_stack = entry;
4141         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4142                 pop_tree_stack_entry();
4143                 return;
4144         }
4146         /* Move the current line to the first tree entry. */
4147         tree_lineno = 1;
4148         entry->lineno = lineno;
4151 /* Parse output from git-ls-tree(1):
4152  *
4153  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4154  */
4156 #define SIZEOF_TREE_ATTR \
4157         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4159 #define SIZEOF_TREE_MODE \
4160         STRING_SIZE("100644 ")
4162 #define TREE_ID_OFFSET \
4163         STRING_SIZE("100644 blob ")
4165 struct tree_entry {
4166         char id[SIZEOF_REV];
4167         mode_t mode;
4168         time_t time;                    /* Date from the author ident. */
4169         const char *author;             /* Author of the commit. */
4170         char name[1];
4171 };
4173 static const char *
4174 tree_path(const struct line *line)
4176         return ((struct tree_entry *) line->data)->name;
4179 static int
4180 tree_compare_entry(const struct line *line1, const struct line *line2)
4182         if (line1->type != line2->type)
4183                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4184         return strcmp(tree_path(line1), tree_path(line2));
4187 static const enum sort_field tree_sort_fields[] = {
4188         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4189 };
4190 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4192 static int
4193 tree_compare(const void *l1, const void *l2)
4195         const struct line *line1 = (const struct line *) l1;
4196         const struct line *line2 = (const struct line *) l2;
4197         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4198         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4200         if (line1->type == LINE_TREE_HEAD)
4201                 return -1;
4202         if (line2->type == LINE_TREE_HEAD)
4203                 return 1;
4205         switch (get_sort_field(tree_sort_state)) {
4206         case ORDERBY_DATE:
4207                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4209         case ORDERBY_AUTHOR:
4210                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4212         case ORDERBY_NAME:
4213         default:
4214                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4215         }
4219 static struct line *
4220 tree_entry(struct view *view, enum line_type type, const char *path,
4221            const char *mode, const char *id)
4223         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4224         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4226         if (!entry || !line) {
4227                 free(entry);
4228                 return NULL;
4229         }
4231         strncpy(entry->name, path, strlen(path));
4232         if (mode)
4233                 entry->mode = strtoul(mode, NULL, 8);
4234         if (id)
4235                 string_copy_rev(entry->id, id);
4237         return line;
4240 static bool
4241 tree_read_date(struct view *view, char *text, bool *read_date)
4243         static const char *author_name;
4244         static time_t author_time;
4246         if (!text && *read_date) {
4247                 *read_date = FALSE;
4248                 return TRUE;
4250         } else if (!text) {
4251                 char *path = *opt_path ? opt_path : ".";
4252                 /* Find next entry to process */
4253                 const char *log_file[] = {
4254                         "git", "log", "--no-color", "--pretty=raw",
4255                                 "--cc", "--raw", view->id, "--", path, NULL
4256                 };
4257                 struct io io = {};
4259                 if (!view->lines) {
4260                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4261                         report("Tree is empty");
4262                         return TRUE;
4263                 }
4265                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4266                         report("Failed to load tree data");
4267                         return TRUE;
4268                 }
4270                 done_io(view->pipe);
4271                 view->io = io;
4272                 *read_date = TRUE;
4273                 return FALSE;
4275         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4276                 parse_author_line(text + STRING_SIZE("author "),
4277                                   &author_name, &author_time);
4279         } else if (*text == ':') {
4280                 char *pos;
4281                 size_t annotated = 1;
4282                 size_t i;
4284                 pos = strchr(text, '\t');
4285                 if (!pos)
4286                         return TRUE;
4287                 text = pos + 1;
4288                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4289                         text += strlen(opt_path);
4290                 pos = strchr(text, '/');
4291                 if (pos)
4292                         *pos = 0;
4294                 for (i = 1; i < view->lines; i++) {
4295                         struct line *line = &view->line[i];
4296                         struct tree_entry *entry = line->data;
4298                         annotated += !!entry->author;
4299                         if (entry->author || strcmp(entry->name, text))
4300                                 continue;
4302                         entry->author = author_name;
4303                         entry->time = author_time;
4304                         line->dirty = 1;
4305                         break;
4306                 }
4308                 if (annotated == view->lines)
4309                         kill_io(view->pipe);
4310         }
4311         return TRUE;
4314 static bool
4315 tree_read(struct view *view, char *text)
4317         static bool read_date = FALSE;
4318         struct tree_entry *data;
4319         struct line *entry, *line;
4320         enum line_type type;
4321         size_t textlen = text ? strlen(text) : 0;
4322         char *path = text + SIZEOF_TREE_ATTR;
4324         if (read_date || !text)
4325                 return tree_read_date(view, text, &read_date);
4327         if (textlen <= SIZEOF_TREE_ATTR)
4328                 return FALSE;
4329         if (view->lines == 0 &&
4330             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4331                 return FALSE;
4333         /* Strip the path part ... */
4334         if (*opt_path) {
4335                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4336                 size_t striplen = strlen(opt_path);
4338                 if (pathlen > striplen)
4339                         memmove(path, path + striplen,
4340                                 pathlen - striplen + 1);
4342                 /* Insert "link" to parent directory. */
4343                 if (view->lines == 1 &&
4344                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4345                         return FALSE;
4346         }
4348         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4349         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4350         if (!entry)
4351                 return FALSE;
4352         data = entry->data;
4354         /* Skip "Directory ..." and ".." line. */
4355         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4356                 if (tree_compare_entry(line, entry) <= 0)
4357                         continue;
4359                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4361                 line->data = data;
4362                 line->type = type;
4363                 for (; line <= entry; line++)
4364                         line->dirty = line->cleareol = 1;
4365                 return TRUE;
4366         }
4368         if (tree_lineno > view->lineno) {
4369                 view->lineno = tree_lineno;
4370                 tree_lineno = 0;
4371         }
4373         return TRUE;
4376 static bool
4377 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4379         struct tree_entry *entry = line->data;
4381         if (line->type == LINE_TREE_HEAD) {
4382                 if (draw_text(view, line->type, "Directory path /", TRUE))
4383                         return TRUE;
4384         } else {
4385                 if (draw_mode(view, entry->mode))
4386                         return TRUE;
4388                 if (opt_author && draw_author(view, entry->author))
4389                         return TRUE;
4391                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4392                         return TRUE;
4393         }
4394         if (draw_text(view, line->type, entry->name, TRUE))
4395                 return TRUE;
4396         return TRUE;
4399 static void
4400 open_blob_editor()
4402         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4403         int fd = mkstemp(file);
4405         if (fd == -1)
4406                 report("Failed to create temporary file");
4407         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4408                 report("Failed to save blob data to file");
4409         else
4410                 open_editor(FALSE, file);
4411         if (fd != -1)
4412                 unlink(file);
4415 static enum request
4416 tree_request(struct view *view, enum request request, struct line *line)
4418         enum open_flags flags;
4420         switch (request) {
4421         case REQ_VIEW_BLAME:
4422                 if (line->type != LINE_TREE_FILE) {
4423                         report("Blame only supported for files");
4424                         return REQ_NONE;
4425                 }
4427                 string_copy(opt_ref, view->vid);
4428                 return request;
4430         case REQ_EDIT:
4431                 if (line->type != LINE_TREE_FILE) {
4432                         report("Edit only supported for files");
4433                 } else if (!is_head_commit(view->vid)) {
4434                         open_blob_editor();
4435                 } else {
4436                         open_editor(TRUE, opt_file);
4437                 }
4438                 return REQ_NONE;
4440         case REQ_TOGGLE_SORT_FIELD:
4441         case REQ_TOGGLE_SORT_ORDER:
4442                 sort_view(view, request, &tree_sort_state, tree_compare);
4443                 return REQ_NONE;
4445         case REQ_PARENT:
4446                 if (!*opt_path) {
4447                         /* quit view if at top of tree */
4448                         return REQ_VIEW_CLOSE;
4449                 }
4450                 /* fake 'cd  ..' */
4451                 line = &view->line[1];
4452                 break;
4454         case REQ_ENTER:
4455                 break;
4457         default:
4458                 return request;
4459         }
4461         /* Cleanup the stack if the tree view is at a different tree. */
4462         while (!*opt_path && tree_stack)
4463                 pop_tree_stack_entry();
4465         switch (line->type) {
4466         case LINE_TREE_DIR:
4467                 /* Depending on whether it is a subdirectory or parent link
4468                  * mangle the path buffer. */
4469                 if (line == &view->line[1] && *opt_path) {
4470                         pop_tree_stack_entry();
4472                 } else {
4473                         const char *basename = tree_path(line);
4475                         push_tree_stack_entry(basename, view->lineno);
4476                 }
4478                 /* Trees and subtrees share the same ID, so they are not not
4479                  * unique like blobs. */
4480                 flags = OPEN_RELOAD;
4481                 request = REQ_VIEW_TREE;
4482                 break;
4484         case LINE_TREE_FILE:
4485                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4486                 request = REQ_VIEW_BLOB;
4487                 break;
4489         default:
4490                 return REQ_NONE;
4491         }
4493         open_view(view, request, flags);
4494         if (request == REQ_VIEW_TREE)
4495                 view->lineno = tree_lineno;
4497         return REQ_NONE;
4500 static bool
4501 tree_grep(struct view *view, struct line *line)
4503         struct tree_entry *entry = line->data;
4504         const char *text[] = {
4505                 entry->name,
4506                 opt_author ? entry->author : "",
4507                 opt_date ? mkdate(&entry->time) : "",
4508                 NULL
4509         };
4511         return grep_text(view, text);
4514 static void
4515 tree_select(struct view *view, struct line *line)
4517         struct tree_entry *entry = line->data;
4519         if (line->type == LINE_TREE_FILE) {
4520                 string_copy_rev(ref_blob, entry->id);
4521                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4523         } else if (line->type != LINE_TREE_DIR) {
4524                 return;
4525         }
4527         string_copy_rev(view->ref, entry->id);
4530 static bool
4531 tree_prepare(struct view *view)
4533         if (view->lines == 0 && opt_prefix[0]) {
4534                 char *pos = opt_prefix;
4536                 while (pos && *pos) {
4537                         char *end = strchr(pos, '/');
4539                         if (end)
4540                                 *end = 0;
4541                         push_tree_stack_entry(pos, 0);
4542                         pos = end;
4543                         if (end) {
4544                                 *end = '/';
4545                                 pos++;
4546                         }
4547                 }
4549         } else if (strcmp(view->vid, view->id)) {
4550                 opt_path[0] = 0;
4551         }
4553         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4556 static const char *tree_argv[SIZEOF_ARG] = {
4557         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4558 };
4560 static struct view_ops tree_ops = {
4561         "file",
4562         tree_argv,
4563         NULL,
4564         tree_read,
4565         tree_draw,
4566         tree_request,
4567         tree_grep,
4568         tree_select,
4569         tree_prepare,
4570 };
4572 static bool
4573 blob_read(struct view *view, char *line)
4575         if (!line)
4576                 return TRUE;
4577         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4580 static enum request
4581 blob_request(struct view *view, enum request request, struct line *line)
4583         switch (request) {
4584         case REQ_EDIT:
4585                 open_blob_editor();
4586                 return REQ_NONE;
4587         default:
4588                 return pager_request(view, request, line);
4589         }
4592 static const char *blob_argv[SIZEOF_ARG] = {
4593         "git", "cat-file", "blob", "%(blob)", NULL
4594 };
4596 static struct view_ops blob_ops = {
4597         "line",
4598         blob_argv,
4599         NULL,
4600         blob_read,
4601         pager_draw,
4602         blob_request,
4603         pager_grep,
4604         pager_select,
4605 };
4607 /*
4608  * Blame backend
4609  *
4610  * Loading the blame view is a two phase job:
4611  *
4612  *  1. File content is read either using opt_file from the
4613  *     filesystem or using git-cat-file.
4614  *  2. Then blame information is incrementally added by
4615  *     reading output from git-blame.
4616  */
4618 static const char *blame_head_argv[] = {
4619         "git", "blame", "--incremental", "--", "%(file)", NULL
4620 };
4622 static const char *blame_ref_argv[] = {
4623         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4624 };
4626 static const char *blame_cat_file_argv[] = {
4627         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4628 };
4630 struct blame_commit {
4631         char id[SIZEOF_REV];            /* SHA1 ID. */
4632         char title[128];                /* First line of the commit message. */
4633         const char *author;             /* Author of the commit. */
4634         time_t time;                    /* Date from the author ident. */
4635         char filename[128];             /* Name of file. */
4636         bool has_previous;              /* Was a "previous" line detected. */
4637 };
4639 struct blame {
4640         struct blame_commit *commit;
4641         unsigned long lineno;
4642         char text[1];
4643 };
4645 static bool
4646 blame_open(struct view *view)
4648         char path[SIZEOF_STR];
4650         if (!view->parent && *opt_prefix) {
4651                 string_copy(path, opt_file);
4652                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4653                         return FALSE;
4654         }
4656         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4657                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4658                         return FALSE;
4659         }
4661         setup_update(view, opt_file);
4662         string_format(view->ref, "%s ...", opt_file);
4664         return TRUE;
4667 static struct blame_commit *
4668 get_blame_commit(struct view *view, const char *id)
4670         size_t i;
4672         for (i = 0; i < view->lines; i++) {
4673                 struct blame *blame = view->line[i].data;
4675                 if (!blame->commit)
4676                         continue;
4678                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4679                         return blame->commit;
4680         }
4682         {
4683                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4685                 if (commit)
4686                         string_ncopy(commit->id, id, SIZEOF_REV);
4687                 return commit;
4688         }
4691 static bool
4692 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4694         const char *pos = *posref;
4696         *posref = NULL;
4697         pos = strchr(pos + 1, ' ');
4698         if (!pos || !isdigit(pos[1]))
4699                 return FALSE;
4700         *number = atoi(pos + 1);
4701         if (*number < min || *number > max)
4702                 return FALSE;
4704         *posref = pos;
4705         return TRUE;
4708 static struct blame_commit *
4709 parse_blame_commit(struct view *view, const char *text, int *blamed)
4711         struct blame_commit *commit;
4712         struct blame *blame;
4713         const char *pos = text + SIZEOF_REV - 2;
4714         size_t orig_lineno = 0;
4715         size_t lineno;
4716         size_t group;
4718         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4719                 return NULL;
4721         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4722             !parse_number(&pos, &lineno, 1, view->lines) ||
4723             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4724                 return NULL;
4726         commit = get_blame_commit(view, text);
4727         if (!commit)
4728                 return NULL;
4730         *blamed += group;
4731         while (group--) {
4732                 struct line *line = &view->line[lineno + group - 1];
4734                 blame = line->data;
4735                 blame->commit = commit;
4736                 blame->lineno = orig_lineno + group - 1;
4737                 line->dirty = 1;
4738         }
4740         return commit;
4743 static bool
4744 blame_read_file(struct view *view, const char *line, bool *read_file)
4746         if (!line) {
4747                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4748                 struct io io = {};
4750                 if (view->lines == 0 && !view->parent)
4751                         die("No blame exist for %s", view->vid);
4753                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4754                         report("Failed to load blame data");
4755                         return TRUE;
4756                 }
4758                 done_io(view->pipe);
4759                 view->io = io;
4760                 *read_file = FALSE;
4761                 return FALSE;
4763         } else {
4764                 size_t linelen = strlen(line);
4765                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4767                 if (!blame)
4768                         return FALSE;
4770                 blame->commit = NULL;
4771                 strncpy(blame->text, line, linelen);
4772                 blame->text[linelen] = 0;
4773                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4774         }
4777 static bool
4778 match_blame_header(const char *name, char **line)
4780         size_t namelen = strlen(name);
4781         bool matched = !strncmp(name, *line, namelen);
4783         if (matched)
4784                 *line += namelen;
4786         return matched;
4789 static bool
4790 blame_read(struct view *view, char *line)
4792         static struct blame_commit *commit = NULL;
4793         static int blamed = 0;
4794         static bool read_file = TRUE;
4796         if (read_file)
4797                 return blame_read_file(view, line, &read_file);
4799         if (!line) {
4800                 /* Reset all! */
4801                 commit = NULL;
4802                 blamed = 0;
4803                 read_file = TRUE;
4804                 string_format(view->ref, "%s", view->vid);
4805                 if (view_is_displayed(view)) {
4806                         update_view_title(view);
4807                         redraw_view_from(view, 0);
4808                 }
4809                 return TRUE;
4810         }
4812         if (!commit) {
4813                 commit = parse_blame_commit(view, line, &blamed);
4814                 string_format(view->ref, "%s %2d%%", view->vid,
4815                               view->lines ? blamed * 100 / view->lines : 0);
4817         } else if (match_blame_header("author ", &line)) {
4818                 commit->author = get_author(line);
4820         } else if (match_blame_header("author-time ", &line)) {
4821                 commit->time = (time_t) atol(line);
4823         } else if (match_blame_header("author-tz ", &line)) {
4824                 parse_timezone(&commit->time, line);
4826         } else if (match_blame_header("summary ", &line)) {
4827                 string_ncopy(commit->title, line, strlen(line));
4829         } else if (match_blame_header("previous ", &line)) {
4830                 commit->has_previous = TRUE;
4832         } else if (match_blame_header("filename ", &line)) {
4833                 string_ncopy(commit->filename, line, strlen(line));
4834                 commit = NULL;
4835         }
4837         return TRUE;
4840 static bool
4841 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4843         struct blame *blame = line->data;
4844         time_t *time = NULL;
4845         const char *id = NULL, *author = NULL;
4846         char text[SIZEOF_STR];
4848         if (blame->commit && *blame->commit->filename) {
4849                 id = blame->commit->id;
4850                 author = blame->commit->author;
4851                 time = &blame->commit->time;
4852         }
4854         if (opt_date && draw_date(view, time))
4855                 return TRUE;
4857         if (opt_author && draw_author(view, author))
4858                 return TRUE;
4860         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4861                 return TRUE;
4863         if (draw_lineno(view, lineno))
4864                 return TRUE;
4866         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4867         draw_text(view, LINE_DEFAULT, text, TRUE);
4868         return TRUE;
4871 static bool
4872 check_blame_commit(struct blame *blame, bool check_null_id)
4874         if (!blame->commit)
4875                 report("Commit data not loaded yet");
4876         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4877                 report("No commit exist for the selected line");
4878         else
4879                 return TRUE;
4880         return FALSE;
4883 static void
4884 setup_blame_parent_line(struct view *view, struct blame *blame)
4886         const char *diff_tree_argv[] = {
4887                 "git", "diff-tree", "-U0", blame->commit->id,
4888                         "--", blame->commit->filename, NULL
4889         };
4890         struct io io = {};
4891         int parent_lineno = -1;
4892         int blamed_lineno = -1;
4893         char *line;
4895         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4896                 return;
4898         while ((line = io_get(&io, '\n', TRUE))) {
4899                 if (*line == '@') {
4900                         char *pos = strchr(line, '+');
4902                         parent_lineno = atoi(line + 4);
4903                         if (pos)
4904                                 blamed_lineno = atoi(pos + 1);
4906                 } else if (*line == '+' && parent_lineno != -1) {
4907                         if (blame->lineno == blamed_lineno - 1 &&
4908                             !strcmp(blame->text, line + 1)) {
4909                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4910                                 break;
4911                         }
4912                         blamed_lineno++;
4913                 }
4914         }
4916         done_io(&io);
4919 static enum request
4920 blame_request(struct view *view, enum request request, struct line *line)
4922         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4923         struct blame *blame = line->data;
4925         switch (request) {
4926         case REQ_VIEW_BLAME:
4927                 if (check_blame_commit(blame, TRUE)) {
4928                         string_copy(opt_ref, blame->commit->id);
4929                         string_copy(opt_file, blame->commit->filename);
4930                         if (blame->lineno)
4931                                 view->lineno = blame->lineno;
4932                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4933                 }
4934                 break;
4936         case REQ_PARENT:
4937                 if (check_blame_commit(blame, TRUE) &&
4938                     select_commit_parent(blame->commit->id, opt_ref,
4939                                          blame->commit->filename)) {
4940                         string_copy(opt_file, blame->commit->filename);
4941                         setup_blame_parent_line(view, blame);
4942                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4943                 }
4944                 break;
4946         case REQ_ENTER:
4947                 if (!check_blame_commit(blame, FALSE))
4948                         break;
4950                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4951                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4952                         break;
4954                 if (!strcmp(blame->commit->id, NULL_ID)) {
4955                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4956                         const char *diff_index_argv[] = {
4957                                 "git", "diff-index", "--root", "--patch-with-stat",
4958                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4959                         };
4961                         if (!blame->commit->has_previous) {
4962                                 diff_index_argv[1] = "diff";
4963                                 diff_index_argv[2] = "--no-color";
4964                                 diff_index_argv[6] = "--";
4965                                 diff_index_argv[7] = "/dev/null";
4966                         }
4968                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4969                                 report("Failed to allocate diff command");
4970                                 break;
4971                         }
4972                         flags |= OPEN_PREPARED;
4973                 }
4975                 open_view(view, REQ_VIEW_DIFF, flags);
4976                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4977                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4978                 break;
4980         default:
4981                 return request;
4982         }
4984         return REQ_NONE;
4987 static bool
4988 blame_grep(struct view *view, struct line *line)
4990         struct blame *blame = line->data;
4991         struct blame_commit *commit = blame->commit;
4992         const char *text[] = {
4993                 blame->text,
4994                 commit ? commit->title : "",
4995                 commit ? commit->id : "",
4996                 commit && opt_author ? commit->author : "",
4997                 commit && opt_date ? mkdate(&commit->time) : "",
4998                 NULL
4999         };
5001         return grep_text(view, text);
5004 static void
5005 blame_select(struct view *view, struct line *line)
5007         struct blame *blame = line->data;
5008         struct blame_commit *commit = blame->commit;
5010         if (!commit)
5011                 return;
5013         if (!strcmp(commit->id, NULL_ID))
5014                 string_ncopy(ref_commit, "HEAD", 4);
5015         else
5016                 string_copy_rev(ref_commit, commit->id);
5019 static struct view_ops blame_ops = {
5020         "line",
5021         NULL,
5022         blame_open,
5023         blame_read,
5024         blame_draw,
5025         blame_request,
5026         blame_grep,
5027         blame_select,
5028 };
5030 /*
5031  * Branch backend
5032  */
5034 struct branch {
5035         const char *author;             /* Author of the last commit. */
5036         time_t time;                    /* Date of the last activity. */
5037         const struct ref *ref;          /* Name and commit ID information. */
5038 };
5040 static const struct ref branch_all;
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 == &branch_all ? "All branches" : branch->ref->name, TRUE);
5079         return TRUE;
5082 static enum request
5083 branch_request(struct view *view, enum request request, struct line *line)
5085         struct branch *branch = line->data;
5087         switch (request) {
5088         case REQ_REFRESH:
5089                 load_refs();
5090                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5091                 return REQ_NONE;
5093         case REQ_TOGGLE_SORT_FIELD:
5094         case REQ_TOGGLE_SORT_ORDER:
5095                 sort_view(view, request, &branch_sort_state, branch_compare);
5096                 return REQ_NONE;
5098         case REQ_ENTER:
5099                 if (branch->ref == &branch_all) {
5100                         const char *all_branches_argv[] = {
5101                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5102                                       "--topo-order", "--all", NULL
5103                         };
5104                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5106                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5107                                 report("Failed to load view of all branches");
5108                                 return REQ_NONE;
5109                         }
5110                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5111                 } else {
5112                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5113                 }
5114                 return REQ_NONE;
5116         default:
5117                 return request;
5118         }
5121 static bool
5122 branch_read(struct view *view, char *line)
5124         static char id[SIZEOF_REV];
5125         struct branch *reference;
5126         size_t i;
5128         if (!line)
5129                 return TRUE;
5131         switch (get_line_type(line)) {
5132         case LINE_COMMIT:
5133                 string_copy_rev(id, line + STRING_SIZE("commit "));
5134                 return TRUE;
5136         case LINE_AUTHOR:
5137                 for (i = 0, reference = NULL; i < view->lines; i++) {
5138                         struct branch *branch = view->line[i].data;
5140                         if (strcmp(branch->ref->id, id))
5141                                 continue;
5143                         view->line[i].dirty = TRUE;
5144                         if (reference) {
5145                                 branch->author = reference->author;
5146                                 branch->time = reference->time;
5147                                 continue;
5148                         }
5150                         parse_author_line(line + STRING_SIZE("author "),
5151                                           &branch->author, &branch->time);
5152                         reference = branch;
5153                 }
5154                 return TRUE;
5156         default:
5157                 return TRUE;
5158         }
5162 static bool
5163 branch_open_visitor(void *data, const struct ref *ref)
5165         struct view *view = data;
5166         struct branch *branch;
5168         if (ref->tag || ref->ltag || ref->remote)
5169                 return TRUE;
5171         branch = calloc(1, sizeof(*branch));
5172         if (!branch)
5173                 return FALSE;
5175         branch->ref = ref;
5176         return !!add_line_data(view, branch, LINE_DEFAULT);
5179 static bool
5180 branch_open(struct view *view)
5182         const char *branch_log[] = {
5183                 "git", "log", "--no-color", "--pretty=raw",
5184                         "--simplify-by-decoration", "--all", NULL
5185         };
5187         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5188                 report("Failed to load branch data");
5189                 return TRUE;
5190         }
5192         setup_update(view, view->id);
5193         branch_open_visitor(view, &branch_all);
5194         foreach_ref(branch_open_visitor, view);
5195         view->p_restore = TRUE;
5197         return TRUE;
5200 static bool
5201 branch_grep(struct view *view, struct line *line)
5203         struct branch *branch = line->data;
5204         const char *text[] = {
5205                 branch->ref->name,
5206                 branch->author,
5207                 NULL
5208         };
5210         return grep_text(view, text);
5213 static void
5214 branch_select(struct view *view, struct line *line)
5216         struct branch *branch = line->data;
5218         string_copy_rev(view->ref, branch->ref->id);
5219         string_copy_rev(ref_commit, branch->ref->id);
5220         string_copy_rev(ref_head, branch->ref->id);
5223 static struct view_ops branch_ops = {
5224         "branch",
5225         NULL,
5226         branch_open,
5227         branch_read,
5228         branch_draw,
5229         branch_request,
5230         branch_grep,
5231         branch_select,
5232 };
5234 /*
5235  * Status backend
5236  */
5238 struct status {
5239         char status;
5240         struct {
5241                 mode_t mode;
5242                 char rev[SIZEOF_REV];
5243                 char name[SIZEOF_STR];
5244         } old;
5245         struct {
5246                 mode_t mode;
5247                 char rev[SIZEOF_REV];
5248                 char name[SIZEOF_STR];
5249         } new;
5250 };
5252 static char status_onbranch[SIZEOF_STR];
5253 static struct status stage_status;
5254 static enum line_type stage_line_type;
5255 static size_t stage_chunks;
5256 static int *stage_chunk;
5258 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5260 /* This should work even for the "On branch" line. */
5261 static inline bool
5262 status_has_none(struct view *view, struct line *line)
5264         return line < view->line + view->lines && !line[1].data;
5267 /* Get fields from the diff line:
5268  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5269  */
5270 static inline bool
5271 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5273         const char *old_mode = buf +  1;
5274         const char *new_mode = buf +  8;
5275         const char *old_rev  = buf + 15;
5276         const char *new_rev  = buf + 56;
5277         const char *status   = buf + 97;
5279         if (bufsize < 98 ||
5280             old_mode[-1] != ':' ||
5281             new_mode[-1] != ' ' ||
5282             old_rev[-1]  != ' ' ||
5283             new_rev[-1]  != ' ' ||
5284             status[-1]   != ' ')
5285                 return FALSE;
5287         file->status = *status;
5289         string_copy_rev(file->old.rev, old_rev);
5290         string_copy_rev(file->new.rev, new_rev);
5292         file->old.mode = strtoul(old_mode, NULL, 8);
5293         file->new.mode = strtoul(new_mode, NULL, 8);
5295         file->old.name[0] = file->new.name[0] = 0;
5297         return TRUE;
5300 static bool
5301 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5303         struct status *unmerged = NULL;
5304         char *buf;
5305         struct io io = {};
5307         if (!run_io(&io, argv, NULL, IO_RD))
5308                 return FALSE;
5310         add_line_data(view, NULL, type);
5312         while ((buf = io_get(&io, 0, TRUE))) {
5313                 struct status *file = unmerged;
5315                 if (!file) {
5316                         file = calloc(1, sizeof(*file));
5317                         if (!file || !add_line_data(view, file, type))
5318                                 goto error_out;
5319                 }
5321                 /* Parse diff info part. */
5322                 if (status) {
5323                         file->status = status;
5324                         if (status == 'A')
5325                                 string_copy(file->old.rev, NULL_ID);
5327                 } else if (!file->status || file == unmerged) {
5328                         if (!status_get_diff(file, buf, strlen(buf)))
5329                                 goto error_out;
5331                         buf = io_get(&io, 0, TRUE);
5332                         if (!buf)
5333                                 break;
5335                         /* Collapse all modified entries that follow an
5336                          * associated unmerged entry. */
5337                         if (unmerged == file) {
5338                                 unmerged->status = 'U';
5339                                 unmerged = NULL;
5340                         } else if (file->status == 'U') {
5341                                 unmerged = file;
5342                         }
5343                 }
5345                 /* Grab the old name for rename/copy. */
5346                 if (!*file->old.name &&
5347                     (file->status == 'R' || file->status == 'C')) {
5348                         string_ncopy(file->old.name, buf, strlen(buf));
5350                         buf = io_get(&io, 0, TRUE);
5351                         if (!buf)
5352                                 break;
5353                 }
5355                 /* git-ls-files just delivers a NUL separated list of
5356                  * file names similar to the second half of the
5357                  * git-diff-* output. */
5358                 string_ncopy(file->new.name, buf, strlen(buf));
5359                 if (!*file->old.name)
5360                         string_copy(file->old.name, file->new.name);
5361                 file = NULL;
5362         }
5364         if (io_error(&io)) {
5365 error_out:
5366                 done_io(&io);
5367                 return FALSE;
5368         }
5370         if (!view->line[view->lines - 1].data)
5371                 add_line_data(view, NULL, LINE_STAT_NONE);
5373         done_io(&io);
5374         return TRUE;
5377 /* Don't show unmerged entries in the staged section. */
5378 static const char *status_diff_index_argv[] = {
5379         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5380                              "--cached", "-M", "HEAD", NULL
5381 };
5383 static const char *status_diff_files_argv[] = {
5384         "git", "diff-files", "-z", NULL
5385 };
5387 static const char *status_list_other_argv[] = {
5388         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5389 };
5391 static const char *status_list_no_head_argv[] = {
5392         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5393 };
5395 static const char *update_index_argv[] = {
5396         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5397 };
5399 /* Restore the previous line number to stay in the context or select a
5400  * line with something that can be updated. */
5401 static void
5402 status_restore(struct view *view)
5404         if (view->p_lineno >= view->lines)
5405                 view->p_lineno = view->lines - 1;
5406         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5407                 view->p_lineno++;
5408         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5409                 view->p_lineno--;
5411         /* If the above fails, always skip the "On branch" line. */
5412         if (view->p_lineno < view->lines)
5413                 view->lineno = view->p_lineno;
5414         else
5415                 view->lineno = 1;
5417         if (view->lineno < view->offset)
5418                 view->offset = view->lineno;
5419         else if (view->offset + view->height <= view->lineno)
5420                 view->offset = view->lineno - view->height + 1;
5422         view->p_restore = FALSE;
5425 static void
5426 status_update_onbranch(void)
5428         static const char *paths[][2] = {
5429                 { "rebase-apply/rebasing",      "Rebasing" },
5430                 { "rebase-apply/applying",      "Applying mailbox" },
5431                 { "rebase-apply/",              "Rebasing mailbox" },
5432                 { "rebase-merge/interactive",   "Interactive rebase" },
5433                 { "rebase-merge/",              "Rebase merge" },
5434                 { "MERGE_HEAD",                 "Merging" },
5435                 { "BISECT_LOG",                 "Bisecting" },
5436                 { "HEAD",                       "On branch" },
5437         };
5438         char buf[SIZEOF_STR];
5439         struct stat stat;
5440         int i;
5442         if (is_initial_commit()) {
5443                 string_copy(status_onbranch, "Initial commit");
5444                 return;
5445         }
5447         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5448                 char *head = opt_head;
5450                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5451                     lstat(buf, &stat) < 0)
5452                         continue;
5454                 if (!*opt_head) {
5455                         struct io io = {};
5457                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5458                             io_read_buf(&io, buf, sizeof(buf))) {
5459                                 head = buf;
5460                                 if (!prefixcmp(head, "refs/heads/"))
5461                                         head += STRING_SIZE("refs/heads/");
5462                         }
5463                 }
5465                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5466                         string_copy(status_onbranch, opt_head);
5467                 return;
5468         }
5470         string_copy(status_onbranch, "Not currently on any branch");
5473 /* First parse staged info using git-diff-index(1), then parse unstaged
5474  * info using git-diff-files(1), and finally untracked files using
5475  * git-ls-files(1). */
5476 static bool
5477 status_open(struct view *view)
5479         reset_view(view);
5481         add_line_data(view, NULL, LINE_STAT_HEAD);
5482         status_update_onbranch();
5484         run_io_bg(update_index_argv);
5486         if (is_initial_commit()) {
5487                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5488                         return FALSE;
5489         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5490                 return FALSE;
5491         }
5493         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5494             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5495                 return FALSE;
5497         /* Restore the exact position or use the specialized restore
5498          * mode? */
5499         if (!view->p_restore)
5500                 status_restore(view);
5501         return TRUE;
5504 static bool
5505 status_draw(struct view *view, struct line *line, unsigned int lineno)
5507         struct status *status = line->data;
5508         enum line_type type;
5509         const char *text;
5511         if (!status) {
5512                 switch (line->type) {
5513                 case LINE_STAT_STAGED:
5514                         type = LINE_STAT_SECTION;
5515                         text = "Changes to be committed:";
5516                         break;
5518                 case LINE_STAT_UNSTAGED:
5519                         type = LINE_STAT_SECTION;
5520                         text = "Changed but not updated:";
5521                         break;
5523                 case LINE_STAT_UNTRACKED:
5524                         type = LINE_STAT_SECTION;
5525                         text = "Untracked files:";
5526                         break;
5528                 case LINE_STAT_NONE:
5529                         type = LINE_DEFAULT;
5530                         text = "  (no files)";
5531                         break;
5533                 case LINE_STAT_HEAD:
5534                         type = LINE_STAT_HEAD;
5535                         text = status_onbranch;
5536                         break;
5538                 default:
5539                         return FALSE;
5540                 }
5541         } else {
5542                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5544                 buf[0] = status->status;
5545                 if (draw_text(view, line->type, buf, TRUE))
5546                         return TRUE;
5547                 type = LINE_DEFAULT;
5548                 text = status->new.name;
5549         }
5551         draw_text(view, type, text, TRUE);
5552         return TRUE;
5555 static enum request
5556 status_load_error(struct view *view, struct view *stage, const char *path)
5558         if (displayed_views() == 2 || display[current_view] != view)
5559                 maximize_view(view);
5560         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5561         return REQ_NONE;
5564 static enum request
5565 status_enter(struct view *view, struct line *line)
5567         struct status *status = line->data;
5568         const char *oldpath = status ? status->old.name : NULL;
5569         /* Diffs for unmerged entries are empty when passing the new
5570          * path, so leave it empty. */
5571         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5572         const char *info;
5573         enum open_flags split;
5574         struct view *stage = VIEW(REQ_VIEW_STAGE);
5576         if (line->type == LINE_STAT_NONE ||
5577             (!status && line[1].type == LINE_STAT_NONE)) {
5578                 report("No file to diff");
5579                 return REQ_NONE;
5580         }
5582         switch (line->type) {
5583         case LINE_STAT_STAGED:
5584                 if (is_initial_commit()) {
5585                         const char *no_head_diff_argv[] = {
5586                                 "git", "diff", "--no-color", "--patch-with-stat",
5587                                         "--", "/dev/null", newpath, NULL
5588                         };
5590                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5591                                 return status_load_error(view, stage, newpath);
5592                 } else {
5593                         const char *index_show_argv[] = {
5594                                 "git", "diff-index", "--root", "--patch-with-stat",
5595                                         "-C", "-M", "--cached", "HEAD", "--",
5596                                         oldpath, newpath, NULL
5597                         };
5599                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5600                                 return status_load_error(view, stage, newpath);
5601                 }
5603                 if (status)
5604                         info = "Staged changes to %s";
5605                 else
5606                         info = "Staged changes";
5607                 break;
5609         case LINE_STAT_UNSTAGED:
5610         {
5611                 const char *files_show_argv[] = {
5612                         "git", "diff-files", "--root", "--patch-with-stat",
5613                                 "-C", "-M", "--", oldpath, newpath, NULL
5614                 };
5616                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5617                         return status_load_error(view, stage, newpath);
5618                 if (status)
5619                         info = "Unstaged changes to %s";
5620                 else
5621                         info = "Unstaged changes";
5622                 break;
5623         }
5624         case LINE_STAT_UNTRACKED:
5625                 if (!newpath) {
5626                         report("No file to show");
5627                         return REQ_NONE;
5628                 }
5630                 if (!suffixcmp(status->new.name, -1, "/")) {
5631                         report("Cannot display a directory");
5632                         return REQ_NONE;
5633                 }
5635                 if (!prepare_update_file(stage, newpath))
5636                         return status_load_error(view, stage, newpath);
5637                 info = "Untracked file %s";
5638                 break;
5640         case LINE_STAT_HEAD:
5641                 return REQ_NONE;
5643         default:
5644                 die("line type %d not handled in switch", line->type);
5645         }
5647         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5648         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5649         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5650                 if (status) {
5651                         stage_status = *status;
5652                 } else {
5653                         memset(&stage_status, 0, sizeof(stage_status));
5654                 }
5656                 stage_line_type = line->type;
5657                 stage_chunks = 0;
5658                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5659         }
5661         return REQ_NONE;
5664 static bool
5665 status_exists(struct status *status, enum line_type type)
5667         struct view *view = VIEW(REQ_VIEW_STATUS);
5668         unsigned long lineno;
5670         for (lineno = 0; lineno < view->lines; lineno++) {
5671                 struct line *line = &view->line[lineno];
5672                 struct status *pos = line->data;
5674                 if (line->type != type)
5675                         continue;
5676                 if (!pos && (!status || !status->status) && line[1].data) {
5677                         select_view_line(view, lineno);
5678                         return TRUE;
5679                 }
5680                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5681                         select_view_line(view, lineno);
5682                         return TRUE;
5683                 }
5684         }
5686         return FALSE;
5690 static bool
5691 status_update_prepare(struct io *io, enum line_type type)
5693         const char *staged_argv[] = {
5694                 "git", "update-index", "-z", "--index-info", NULL
5695         };
5696         const char *others_argv[] = {
5697                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5698         };
5700         switch (type) {
5701         case LINE_STAT_STAGED:
5702                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5704         case LINE_STAT_UNSTAGED:
5705                 return run_io(io, others_argv, opt_cdup, IO_WR);
5707         case LINE_STAT_UNTRACKED:
5708                 return run_io(io, others_argv, NULL, IO_WR);
5710         default:
5711                 die("line type %d not handled in switch", type);
5712                 return FALSE;
5713         }
5716 static bool
5717 status_update_write(struct io *io, struct status *status, enum line_type type)
5719         char buf[SIZEOF_STR];
5720         size_t bufsize = 0;
5722         switch (type) {
5723         case LINE_STAT_STAGED:
5724                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5725                                         status->old.mode,
5726                                         status->old.rev,
5727                                         status->old.name, 0))
5728                         return FALSE;
5729                 break;
5731         case LINE_STAT_UNSTAGED:
5732         case LINE_STAT_UNTRACKED:
5733                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5734                         return FALSE;
5735                 break;
5737         default:
5738                 die("line type %d not handled in switch", type);
5739         }
5741         return io_write(io, buf, bufsize);
5744 static bool
5745 status_update_file(struct status *status, enum line_type type)
5747         struct io io = {};
5748         bool result;
5750         if (!status_update_prepare(&io, type))
5751                 return FALSE;
5753         result = status_update_write(&io, status, type);
5754         return done_io(&io) && result;
5757 static bool
5758 status_update_files(struct view *view, struct line *line)
5760         char buf[sizeof(view->ref)];
5761         struct io io = {};
5762         bool result = TRUE;
5763         struct line *pos = view->line + view->lines;
5764         int files = 0;
5765         int file, done;
5766         int cursor_y = -1, cursor_x = -1;
5768         if (!status_update_prepare(&io, line->type))
5769                 return FALSE;
5771         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5772                 files++;
5774         string_copy(buf, view->ref);
5775         getsyx(cursor_y, cursor_x);
5776         for (file = 0, done = 5; result && file < files; line++, file++) {
5777                 int almost_done = file * 100 / files;
5779                 if (almost_done > done) {
5780                         done = almost_done;
5781                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5782                                       file, files, done);
5783                         update_view_title(view);
5784                         setsyx(cursor_y, cursor_x);
5785                         doupdate();
5786                 }
5787                 result = status_update_write(&io, line->data, line->type);
5788         }
5789         string_copy(view->ref, buf);
5791         return done_io(&io) && result;
5794 static bool
5795 status_update(struct view *view)
5797         struct line *line = &view->line[view->lineno];
5799         assert(view->lines);
5801         if (!line->data) {
5802                 /* This should work even for the "On branch" line. */
5803                 if (line < view->line + view->lines && !line[1].data) {
5804                         report("Nothing to update");
5805                         return FALSE;
5806                 }
5808                 if (!status_update_files(view, line + 1)) {
5809                         report("Failed to update file status");
5810                         return FALSE;
5811                 }
5813         } else if (!status_update_file(line->data, line->type)) {
5814                 report("Failed to update file status");
5815                 return FALSE;
5816         }
5818         return TRUE;
5821 static bool
5822 status_revert(struct status *status, enum line_type type, bool has_none)
5824         if (!status || type != LINE_STAT_UNSTAGED) {
5825                 if (type == LINE_STAT_STAGED) {
5826                         report("Cannot revert changes to staged files");
5827                 } else if (type == LINE_STAT_UNTRACKED) {
5828                         report("Cannot revert changes to untracked files");
5829                 } else if (has_none) {
5830                         report("Nothing to revert");
5831                 } else {
5832                         report("Cannot revert changes to multiple files");
5833                 }
5835         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5836                 char mode[10] = "100644";
5837                 const char *reset_argv[] = {
5838                         "git", "update-index", "--cacheinfo", mode,
5839                                 status->old.rev, status->old.name, NULL
5840                 };
5841                 const char *checkout_argv[] = {
5842                         "git", "checkout", "--", status->old.name, NULL
5843                 };
5845                 if (status->status == 'U') {
5846                         string_format(mode, "%5o", status->old.mode);
5848                         if (status->old.mode == 0 && status->new.mode == 0) {
5849                                 reset_argv[2] = "--force-remove";
5850                                 reset_argv[3] = status->old.name;
5851                                 reset_argv[4] = NULL;
5852                         }
5854                         if (!run_io_fg(reset_argv, opt_cdup))
5855                                 return FALSE;
5856                         if (status->old.mode == 0 && status->new.mode == 0)
5857                                 return TRUE;
5858                 }
5860                 return run_io_fg(checkout_argv, opt_cdup);
5861         }
5863         return FALSE;
5866 static enum request
5867 status_request(struct view *view, enum request request, struct line *line)
5869         struct status *status = line->data;
5871         switch (request) {
5872         case REQ_STATUS_UPDATE:
5873                 if (!status_update(view))
5874                         return REQ_NONE;
5875                 break;
5877         case REQ_STATUS_REVERT:
5878                 if (!status_revert(status, line->type, status_has_none(view, line)))
5879                         return REQ_NONE;
5880                 break;
5882         case REQ_STATUS_MERGE:
5883                 if (!status || status->status != 'U') {
5884                         report("Merging only possible for files with unmerged status ('U').");
5885                         return REQ_NONE;
5886                 }
5887                 open_mergetool(status->new.name);
5888                 break;
5890         case REQ_EDIT:
5891                 if (!status)
5892                         return request;
5893                 if (status->status == 'D') {
5894                         report("File has been deleted.");
5895                         return REQ_NONE;
5896                 }
5898                 open_editor(status->status != '?', status->new.name);
5899                 break;
5901         case REQ_VIEW_BLAME:
5902                 if (status)
5903                         opt_ref[0] = 0;
5904                 return request;
5906         case REQ_ENTER:
5907                 /* After returning the status view has been split to
5908                  * show the stage view. No further reloading is
5909                  * necessary. */
5910                 return status_enter(view, line);
5912         case REQ_REFRESH:
5913                 /* Simply reload the view. */
5914                 break;
5916         default:
5917                 return request;
5918         }
5920         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5922         return REQ_NONE;
5925 static void
5926 status_select(struct view *view, struct line *line)
5928         struct status *status = line->data;
5929         char file[SIZEOF_STR] = "all files";
5930         const char *text;
5931         const char *key;
5933         if (status && !string_format(file, "'%s'", status->new.name))
5934                 return;
5936         if (!status && line[1].type == LINE_STAT_NONE)
5937                 line++;
5939         switch (line->type) {
5940         case LINE_STAT_STAGED:
5941                 text = "Press %s to unstage %s for commit";
5942                 break;
5944         case LINE_STAT_UNSTAGED:
5945                 text = "Press %s to stage %s for commit";
5946                 break;
5948         case LINE_STAT_UNTRACKED:
5949                 text = "Press %s to stage %s for addition";
5950                 break;
5952         case LINE_STAT_HEAD:
5953         case LINE_STAT_NONE:
5954                 text = "Nothing to update";
5955                 break;
5957         default:
5958                 die("line type %d not handled in switch", line->type);
5959         }
5961         if (status && status->status == 'U') {
5962                 text = "Press %s to resolve conflict in %s";
5963                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5965         } else {
5966                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5967         }
5969         string_format(view->ref, text, key, file);
5970         if (status)
5971                 string_copy(opt_file, status->new.name);
5974 static bool
5975 status_grep(struct view *view, struct line *line)
5977         struct status *status = line->data;
5979         if (status) {
5980                 const char buf[2] = { status->status, 0 };
5981                 const char *text[] = { status->new.name, buf, NULL };
5983                 return grep_text(view, text);
5984         }
5986         return FALSE;
5989 static struct view_ops status_ops = {
5990         "file",
5991         NULL,
5992         status_open,
5993         NULL,
5994         status_draw,
5995         status_request,
5996         status_grep,
5997         status_select,
5998 };
6001 static bool
6002 stage_diff_write(struct io *io, struct line *line, struct line *end)
6004         while (line < end) {
6005                 if (!io_write(io, line->data, strlen(line->data)) ||
6006                     !io_write(io, "\n", 1))
6007                         return FALSE;
6008                 line++;
6009                 if (line->type == LINE_DIFF_CHUNK ||
6010                     line->type == LINE_DIFF_HEADER)
6011                         break;
6012         }
6014         return TRUE;
6017 static struct line *
6018 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6020         for (; view->line < line; line--)
6021                 if (line->type == type)
6022                         return line;
6024         return NULL;
6027 static bool
6028 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6030         const char *apply_argv[SIZEOF_ARG] = {
6031                 "git", "apply", "--whitespace=nowarn", NULL
6032         };
6033         struct line *diff_hdr;
6034         struct io io = {};
6035         int argc = 3;
6037         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6038         if (!diff_hdr)
6039                 return FALSE;
6041         if (!revert)
6042                 apply_argv[argc++] = "--cached";
6043         if (revert || stage_line_type == LINE_STAT_STAGED)
6044                 apply_argv[argc++] = "-R";
6045         apply_argv[argc++] = "-";
6046         apply_argv[argc++] = NULL;
6047         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6048                 return FALSE;
6050         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6051             !stage_diff_write(&io, chunk, view->line + view->lines))
6052                 chunk = NULL;
6054         done_io(&io);
6055         run_io_bg(update_index_argv);
6057         return chunk ? TRUE : FALSE;
6060 static bool
6061 stage_update(struct view *view, struct line *line)
6063         struct line *chunk = NULL;
6065         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6066                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6068         if (chunk) {
6069                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6070                         report("Failed to apply chunk");
6071                         return FALSE;
6072                 }
6074         } else if (!stage_status.status) {
6075                 view = VIEW(REQ_VIEW_STATUS);
6077                 for (line = view->line; line < view->line + view->lines; line++)
6078                         if (line->type == stage_line_type)
6079                                 break;
6081                 if (!status_update_files(view, line + 1)) {
6082                         report("Failed to update files");
6083                         return FALSE;
6084                 }
6086         } else if (!status_update_file(&stage_status, stage_line_type)) {
6087                 report("Failed to update file");
6088                 return FALSE;
6089         }
6091         return TRUE;
6094 static bool
6095 stage_revert(struct view *view, struct line *line)
6097         struct line *chunk = NULL;
6099         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6100                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6102         if (chunk) {
6103                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6104                         return FALSE;
6106                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6107                         report("Failed to revert chunk");
6108                         return FALSE;
6109                 }
6110                 return TRUE;
6112         } else {
6113                 return status_revert(stage_status.status ? &stage_status : NULL,
6114                                      stage_line_type, FALSE);
6115         }
6119 static void
6120 stage_next(struct view *view, struct line *line)
6122         int i;
6124         if (!stage_chunks) {
6125                 for (line = view->line; line < view->line + view->lines; line++) {
6126                         if (line->type != LINE_DIFF_CHUNK)
6127                                 continue;
6129                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6130                                 report("Allocation failure");
6131                                 return;
6132                         }
6134                         stage_chunk[stage_chunks++] = line - view->line;
6135                 }
6136         }
6138         for (i = 0; i < stage_chunks; i++) {
6139                 if (stage_chunk[i] > view->lineno) {
6140                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6141                         report("Chunk %d of %d", i + 1, stage_chunks);
6142                         return;
6143                 }
6144         }
6146         report("No next chunk found");
6149 static enum request
6150 stage_request(struct view *view, enum request request, struct line *line)
6152         switch (request) {
6153         case REQ_STATUS_UPDATE:
6154                 if (!stage_update(view, line))
6155                         return REQ_NONE;
6156                 break;
6158         case REQ_STATUS_REVERT:
6159                 if (!stage_revert(view, line))
6160                         return REQ_NONE;
6161                 break;
6163         case REQ_STAGE_NEXT:
6164                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6165                         report("File is untracked; press %s to add",
6166                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6167                         return REQ_NONE;
6168                 }
6169                 stage_next(view, line);
6170                 return REQ_NONE;
6172         case REQ_EDIT:
6173                 if (!stage_status.new.name[0])
6174                         return request;
6175                 if (stage_status.status == 'D') {
6176                         report("File has been deleted.");
6177                         return REQ_NONE;
6178                 }
6180                 open_editor(stage_status.status != '?', stage_status.new.name);
6181                 break;
6183         case REQ_REFRESH:
6184                 /* Reload everything ... */
6185                 break;
6187         case REQ_VIEW_BLAME:
6188                 if (stage_status.new.name[0]) {
6189                         string_copy(opt_file, stage_status.new.name);
6190                         opt_ref[0] = 0;
6191                 }
6192                 return request;
6194         case REQ_ENTER:
6195                 return pager_request(view, request, line);
6197         default:
6198                 return request;
6199         }
6201         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6202         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6204         /* Check whether the staged entry still exists, and close the
6205          * stage view if it doesn't. */
6206         if (!status_exists(&stage_status, stage_line_type)) {
6207                 status_restore(VIEW(REQ_VIEW_STATUS));
6208                 return REQ_VIEW_CLOSE;
6209         }
6211         if (stage_line_type == LINE_STAT_UNTRACKED) {
6212                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6213                         report("Cannot display a directory");
6214                         return REQ_NONE;
6215                 }
6217                 if (!prepare_update_file(view, stage_status.new.name)) {
6218                         report("Failed to open file: %s", strerror(errno));
6219                         return REQ_NONE;
6220                 }
6221         }
6222         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6224         return REQ_NONE;
6227 static struct view_ops stage_ops = {
6228         "line",
6229         NULL,
6230         NULL,
6231         pager_read,
6232         pager_draw,
6233         stage_request,
6234         pager_grep,
6235         pager_select,
6236 };
6239 /*
6240  * Revision graph
6241  */
6243 struct commit {
6244         char id[SIZEOF_REV];            /* SHA1 ID. */
6245         char title[128];                /* First line of the commit message. */
6246         const char *author;             /* Author of the commit. */
6247         time_t time;                    /* Date from the author ident. */
6248         struct ref_list *refs;          /* Repository references. */
6249         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6250         size_t graph_size;              /* The width of the graph array. */
6251         bool has_parents;               /* Rewritten --parents seen. */
6252 };
6254 /* Size of rev graph with no  "padding" columns */
6255 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6257 struct rev_graph {
6258         struct rev_graph *prev, *next, *parents;
6259         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6260         size_t size;
6261         struct commit *commit;
6262         size_t pos;
6263         unsigned int boundary:1;
6264 };
6266 /* Parents of the commit being visualized. */
6267 static struct rev_graph graph_parents[4];
6269 /* The current stack of revisions on the graph. */
6270 static struct rev_graph graph_stacks[4] = {
6271         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6272         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6273         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6274         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6275 };
6277 static inline bool
6278 graph_parent_is_merge(struct rev_graph *graph)
6280         return graph->parents->size > 1;
6283 static inline void
6284 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6286         struct commit *commit = graph->commit;
6288         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6289                 commit->graph[commit->graph_size++] = symbol;
6292 static void
6293 clear_rev_graph(struct rev_graph *graph)
6295         graph->boundary = 0;
6296         graph->size = graph->pos = 0;
6297         graph->commit = NULL;
6298         memset(graph->parents, 0, sizeof(*graph->parents));
6301 static void
6302 done_rev_graph(struct rev_graph *graph)
6304         if (graph_parent_is_merge(graph) &&
6305             graph->pos < graph->size - 1 &&
6306             graph->next->size == graph->size + graph->parents->size - 1) {
6307                 size_t i = graph->pos + graph->parents->size - 1;
6309                 graph->commit->graph_size = i * 2;
6310                 while (i < graph->next->size - 1) {
6311                         append_to_rev_graph(graph, ' ');
6312                         append_to_rev_graph(graph, '\\');
6313                         i++;
6314                 }
6315         }
6317         clear_rev_graph(graph);
6320 static void
6321 push_rev_graph(struct rev_graph *graph, const char *parent)
6323         int i;
6325         /* "Collapse" duplicate parents lines.
6326          *
6327          * FIXME: This needs to also update update the drawn graph but
6328          * for now it just serves as a method for pruning graph lines. */
6329         for (i = 0; i < graph->size; i++)
6330                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6331                         return;
6333         if (graph->size < SIZEOF_REVITEMS) {
6334                 string_copy_rev(graph->rev[graph->size++], parent);
6335         }
6338 static chtype
6339 get_rev_graph_symbol(struct rev_graph *graph)
6341         chtype symbol;
6343         if (graph->boundary)
6344                 symbol = REVGRAPH_BOUND;
6345         else if (graph->parents->size == 0)
6346                 symbol = REVGRAPH_INIT;
6347         else if (graph_parent_is_merge(graph))
6348                 symbol = REVGRAPH_MERGE;
6349         else if (graph->pos >= graph->size)
6350                 symbol = REVGRAPH_BRANCH;
6351         else
6352                 symbol = REVGRAPH_COMMIT;
6354         return symbol;
6357 static void
6358 draw_rev_graph(struct rev_graph *graph)
6360         struct rev_filler {
6361                 chtype separator, line;
6362         };
6363         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6364         static struct rev_filler fillers[] = {
6365                 { ' ',  '|' },
6366                 { '`',  '.' },
6367                 { '\'', ' ' },
6368                 { '/',  ' ' },
6369         };
6370         chtype symbol = get_rev_graph_symbol(graph);
6371         struct rev_filler *filler;
6372         size_t i;
6374         if (opt_line_graphics)
6375                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6377         filler = &fillers[DEFAULT];
6379         for (i = 0; i < graph->pos; i++) {
6380                 append_to_rev_graph(graph, filler->line);
6381                 if (graph_parent_is_merge(graph->prev) &&
6382                     graph->prev->pos == i)
6383                         filler = &fillers[RSHARP];
6385                 append_to_rev_graph(graph, filler->separator);
6386         }
6388         /* Place the symbol for this revision. */
6389         append_to_rev_graph(graph, symbol);
6391         if (graph->prev->size > graph->size)
6392                 filler = &fillers[RDIAG];
6393         else
6394                 filler = &fillers[DEFAULT];
6396         i++;
6398         for (; i < graph->size; i++) {
6399                 append_to_rev_graph(graph, filler->separator);
6400                 append_to_rev_graph(graph, filler->line);
6401                 if (graph_parent_is_merge(graph->prev) &&
6402                     i < graph->prev->pos + graph->parents->size)
6403                         filler = &fillers[RSHARP];
6404                 if (graph->prev->size > graph->size)
6405                         filler = &fillers[LDIAG];
6406         }
6408         if (graph->prev->size > graph->size) {
6409                 append_to_rev_graph(graph, filler->separator);
6410                 if (filler->line != ' ')
6411                         append_to_rev_graph(graph, filler->line);
6412         }
6415 /* Prepare the next rev graph */
6416 static void
6417 prepare_rev_graph(struct rev_graph *graph)
6419         size_t i;
6421         /* First, traverse all lines of revisions up to the active one. */
6422         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6423                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6424                         break;
6426                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6427         }
6429         /* Interleave the new revision parent(s). */
6430         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6431                 push_rev_graph(graph->next, graph->parents->rev[i]);
6433         /* Lastly, put any remaining revisions. */
6434         for (i = graph->pos + 1; i < graph->size; i++)
6435                 push_rev_graph(graph->next, graph->rev[i]);
6438 static void
6439 update_rev_graph(struct view *view, struct rev_graph *graph)
6441         /* If this is the finalizing update ... */
6442         if (graph->commit)
6443                 prepare_rev_graph(graph);
6445         /* Graph visualization needs a one rev look-ahead,
6446          * so the first update doesn't visualize anything. */
6447         if (!graph->prev->commit)
6448                 return;
6450         if (view->lines > 2)
6451                 view->line[view->lines - 3].dirty = 1;
6452         if (view->lines > 1)
6453                 view->line[view->lines - 2].dirty = 1;
6454         draw_rev_graph(graph->prev);
6455         done_rev_graph(graph->prev->prev);
6459 /*
6460  * Main view backend
6461  */
6463 static const char *main_argv[SIZEOF_ARG] = {
6464         "git", "log", "--no-color", "--pretty=raw", "--parents",
6465                       "--topo-order", "%(head)", NULL
6466 };
6468 static bool
6469 main_draw(struct view *view, struct line *line, unsigned int lineno)
6471         struct commit *commit = line->data;
6473         if (!commit->author)
6474                 return FALSE;
6476         if (opt_date && draw_date(view, &commit->time))
6477                 return TRUE;
6479         if (opt_author && draw_author(view, commit->author))
6480                 return TRUE;
6482         if (opt_rev_graph && commit->graph_size &&
6483             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6484                 return TRUE;
6486         if (opt_show_refs && commit->refs) {
6487                 size_t i;
6489                 for (i = 0; i < commit->refs->size; i++) {
6490                         struct ref *ref = commit->refs->refs[i];
6491                         enum line_type type;
6493                         if (ref->head)
6494                                 type = LINE_MAIN_HEAD;
6495                         else if (ref->ltag)
6496                                 type = LINE_MAIN_LOCAL_TAG;
6497                         else if (ref->tag)
6498                                 type = LINE_MAIN_TAG;
6499                         else if (ref->tracked)
6500                                 type = LINE_MAIN_TRACKED;
6501                         else if (ref->remote)
6502                                 type = LINE_MAIN_REMOTE;
6503                         else
6504                                 type = LINE_MAIN_REF;
6506                         if (draw_text(view, type, "[", TRUE) ||
6507                             draw_text(view, type, ref->name, TRUE) ||
6508                             draw_text(view, type, "]", TRUE))
6509                                 return TRUE;
6511                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6512                                 return TRUE;
6513                 }
6514         }
6516         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6517         return TRUE;
6520 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6521 static bool
6522 main_read(struct view *view, char *line)
6524         static struct rev_graph *graph = graph_stacks;
6525         enum line_type type;
6526         struct commit *commit;
6528         if (!line) {
6529                 int i;
6531                 if (!view->lines && !view->parent)
6532                         die("No revisions match the given arguments.");
6533                 if (view->lines > 0) {
6534                         commit = view->line[view->lines - 1].data;
6535                         view->line[view->lines - 1].dirty = 1;
6536                         if (!commit->author) {
6537                                 view->lines--;
6538                                 free(commit);
6539                                 graph->commit = NULL;
6540                         }
6541                 }
6542                 update_rev_graph(view, graph);
6544                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6545                         clear_rev_graph(&graph_stacks[i]);
6546                 return TRUE;
6547         }
6549         type = get_line_type(line);
6550         if (type == LINE_COMMIT) {
6551                 commit = calloc(1, sizeof(struct commit));
6552                 if (!commit)
6553                         return FALSE;
6555                 line += STRING_SIZE("commit ");
6556                 if (*line == '-') {
6557                         graph->boundary = 1;
6558                         line++;
6559                 }
6561                 string_copy_rev(commit->id, line);
6562                 commit->refs = get_ref_list(commit->id);
6563                 graph->commit = commit;
6564                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6566                 while ((line = strchr(line, ' '))) {
6567                         line++;
6568                         push_rev_graph(graph->parents, line);
6569                         commit->has_parents = TRUE;
6570                 }
6571                 return TRUE;
6572         }
6574         if (!view->lines)
6575                 return TRUE;
6576         commit = view->line[view->lines - 1].data;
6578         switch (type) {
6579         case LINE_PARENT:
6580                 if (commit->has_parents)
6581                         break;
6582                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6583                 break;
6585         case LINE_AUTHOR:
6586                 parse_author_line(line + STRING_SIZE("author "),
6587                                   &commit->author, &commit->time);
6588                 update_rev_graph(view, graph);
6589                 graph = graph->next;
6590                 break;
6592         default:
6593                 /* Fill in the commit title if it has not already been set. */
6594                 if (commit->title[0])
6595                         break;
6597                 /* Require titles to start with a non-space character at the
6598                  * offset used by git log. */
6599                 if (strncmp(line, "    ", 4))
6600                         break;
6601                 line += 4;
6602                 /* Well, if the title starts with a whitespace character,
6603                  * try to be forgiving.  Otherwise we end up with no title. */
6604                 while (isspace(*line))
6605                         line++;
6606                 if (*line == '\0')
6607                         break;
6608                 /* FIXME: More graceful handling of titles; append "..." to
6609                  * shortened titles, etc. */
6611                 string_expand(commit->title, sizeof(commit->title), line, 1);
6612                 view->line[view->lines - 1].dirty = 1;
6613         }
6615         return TRUE;
6618 static enum request
6619 main_request(struct view *view, enum request request, struct line *line)
6621         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6623         switch (request) {
6624         case REQ_ENTER:
6625                 open_view(view, REQ_VIEW_DIFF, flags);
6626                 break;
6627         case REQ_REFRESH:
6628                 load_refs();
6629                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6630                 break;
6631         default:
6632                 return request;
6633         }
6635         return REQ_NONE;
6638 static bool
6639 grep_refs(struct ref_list *list, regex_t *regex)
6641         regmatch_t pmatch;
6642         size_t i;
6644         if (!opt_show_refs || !list)
6645                 return FALSE;
6647         for (i = 0; i < list->size; i++) {
6648                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6649                         return TRUE;
6650         }
6652         return FALSE;
6655 static bool
6656 main_grep(struct view *view, struct line *line)
6658         struct commit *commit = line->data;
6659         const char *text[] = {
6660                 commit->title,
6661                 opt_author ? commit->author : "",
6662                 opt_date ? mkdate(&commit->time) : "",
6663                 NULL
6664         };
6666         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6669 static void
6670 main_select(struct view *view, struct line *line)
6672         struct commit *commit = line->data;
6674         string_copy_rev(view->ref, commit->id);
6675         string_copy_rev(ref_commit, view->ref);
6678 static struct view_ops main_ops = {
6679         "commit",
6680         main_argv,
6681         NULL,
6682         main_read,
6683         main_draw,
6684         main_request,
6685         main_grep,
6686         main_select,
6687 };
6690 /*
6691  * Unicode / UTF-8 handling
6692  *
6693  * NOTE: Much of the following code for dealing with Unicode is derived from
6694  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6695  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6696  */
6698 static inline int
6699 unicode_width(unsigned long c)
6701         if (c >= 0x1100 &&
6702            (c <= 0x115f                         /* Hangul Jamo */
6703             || c == 0x2329
6704             || c == 0x232a
6705             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6706                                                 /* CJK ... Yi */
6707             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6708             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6709             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6710             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6711             || (c >= 0xffe0  && c <= 0xffe6)
6712             || (c >= 0x20000 && c <= 0x2fffd)
6713             || (c >= 0x30000 && c <= 0x3fffd)))
6714                 return 2;
6716         if (c == '\t')
6717                 return opt_tab_size;
6719         return 1;
6722 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6723  * Illegal bytes are set one. */
6724 static const unsigned char utf8_bytes[256] = {
6725         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,
6726         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,
6727         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,
6728         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,
6729         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,
6730         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,
6731         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,
6732         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,
6733 };
6735 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6736 static inline unsigned long
6737 utf8_to_unicode(const char *string, size_t length)
6739         unsigned long unicode;
6741         switch (length) {
6742         case 1:
6743                 unicode  =   string[0];
6744                 break;
6745         case 2:
6746                 unicode  =  (string[0] & 0x1f) << 6;
6747                 unicode +=  (string[1] & 0x3f);
6748                 break;
6749         case 3:
6750                 unicode  =  (string[0] & 0x0f) << 12;
6751                 unicode += ((string[1] & 0x3f) << 6);
6752                 unicode +=  (string[2] & 0x3f);
6753                 break;
6754         case 4:
6755                 unicode  =  (string[0] & 0x0f) << 18;
6756                 unicode += ((string[1] & 0x3f) << 12);
6757                 unicode += ((string[2] & 0x3f) << 6);
6758                 unicode +=  (string[3] & 0x3f);
6759                 break;
6760         case 5:
6761                 unicode  =  (string[0] & 0x0f) << 24;
6762                 unicode += ((string[1] & 0x3f) << 18);
6763                 unicode += ((string[2] & 0x3f) << 12);
6764                 unicode += ((string[3] & 0x3f) << 6);
6765                 unicode +=  (string[4] & 0x3f);
6766                 break;
6767         case 6:
6768                 unicode  =  (string[0] & 0x01) << 30;
6769                 unicode += ((string[1] & 0x3f) << 24);
6770                 unicode += ((string[2] & 0x3f) << 18);
6771                 unicode += ((string[3] & 0x3f) << 12);
6772                 unicode += ((string[4] & 0x3f) << 6);
6773                 unicode +=  (string[5] & 0x3f);
6774                 break;
6775         default:
6776                 die("Invalid Unicode length");
6777         }
6779         /* Invalid characters could return the special 0xfffd value but NUL
6780          * should be just as good. */
6781         return unicode > 0xffff ? 0 : unicode;
6784 /* Calculates how much of string can be shown within the given maximum width
6785  * and sets trimmed parameter to non-zero value if all of string could not be
6786  * shown. If the reserve flag is TRUE, it will reserve at least one
6787  * trailing character, which can be useful when drawing a delimiter.
6788  *
6789  * Returns the number of bytes to output from string to satisfy max_width. */
6790 static size_t
6791 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6793         const char *string = *start;
6794         const char *end = strchr(string, '\0');
6795         unsigned char last_bytes = 0;
6796         size_t last_ucwidth = 0;
6798         *width = 0;
6799         *trimmed = 0;
6801         while (string < end) {
6802                 int c = *(unsigned char *) string;
6803                 unsigned char bytes = utf8_bytes[c];
6804                 size_t ucwidth;
6805                 unsigned long unicode;
6807                 if (string + bytes > end)
6808                         break;
6810                 /* Change representation to figure out whether
6811                  * it is a single- or double-width character. */
6813                 unicode = utf8_to_unicode(string, bytes);
6814                 /* FIXME: Graceful handling of invalid Unicode character. */
6815                 if (!unicode)
6816                         break;
6818                 ucwidth = unicode_width(unicode);
6819                 if (skip > 0) {
6820                         skip -= ucwidth <= skip ? ucwidth : skip;
6821                         *start += bytes;
6822                 }
6823                 *width  += ucwidth;
6824                 if (*width > max_width) {
6825                         *trimmed = 1;
6826                         *width -= ucwidth;
6827                         if (reserve && *width == max_width) {
6828                                 string -= last_bytes;
6829                                 *width -= last_ucwidth;
6830                         }
6831                         break;
6832                 }
6834                 string  += bytes;
6835                 last_bytes = ucwidth ? bytes : 0;
6836                 last_ucwidth = ucwidth;
6837         }
6839         return string - *start;
6843 /*
6844  * Status management
6845  */
6847 /* Whether or not the curses interface has been initialized. */
6848 static bool cursed = FALSE;
6850 /* Terminal hacks and workarounds. */
6851 static bool use_scroll_redrawwin;
6852 static bool use_scroll_status_wclear;
6854 /* The status window is used for polling keystrokes. */
6855 static WINDOW *status_win;
6857 /* Reading from the prompt? */
6858 static bool input_mode = FALSE;
6860 static bool status_empty = FALSE;
6862 /* Update status and title window. */
6863 static void
6864 report(const char *msg, ...)
6866         struct view *view = display[current_view];
6868         if (input_mode)
6869                 return;
6871         if (!view) {
6872                 char buf[SIZEOF_STR];
6873                 va_list args;
6875                 va_start(args, msg);
6876                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6877                         buf[sizeof(buf) - 1] = 0;
6878                         buf[sizeof(buf) - 2] = '.';
6879                         buf[sizeof(buf) - 3] = '.';
6880                         buf[sizeof(buf) - 4] = '.';
6881                 }
6882                 va_end(args);
6883                 die("%s", buf);
6884         }
6886         if (!status_empty || *msg) {
6887                 va_list args;
6889                 va_start(args, msg);
6891                 wmove(status_win, 0, 0);
6892                 if (view->has_scrolled && use_scroll_status_wclear)
6893                         wclear(status_win);
6894                 if (*msg) {
6895                         vwprintw(status_win, msg, args);
6896                         status_empty = FALSE;
6897                 } else {
6898                         status_empty = TRUE;
6899                 }
6900                 wclrtoeol(status_win);
6901                 wnoutrefresh(status_win);
6903                 va_end(args);
6904         }
6906         update_view_title(view);
6909 /* Controls when nodelay should be in effect when polling user input. */
6910 static void
6911 set_nonblocking_input(bool loading)
6913         static unsigned int loading_views;
6915         if ((loading == FALSE && loading_views-- == 1) ||
6916             (loading == TRUE  && loading_views++ == 0))
6917                 nodelay(status_win, loading);
6920 static void
6921 init_display(void)
6923         const char *term;
6924         int x, y;
6926         /* Initialize the curses library */
6927         if (isatty(STDIN_FILENO)) {
6928                 cursed = !!initscr();
6929                 opt_tty = stdin;
6930         } else {
6931                 /* Leave stdin and stdout alone when acting as a pager. */
6932                 opt_tty = fopen("/dev/tty", "r+");
6933                 if (!opt_tty)
6934                         die("Failed to open /dev/tty");
6935                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6936         }
6938         if (!cursed)
6939                 die("Failed to initialize curses");
6941         nonl();         /* Disable conversion and detect newlines from input. */
6942         cbreak();       /* Take input chars one at a time, no wait for \n */
6943         noecho();       /* Don't echo input */
6944         leaveok(stdscr, FALSE);
6946         if (has_colors())
6947                 init_colors();
6949         getmaxyx(stdscr, y, x);
6950         status_win = newwin(1, 0, y - 1, 0);
6951         if (!status_win)
6952                 die("Failed to create status window");
6954         /* Enable keyboard mapping */
6955         keypad(status_win, TRUE);
6956         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6958         TABSIZE = opt_tab_size;
6959         if (opt_line_graphics) {
6960                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6961         }
6963         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6964         if (term && !strcmp(term, "gnome-terminal")) {
6965                 /* In the gnome-terminal-emulator, the message from
6966                  * scrolling up one line when impossible followed by
6967                  * scrolling down one line causes corruption of the
6968                  * status line. This is fixed by calling wclear. */
6969                 use_scroll_status_wclear = TRUE;
6970                 use_scroll_redrawwin = FALSE;
6972         } else if (term && !strcmp(term, "xrvt-xpm")) {
6973                 /* No problems with full optimizations in xrvt-(unicode)
6974                  * and aterm. */
6975                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6977         } else {
6978                 /* When scrolling in (u)xterm the last line in the
6979                  * scrolling direction will update slowly. */
6980                 use_scroll_redrawwin = TRUE;
6981                 use_scroll_status_wclear = FALSE;
6982         }
6985 static int
6986 get_input(int prompt_position)
6988         struct view *view;
6989         int i, key, cursor_y, cursor_x;
6991         if (prompt_position)
6992                 input_mode = TRUE;
6994         while (TRUE) {
6995                 foreach_view (view, i) {
6996                         update_view(view);
6997                         if (view_is_displayed(view) && view->has_scrolled &&
6998                             use_scroll_redrawwin)
6999                                 redrawwin(view->win);
7000                         view->has_scrolled = FALSE;
7001                 }
7003                 /* Update the cursor position. */
7004                 if (prompt_position) {
7005                         getbegyx(status_win, cursor_y, cursor_x);
7006                         cursor_x = prompt_position;
7007                 } else {
7008                         view = display[current_view];
7009                         getbegyx(view->win, cursor_y, cursor_x);
7010                         cursor_x = view->width - 1;
7011                         cursor_y += view->lineno - view->offset;
7012                 }
7013                 setsyx(cursor_y, cursor_x);
7015                 /* Refresh, accept single keystroke of input */
7016                 doupdate();
7017                 key = wgetch(status_win);
7019                 /* wgetch() with nodelay() enabled returns ERR when
7020                  * there's no input. */
7021                 if (key == ERR) {
7023                 } else if (key == KEY_RESIZE) {
7024                         int height, width;
7026                         getmaxyx(stdscr, height, width);
7028                         wresize(status_win, 1, width);
7029                         mvwin(status_win, height - 1, 0);
7030                         wnoutrefresh(status_win);
7031                         resize_display();
7032                         redraw_display(TRUE);
7034                 } else {
7035                         input_mode = FALSE;
7036                         return key;
7037                 }
7038         }
7041 static char *
7042 prompt_input(const char *prompt, input_handler handler, void *data)
7044         enum input_status status = INPUT_OK;
7045         static char buf[SIZEOF_STR];
7046         size_t pos = 0;
7048         buf[pos] = 0;
7050         while (status == INPUT_OK || status == INPUT_SKIP) {
7051                 int key;
7053                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7054                 wclrtoeol(status_win);
7056                 key = get_input(pos + 1);
7057                 switch (key) {
7058                 case KEY_RETURN:
7059                 case KEY_ENTER:
7060                 case '\n':
7061                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7062                         break;
7064                 case KEY_BACKSPACE:
7065                         if (pos > 0)
7066                                 buf[--pos] = 0;
7067                         else
7068                                 status = INPUT_CANCEL;
7069                         break;
7071                 case KEY_ESC:
7072                         status = INPUT_CANCEL;
7073                         break;
7075                 default:
7076                         if (pos >= sizeof(buf)) {
7077                                 report("Input string too long");
7078                                 return NULL;
7079                         }
7081                         status = handler(data, buf, key);
7082                         if (status == INPUT_OK)
7083                                 buf[pos++] = (char) key;
7084                 }
7085         }
7087         /* Clear the status window */
7088         status_empty = FALSE;
7089         report("");
7091         if (status == INPUT_CANCEL)
7092                 return NULL;
7094         buf[pos++] = 0;
7096         return buf;
7099 static enum input_status
7100 prompt_yesno_handler(void *data, char *buf, int c)
7102         if (c == 'y' || c == 'Y')
7103                 return INPUT_STOP;
7104         if (c == 'n' || c == 'N')
7105                 return INPUT_CANCEL;
7106         return INPUT_SKIP;
7109 static bool
7110 prompt_yesno(const char *prompt)
7112         char prompt2[SIZEOF_STR];
7114         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7115                 return FALSE;
7117         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7120 static enum input_status
7121 read_prompt_handler(void *data, char *buf, int c)
7123         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7126 static char *
7127 read_prompt(const char *prompt)
7129         return prompt_input(prompt, read_prompt_handler, NULL);
7132 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7134         enum input_status status = INPUT_OK;
7135         int size = 0;
7137         while (items[size].text)
7138                 size++;
7140         while (status == INPUT_OK) {
7141                 const struct menu_item *item = &items[*selected];
7142                 int key;
7143                 int i;
7145                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7146                           prompt, *selected + 1, size);
7147                 if (item->hotkey)
7148                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7149                 wprintw(status_win, "%s", item->text);
7150                 wclrtoeol(status_win);
7152                 key = get_input(COLS - 1);
7153                 switch (key) {
7154                 case KEY_RETURN:
7155                 case KEY_ENTER:
7156                 case '\n':
7157                         status = INPUT_STOP;
7158                         break;
7160                 case KEY_LEFT:
7161                 case KEY_UP:
7162                         *selected = *selected - 1;
7163                         if (*selected < 0)
7164                                 *selected = size - 1;
7165                         break;
7167                 case KEY_RIGHT:
7168                 case KEY_DOWN:
7169                         *selected = (*selected + 1) % size;
7170                         break;
7172                 case KEY_ESC:
7173                         status = INPUT_CANCEL;
7174                         break;
7176                 default:
7177                         for (i = 0; items[i].text; i++)
7178                                 if (items[i].hotkey == key) {
7179                                         *selected = i;
7180                                         status = INPUT_STOP;
7181                                         break;
7182                                 }
7183                 }
7184         }
7186         /* Clear the status window */
7187         status_empty = FALSE;
7188         report("");
7190         return status != INPUT_CANCEL;
7193 /*
7194  * Repository properties
7195  */
7197 static struct ref **refs = NULL;
7198 static size_t refs_size = 0;
7200 static struct ref_list **ref_lists = NULL;
7201 static size_t ref_lists_size = 0;
7203 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7204 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7205 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7207 static int
7208 compare_refs(const void *ref1_, const void *ref2_)
7210         const struct ref *ref1 = *(const struct ref **)ref1_;
7211         const struct ref *ref2 = *(const struct ref **)ref2_;
7213         if (ref1->tag != ref2->tag)
7214                 return ref2->tag - ref1->tag;
7215         if (ref1->ltag != ref2->ltag)
7216                 return ref2->ltag - ref2->ltag;
7217         if (ref1->head != ref2->head)
7218                 return ref2->head - ref1->head;
7219         if (ref1->tracked != ref2->tracked)
7220                 return ref2->tracked - ref1->tracked;
7221         if (ref1->remote != ref2->remote)
7222                 return ref2->remote - ref1->remote;
7223         return strcmp(ref1->name, ref2->name);
7226 static void
7227 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7229         size_t i;
7231         for (i = 0; i < refs_size; i++)
7232                 if (!visitor(data, refs[i]))
7233                         break;
7236 static struct ref_list *
7237 get_ref_list(const char *id)
7239         struct ref_list *list;
7240         size_t i;
7242         for (i = 0; i < ref_lists_size; i++)
7243                 if (!strcmp(id, ref_lists[i]->id))
7244                         return ref_lists[i];
7246         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7247                 return NULL;
7248         list = calloc(1, sizeof(*list));
7249         if (!list)
7250                 return NULL;
7252         for (i = 0; i < refs_size; i++) {
7253                 if (!strcmp(id, refs[i]->id) &&
7254                     realloc_refs_list(&list->refs, list->size, 1))
7255                         list->refs[list->size++] = refs[i];
7256         }
7258         if (!list->refs) {
7259                 free(list);
7260                 return NULL;
7261         }
7263         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7264         ref_lists[ref_lists_size++] = list;
7265         return list;
7268 static int
7269 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7271         struct ref *ref = NULL;
7272         bool tag = FALSE;
7273         bool ltag = FALSE;
7274         bool remote = FALSE;
7275         bool tracked = FALSE;
7276         bool head = FALSE;
7277         int from = 0, to = refs_size - 1;
7279         if (!prefixcmp(name, "refs/tags/")) {
7280                 if (!suffixcmp(name, namelen, "^{}")) {
7281                         namelen -= 3;
7282                         name[namelen] = 0;
7283                 } else {
7284                         ltag = TRUE;
7285                 }
7287                 tag = TRUE;
7288                 namelen -= STRING_SIZE("refs/tags/");
7289                 name    += STRING_SIZE("refs/tags/");
7291         } else if (!prefixcmp(name, "refs/remotes/")) {
7292                 remote = TRUE;
7293                 namelen -= STRING_SIZE("refs/remotes/");
7294                 name    += STRING_SIZE("refs/remotes/");
7295                 tracked  = !strcmp(opt_remote, name);
7297         } else if (!prefixcmp(name, "refs/heads/")) {
7298                 namelen -= STRING_SIZE("refs/heads/");
7299                 name    += STRING_SIZE("refs/heads/");
7300                 head     = !strncmp(opt_head, name, namelen);
7302         } else if (!strcmp(name, "HEAD")) {
7303                 string_ncopy(opt_head_rev, id, idlen);
7304                 return OK;
7305         }
7307         /* If we are reloading or it's an annotated tag, replace the
7308          * previous SHA1 with the resolved commit id; relies on the fact
7309          * git-ls-remote lists the commit id of an annotated tag right
7310          * before the commit id it points to. */
7311         while (from <= to) {
7312                 size_t pos = (to + from) / 2;
7313                 int cmp = strcmp(name, refs[pos]->name);
7315                 if (!cmp) {
7316                         ref = refs[pos];
7317                         break;
7318                 }
7320                 if (cmp < 0)
7321                         to = pos - 1;
7322                 else
7323                         from = pos + 1;
7324         }
7326         if (!ref) {
7327                 if (!realloc_refs(&refs, refs_size, 1))
7328                         return ERR;
7329                 ref = calloc(1, sizeof(*ref) + namelen);
7330                 if (!ref)
7331                         return ERR;
7332                 memmove(refs + from + 1, refs + from,
7333                         (refs_size - from) * sizeof(*refs));
7334                 refs[from] = ref;
7335                 strncpy(ref->name, name, namelen);
7336                 refs_size++;
7337         }
7339         ref->head = head;
7340         ref->tag = tag;
7341         ref->ltag = ltag;
7342         ref->remote = remote;
7343         ref->tracked = tracked;
7344         string_copy_rev(ref->id, id);
7346         return OK;
7349 static int
7350 load_refs(void)
7352         const char *head_argv[] = {
7353                 "git", "symbolic-ref", "HEAD", NULL
7354         };
7355         static const char *ls_remote_argv[SIZEOF_ARG] = {
7356                 "git", "ls-remote", opt_git_dir, NULL
7357         };
7358         static bool init = FALSE;
7359         size_t i;
7361         if (!init) {
7362                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7363                 init = TRUE;
7364         }
7366         if (!*opt_git_dir)
7367                 return OK;
7369         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7370             !prefixcmp(opt_head, "refs/heads/")) {
7371                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7373                 memmove(opt_head, offset, strlen(offset) + 1);
7374         }
7376         for (i = 0; i < refs_size; i++)
7377                 refs[i]->id[0] = 0;
7379         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7380                 return ERR;
7382         /* Update the ref lists to reflect changes. */
7383         for (i = 0; i < ref_lists_size; i++) {
7384                 struct ref_list *list = ref_lists[i];
7385                 size_t old, new;
7387                 for (old = new = 0; old < list->size; old++)
7388                         if (!strcmp(list->id, list->refs[old]->id))
7389                                 list->refs[new++] = list->refs[old];
7390                 list->size = new;
7391         }
7393         return OK;
7396 static void
7397 set_remote_branch(const char *name, const char *value, size_t valuelen)
7399         if (!strcmp(name, ".remote")) {
7400                 string_ncopy(opt_remote, value, valuelen);
7402         } else if (*opt_remote && !strcmp(name, ".merge")) {
7403                 size_t from = strlen(opt_remote);
7405                 if (!prefixcmp(value, "refs/heads/"))
7406                         value += STRING_SIZE("refs/heads/");
7408                 if (!string_format_from(opt_remote, &from, "/%s", value))
7409                         opt_remote[0] = 0;
7410         }
7413 static void
7414 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7416         const char *argv[SIZEOF_ARG] = { name, "=" };
7417         int argc = 1 + (cmd == option_set_command);
7418         int error = ERR;
7420         if (!argv_from_string(argv, &argc, value))
7421                 config_msg = "Too many option arguments";
7422         else
7423                 error = cmd(argc, argv);
7425         if (error == ERR)
7426                 warn("Option 'tig.%s': %s", name, config_msg);
7429 static bool
7430 set_environment_variable(const char *name, const char *value)
7432         size_t len = strlen(name) + 1 + strlen(value) + 1;
7433         char *env = malloc(len);
7435         if (env &&
7436             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7437             putenv(env) == 0)
7438                 return TRUE;
7439         free(env);
7440         return FALSE;
7443 static void
7444 set_work_tree(const char *value)
7446         char cwd[SIZEOF_STR];
7448         if (!getcwd(cwd, sizeof(cwd)))
7449                 die("Failed to get cwd path: %s", strerror(errno));
7450         if (chdir(opt_git_dir) < 0)
7451                 die("Failed to chdir(%s): %s", strerror(errno));
7452         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7453                 die("Failed to get git path: %s", strerror(errno));
7454         if (chdir(cwd) < 0)
7455                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7456         if (chdir(value) < 0)
7457                 die("Failed to chdir(%s): %s", value, strerror(errno));
7458         if (!getcwd(cwd, sizeof(cwd)))
7459                 die("Failed to get cwd path: %s", strerror(errno));
7460         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7461                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7462         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7463                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7464         opt_is_inside_work_tree = TRUE;
7467 static int
7468 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7470         if (!strcmp(name, "i18n.commitencoding"))
7471                 string_ncopy(opt_encoding, value, valuelen);
7473         else if (!strcmp(name, "core.editor"))
7474                 string_ncopy(opt_editor, value, valuelen);
7476         else if (!strcmp(name, "core.worktree"))
7477                 set_work_tree(value);
7479         else if (!prefixcmp(name, "tig.color."))
7480                 set_repo_config_option(name + 10, value, option_color_command);
7482         else if (!prefixcmp(name, "tig.bind."))
7483                 set_repo_config_option(name + 9, value, option_bind_command);
7485         else if (!prefixcmp(name, "tig."))
7486                 set_repo_config_option(name + 4, value, option_set_command);
7488         else if (*opt_head && !prefixcmp(name, "branch.") &&
7489                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7490                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7492         return OK;
7495 static int
7496 load_git_config(void)
7498         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7500         return run_io_load(config_list_argv, "=", read_repo_config_option);
7503 static int
7504 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7506         if (!opt_git_dir[0]) {
7507                 string_ncopy(opt_git_dir, name, namelen);
7509         } else if (opt_is_inside_work_tree == -1) {
7510                 /* This can be 3 different values depending on the
7511                  * version of git being used. If git-rev-parse does not
7512                  * understand --is-inside-work-tree it will simply echo
7513                  * the option else either "true" or "false" is printed.
7514                  * Default to true for the unknown case. */
7515                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7517         } else if (*name == '.') {
7518                 string_ncopy(opt_cdup, name, namelen);
7520         } else {
7521                 string_ncopy(opt_prefix, name, namelen);
7522         }
7524         return OK;
7527 static int
7528 load_repo_info(void)
7530         const char *rev_parse_argv[] = {
7531                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7532                         "--show-cdup", "--show-prefix", NULL
7533         };
7535         return run_io_load(rev_parse_argv, "=", read_repo_info);
7539 /*
7540  * Main
7541  */
7543 static const char usage[] =
7544 "tig " TIG_VERSION " (" __DATE__ ")\n"
7545 "\n"
7546 "Usage: tig        [options] [revs] [--] [paths]\n"
7547 "   or: tig show   [options] [revs] [--] [paths]\n"
7548 "   or: tig blame  [rev] path\n"
7549 "   or: tig status\n"
7550 "   or: tig <      [git command output]\n"
7551 "\n"
7552 "Options:\n"
7553 "  -v, --version   Show version and exit\n"
7554 "  -h, --help      Show help message and exit";
7556 static void __NORETURN
7557 quit(int sig)
7559         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7560         if (cursed)
7561                 endwin();
7562         exit(0);
7565 static void __NORETURN
7566 die(const char *err, ...)
7568         va_list args;
7570         endwin();
7572         va_start(args, err);
7573         fputs("tig: ", stderr);
7574         vfprintf(stderr, err, args);
7575         fputs("\n", stderr);
7576         va_end(args);
7578         exit(1);
7581 static void
7582 warn(const char *msg, ...)
7584         va_list args;
7586         va_start(args, msg);
7587         fputs("tig warning: ", stderr);
7588         vfprintf(stderr, msg, args);
7589         fputs("\n", stderr);
7590         va_end(args);
7593 static enum request
7594 parse_options(int argc, const char *argv[])
7596         enum request request = REQ_VIEW_MAIN;
7597         const char *subcommand;
7598         bool seen_dashdash = FALSE;
7599         /* XXX: This is vulnerable to the user overriding options
7600          * required for the main view parser. */
7601         const char *custom_argv[SIZEOF_ARG] = {
7602                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7603                         "--topo-order", NULL
7604         };
7605         int i, j = 6;
7607         if (!isatty(STDIN_FILENO)) {
7608                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7609                 return REQ_VIEW_PAGER;
7610         }
7612         if (argc <= 1)
7613                 return REQ_NONE;
7615         subcommand = argv[1];
7616         if (!strcmp(subcommand, "status")) {
7617                 if (argc > 2)
7618                         warn("ignoring arguments after `%s'", subcommand);
7619                 return REQ_VIEW_STATUS;
7621         } else if (!strcmp(subcommand, "blame")) {
7622                 if (argc <= 2 || argc > 4)
7623                         die("invalid number of options to blame\n\n%s", usage);
7625                 i = 2;
7626                 if (argc == 4) {
7627                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7628                         i++;
7629                 }
7631                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7632                 return REQ_VIEW_BLAME;
7634         } else if (!strcmp(subcommand, "show")) {
7635                 request = REQ_VIEW_DIFF;
7637         } else {
7638                 subcommand = NULL;
7639         }
7641         if (subcommand) {
7642                 custom_argv[1] = subcommand;
7643                 j = 2;
7644         }
7646         for (i = 1 + !!subcommand; i < argc; i++) {
7647                 const char *opt = argv[i];
7649                 if (seen_dashdash || !strcmp(opt, "--")) {
7650                         seen_dashdash = TRUE;
7652                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7653                         printf("tig version %s\n", TIG_VERSION);
7654                         quit(0);
7656                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7657                         printf("%s\n", usage);
7658                         quit(0);
7659                 }
7661                 custom_argv[j++] = opt;
7662                 if (j >= ARRAY_SIZE(custom_argv))
7663                         die("command too long");
7664         }
7666         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7667                 die("Failed to format arguments");
7669         return request;
7672 int
7673 main(int argc, const char *argv[])
7675         enum request request = parse_options(argc, argv);
7676         struct view *view;
7677         size_t i;
7679         signal(SIGINT, quit);
7680         signal(SIGPIPE, SIG_IGN);
7682         if (setlocale(LC_ALL, "")) {
7683                 char *codeset = nl_langinfo(CODESET);
7685                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7686         }
7688         if (load_repo_info() == ERR)
7689                 die("Failed to load repo info.");
7691         if (load_options() == ERR)
7692                 die("Failed to load user config.");
7694         if (load_git_config() == ERR)
7695                 die("Failed to load repo config.");
7697         /* Require a git repository unless when running in pager mode. */
7698         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7699                 die("Not a git repository");
7701         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7702                 opt_utf8 = FALSE;
7704         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7705                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7706                 if (opt_iconv == ICONV_NONE)
7707                         die("Failed to initialize character set conversion");
7708         }
7710         if (load_refs() == ERR)
7711                 die("Failed to load refs.");
7713         foreach_view (view, i)
7714                 argv_from_env(view->ops->argv, view->cmd_env);
7716         init_display();
7718         if (request != REQ_NONE)
7719                 open_view(NULL, request, OPEN_PREPARED);
7720         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7722         while (view_driver(display[current_view], request)) {
7723                 int key = get_input(0);
7725                 view = display[current_view];
7726                 request = get_keybinding(view->keymap, key);
7728                 /* Some low-level request handling. This keeps access to
7729                  * status_win restricted. */
7730                 switch (request) {
7731                 case REQ_PROMPT:
7732                 {
7733                         char *cmd = read_prompt(":");
7735                         if (cmd && isdigit(*cmd)) {
7736                                 int lineno = view->lineno + 1;
7738                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7739                                         select_view_line(view, lineno - 1);
7740                                         report("");
7741                                 } else {
7742                                         report("Unable to parse '%s' as a line number", cmd);
7743                                 }
7745                         } else if (cmd) {
7746                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7747                                 const char *argv[SIZEOF_ARG] = { "git" };
7748                                 int argc = 1;
7750                                 /* When running random commands, initially show the
7751                                  * command in the title. However, it maybe later be
7752                                  * overwritten if a commit line is selected. */
7753                                 string_ncopy(next->ref, cmd, strlen(cmd));
7755                                 if (!argv_from_string(argv, &argc, cmd)) {
7756                                         report("Too many arguments");
7757                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7758                                         report("Failed to format command");
7759                                 } else {
7760                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7761                                 }
7762                         }
7764                         request = REQ_NONE;
7765                         break;
7766                 }
7767                 case REQ_SEARCH:
7768                 case REQ_SEARCH_BACK:
7769                 {
7770                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7771                         char *search = read_prompt(prompt);
7773                         if (search)
7774                                 string_ncopy(opt_search, search, strlen(search));
7775                         else if (*opt_search)
7776                                 request = request == REQ_SEARCH ?
7777                                         REQ_FIND_NEXT :
7778                                         REQ_FIND_PREV;
7779                         else
7780                                 request = REQ_NONE;
7781                         break;
7782                 }
7783                 default:
7784                         break;
7785                 }
7786         }
7788         quit(0);
7790         return 0;