Code

Abbreviation of author names is now configurable and toggleable
[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 #define enum_equals(entry, str, len) \
301         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
303 struct enum_map {
304         const char *name;
305         int namelen;
306         int value;
307 };
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static char *
312 enum_map_name(const char *name, size_t namelen)
314         static char buf[SIZEOF_STR];
315         int bufpos;
317         for (bufpos = 0; bufpos <= namelen; bufpos++) {
318                 buf[bufpos] = tolower(name[bufpos]);
319                 if (buf[bufpos] == '_')
320                         buf[bufpos] = '-';
321         }
323         buf[bufpos] = 0;
324         return buf;
327 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
329 static bool
330 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
332         size_t namelen = strlen(name);
333         int i;
335         for (i = 0; i < map_size; i++)
336                 if (enum_equals(map[i], name, namelen)) {
337                         *value = map[i].value;
338                         return TRUE;
339                 }
341         return FALSE;
344 #define map_enum(attr, map, name) \
345         map_enum_do(map, ARRAY_SIZE(map), attr, name)
347 #define prefixcmp(str1, str2) \
348         strncmp(str1, str2, STRING_SIZE(str2))
350 static inline int
351 suffixcmp(const char *str, int slen, const char *suffix)
353         size_t len = slen >= 0 ? slen : strlen(str);
354         size_t suffixlen = strlen(suffix);
356         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
360 /*
361  * What value of "tz" was in effect back then at "time" in the
362  * local timezone?
363  */
364 static int local_tzoffset(time_t time)
366         time_t t, t_local;
367         struct tm tm;
368         int offset, eastwest; 
370         t = time;
371         localtime_r(&t, &tm);
372         t_local = mktime(&tm);
374         if (t_local < t) {
375                 eastwest = -1;
376                 offset = t - t_local;
377         } else {
378                 eastwest = 1;
379                 offset = t_local - t;
380         }
381         offset /= 60; /* in minutes */
382         offset = (offset % 60) + ((offset / 60) * 100);
383         return offset * eastwest;
386 #define DATE_INFO \
387         DATE_(NO), \
388         DATE_(DEFAULT), \
389         DATE_(RELATIVE), \
390         DATE_(SHORT)
392 enum date {
393 #define DATE_(name) DATE_##name
394         DATE_INFO
395 #undef  DATE_
396 };
398 static const struct enum_map date_map[] = {
399 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
400         DATE_INFO
401 #undef  DATE_
402 };
404 static const char *
405 string_date(const time_t *time, enum date date)
407         static char buf[DATE_COLS + 1];
408         static const struct enum_map reldate[] = {
409                 { "second", 1,                  60 * 2 },
410                 { "minute", 60,                 60 * 60 * 2 },
411                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
412                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
413                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
414                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
415         };
416         struct tm tm;
418         if (date == DATE_RELATIVE) {
419                 struct timeval now;
420                 time_t date = *time + local_tzoffset(*time);
421                 time_t seconds;
422                 int i;
424                 gettimeofday(&now, NULL);
425                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
426                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
427                         if (seconds >= reldate[i].value)
428                                 continue;
430                         seconds /= reldate[i].namelen;
431                         if (!string_format(buf, "%ld %s%s %s",
432                                            seconds, reldate[i].name,
433                                            seconds > 1 ? "s" : "",
434                                            now.tv_sec >= date ? "ago" : "ahead"))
435                                 break;
436                         return buf;
437                 }
438         }
440         gmtime_r(time, &tm);
441         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
445 #define AUTHOR_VALUES \
446         AUTHOR_(NO), \
447         AUTHOR_(FULL), \
448         AUTHOR_(ABBREVIATED)
450 enum author {
451 #define AUTHOR_(name) AUTHOR_##name
452         AUTHOR_VALUES,
453 #undef  AUTHOR_
454         AUTHOR_DEFAULT = AUTHOR_FULL
455 };
457 static const struct enum_map author_map[] = {
458 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
459         AUTHOR_VALUES
460 #undef  AUTHOR_
461 };
463 static const char *
464 get_author_initials(const char *author, size_t max_columns)
466         static char initials[10];
467         size_t pos;
469 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
471         memset(initials, 0, sizeof(initials));
472         for (pos = 0; *author && pos < max_columns - 1; author++, pos++) {
473                 while (is_initial_sep(*author))
474                         author++;
475                 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
476                 while (*author && !is_initial_sep(author[1]))
477                         author++;
478         }
480         return initials;
484 static bool
485 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
487         int valuelen;
489         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
490                 bool advance = cmd[valuelen] != 0;
492                 cmd[valuelen] = 0;
493                 argv[(*argc)++] = chomp_string(cmd);
494                 cmd = chomp_string(cmd + valuelen + advance);
495         }
497         if (*argc < SIZEOF_ARG)
498                 argv[*argc] = NULL;
499         return *argc < SIZEOF_ARG;
502 static void
503 argv_from_env(const char **argv, const char *name)
505         char *env = argv ? getenv(name) : NULL;
506         int argc = 0;
508         if (env && *env)
509                 env = strdup(env);
510         if (env && !argv_from_string(argv, &argc, env))
511                 die("Too many arguments in the `%s` environment variable", name);
515 /*
516  * Executing external commands.
517  */
519 enum io_type {
520         IO_FD,                  /* File descriptor based IO. */
521         IO_BG,                  /* Execute command in the background. */
522         IO_FG,                  /* Execute command with same std{in,out,err}. */
523         IO_RD,                  /* Read only fork+exec IO. */
524         IO_WR,                  /* Write only fork+exec IO. */
525         IO_AP,                  /* Append fork+exec output to file. */
526 };
528 struct io {
529         enum io_type type;      /* The requested type of pipe. */
530         const char *dir;        /* Directory from which to execute. */
531         pid_t pid;              /* Pipe for reading or writing. */
532         int pipe;               /* Pipe end for reading or writing. */
533         int error;              /* Error status. */
534         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
535         char *buf;              /* Read buffer. */
536         size_t bufalloc;        /* Allocated buffer size. */
537         size_t bufsize;         /* Buffer content size. */
538         char *bufpos;           /* Current buffer position. */
539         unsigned int eof:1;     /* Has end of file been reached. */
540 };
542 static void
543 reset_io(struct io *io)
545         io->pipe = -1;
546         io->pid = 0;
547         io->buf = io->bufpos = NULL;
548         io->bufalloc = io->bufsize = 0;
549         io->error = 0;
550         io->eof = 0;
553 static void
554 init_io(struct io *io, const char *dir, enum io_type type)
556         reset_io(io);
557         io->type = type;
558         io->dir = dir;
561 static bool
562 init_io_rd(struct io *io, const char *argv[], const char *dir,
563                 enum format_flags flags)
565         init_io(io, dir, IO_RD);
566         return format_argv(io->argv, argv, flags);
569 static bool
570 io_open(struct io *io, const char *fmt, ...)
572         char name[SIZEOF_STR] = "";
573         bool fits;
574         va_list args;
576         init_io(io, NULL, IO_FD);
578         va_start(args, fmt);
579         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
580         va_end(args);
582         if (!fits) {
583                 io->error = ENAMETOOLONG;
584                 return FALSE;
585         }
586         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
587         if (io->pipe == -1)
588                 io->error = errno;
589         return io->pipe != -1;
592 static bool
593 kill_io(struct io *io)
595         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
598 static bool
599 done_io(struct io *io)
601         pid_t pid = io->pid;
603         if (io->pipe != -1)
604                 close(io->pipe);
605         free(io->buf);
606         reset_io(io);
608         while (pid > 0) {
609                 int status;
610                 pid_t waiting = waitpid(pid, &status, 0);
612                 if (waiting < 0) {
613                         if (errno == EINTR)
614                                 continue;
615                         report("waitpid failed (%s)", strerror(errno));
616                         return FALSE;
617                 }
619                 return waiting == pid &&
620                        !WIFSIGNALED(status) &&
621                        WIFEXITED(status) &&
622                        !WEXITSTATUS(status);
623         }
625         return TRUE;
628 static bool
629 start_io(struct io *io)
631         int pipefds[2] = { -1, -1 };
633         if (io->type == IO_FD)
634                 return TRUE;
636         if ((io->type == IO_RD || io->type == IO_WR) &&
637             pipe(pipefds) < 0)
638                 return FALSE;
639         else if (io->type == IO_AP)
640                 pipefds[1] = io->pipe;
642         if ((io->pid = fork())) {
643                 if (pipefds[!(io->type == IO_WR)] != -1)
644                         close(pipefds[!(io->type == IO_WR)]);
645                 if (io->pid != -1) {
646                         io->pipe = pipefds[!!(io->type == IO_WR)];
647                         return TRUE;
648                 }
650         } else {
651                 if (io->type != IO_FG) {
652                         int devnull = open("/dev/null", O_RDWR);
653                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
654                         int writefd = (io->type == IO_RD || io->type == IO_AP)
655                                                         ? pipefds[1] : devnull;
657                         dup2(readfd,  STDIN_FILENO);
658                         dup2(writefd, STDOUT_FILENO);
659                         dup2(devnull, STDERR_FILENO);
661                         close(devnull);
662                         if (pipefds[0] != -1)
663                                 close(pipefds[0]);
664                         if (pipefds[1] != -1)
665                                 close(pipefds[1]);
666                 }
668                 if (io->dir && *io->dir && chdir(io->dir) == -1)
669                         die("Failed to change directory: %s", strerror(errno));
671                 execvp(io->argv[0], (char *const*) io->argv);
672                 die("Failed to execute program: %s", strerror(errno));
673         }
675         if (pipefds[!!(io->type == IO_WR)] != -1)
676                 close(pipefds[!!(io->type == IO_WR)]);
677         return FALSE;
680 static bool
681 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
683         init_io(io, dir, type);
684         if (!format_argv(io->argv, argv, FORMAT_NONE))
685                 return FALSE;
686         return start_io(io);
689 static int
690 run_io_do(struct io *io)
692         return start_io(io) && done_io(io);
695 static int
696 run_io_bg(const char **argv)
698         struct io io = {};
700         init_io(&io, NULL, IO_BG);
701         if (!format_argv(io.argv, argv, FORMAT_NONE))
702                 return FALSE;
703         return run_io_do(&io);
706 static bool
707 run_io_fg(const char **argv, const char *dir)
709         struct io io = {};
711         init_io(&io, dir, IO_FG);
712         if (!format_argv(io.argv, argv, FORMAT_NONE))
713                 return FALSE;
714         return run_io_do(&io);
717 static bool
718 run_io_append(const char **argv, enum format_flags flags, int fd)
720         struct io io = {};
722         init_io(&io, NULL, IO_AP);
723         io.pipe = fd;
724         if (format_argv(io.argv, argv, flags))
725                 return run_io_do(&io);
726         close(fd);
727         return FALSE;
730 static bool
731 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
733         return init_io_rd(io, argv, dir, flags) && start_io(io);
736 static bool
737 io_eof(struct io *io)
739         return io->eof;
742 static int
743 io_error(struct io *io)
745         return io->error;
748 static char *
749 io_strerror(struct io *io)
751         return strerror(io->error);
754 static bool
755 io_can_read(struct io *io)
757         struct timeval tv = { 0, 500 };
758         fd_set fds;
760         FD_ZERO(&fds);
761         FD_SET(io->pipe, &fds);
763         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
766 static ssize_t
767 io_read(struct io *io, void *buf, size_t bufsize)
769         do {
770                 ssize_t readsize = read(io->pipe, buf, bufsize);
772                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
773                         continue;
774                 else if (readsize == -1)
775                         io->error = errno;
776                 else if (readsize == 0)
777                         io->eof = 1;
778                 return readsize;
779         } while (1);
782 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
784 static char *
785 io_get(struct io *io, int c, bool can_read)
787         char *eol;
788         ssize_t readsize;
790         while (TRUE) {
791                 if (io->bufsize > 0) {
792                         eol = memchr(io->bufpos, c, io->bufsize);
793                         if (eol) {
794                                 char *line = io->bufpos;
796                                 *eol = 0;
797                                 io->bufpos = eol + 1;
798                                 io->bufsize -= io->bufpos - line;
799                                 return line;
800                         }
801                 }
803                 if (io_eof(io)) {
804                         if (io->bufsize) {
805                                 io->bufpos[io->bufsize] = 0;
806                                 io->bufsize = 0;
807                                 return io->bufpos;
808                         }
809                         return NULL;
810                 }
812                 if (!can_read)
813                         return NULL;
815                 if (io->bufsize > 0 && io->bufpos > io->buf)
816                         memmove(io->buf, io->bufpos, io->bufsize);
818                 if (io->bufalloc == io->bufsize) {
819                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
820                                 return NULL;
821                         io->bufalloc += BUFSIZ;
822                 }
824                 io->bufpos = io->buf;
825                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
826                 if (io_error(io))
827                         return NULL;
828                 io->bufsize += readsize;
829         }
832 static bool
833 io_write(struct io *io, const void *buf, size_t bufsize)
835         size_t written = 0;
837         while (!io_error(io) && written < bufsize) {
838                 ssize_t size;
840                 size = write(io->pipe, buf + written, bufsize - written);
841                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
842                         continue;
843                 else if (size == -1)
844                         io->error = errno;
845                 else
846                         written += size;
847         }
849         return written == bufsize;
852 static bool
853 io_read_buf(struct io *io, char buf[], size_t bufsize)
855         char *result = io_get(io, '\n', TRUE);
857         if (result) {
858                 result = chomp_string(result);
859                 string_ncopy_do(buf, bufsize, result, strlen(result));
860         }
862         return done_io(io) && result;
865 static bool
866 run_io_buf(const char **argv, char buf[], size_t bufsize)
868         struct io io = {};
870         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
871             && io_read_buf(&io, buf, bufsize);
874 static int
875 io_load(struct io *io, const char *separators,
876         int (*read_property)(char *, size_t, char *, size_t))
878         char *name;
879         int state = OK;
881         if (!start_io(io))
882                 return ERR;
884         while (state == OK && (name = io_get(io, '\n', TRUE))) {
885                 char *value;
886                 size_t namelen;
887                 size_t valuelen;
889                 name = chomp_string(name);
890                 namelen = strcspn(name, separators);
892                 if (name[namelen]) {
893                         name[namelen] = 0;
894                         value = chomp_string(name + namelen + 1);
895                         valuelen = strlen(value);
897                 } else {
898                         value = "";
899                         valuelen = 0;
900                 }
902                 state = read_property(name, namelen, value, valuelen);
903         }
905         if (state != ERR && io_error(io))
906                 state = ERR;
907         done_io(io);
909         return state;
912 static int
913 run_io_load(const char **argv, const char *separators,
914             int (*read_property)(char *, size_t, char *, size_t))
916         struct io io = {};
918         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
919                 ? io_load(&io, separators, read_property) : ERR;
923 /*
924  * User requests
925  */
927 #define REQ_INFO \
928         /* XXX: Keep the view request first and in sync with views[]. */ \
929         REQ_GROUP("View switching") \
930         REQ_(VIEW_MAIN,         "Show main view"), \
931         REQ_(VIEW_DIFF,         "Show diff view"), \
932         REQ_(VIEW_LOG,          "Show log view"), \
933         REQ_(VIEW_TREE,         "Show tree view"), \
934         REQ_(VIEW_BLOB,         "Show blob view"), \
935         REQ_(VIEW_BLAME,        "Show blame view"), \
936         REQ_(VIEW_BRANCH,       "Show branch view"), \
937         REQ_(VIEW_HELP,         "Show help page"), \
938         REQ_(VIEW_PAGER,        "Show pager view"), \
939         REQ_(VIEW_STATUS,       "Show status view"), \
940         REQ_(VIEW_STAGE,        "Show stage view"), \
941         \
942         REQ_GROUP("View manipulation") \
943         REQ_(ENTER,             "Enter current line and scroll"), \
944         REQ_(NEXT,              "Move to next"), \
945         REQ_(PREVIOUS,          "Move to previous"), \
946         REQ_(PARENT,            "Move to parent"), \
947         REQ_(VIEW_NEXT,         "Move focus to next view"), \
948         REQ_(REFRESH,           "Reload and refresh"), \
949         REQ_(MAXIMIZE,          "Maximize the current view"), \
950         REQ_(VIEW_CLOSE,        "Close the current view"), \
951         REQ_(QUIT,              "Close all views and quit"), \
952         \
953         REQ_GROUP("View specific requests") \
954         REQ_(STATUS_UPDATE,     "Update file status"), \
955         REQ_(STATUS_REVERT,     "Revert file changes"), \
956         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
957         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
958         \
959         REQ_GROUP("Cursor navigation") \
960         REQ_(MOVE_UP,           "Move cursor one line up"), \
961         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
962         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
963         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
964         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
965         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
966         \
967         REQ_GROUP("Scrolling") \
968         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
969         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
970         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
971         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
972         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
973         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
974         \
975         REQ_GROUP("Searching") \
976         REQ_(SEARCH,            "Search the view"), \
977         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
978         REQ_(FIND_NEXT,         "Find next search match"), \
979         REQ_(FIND_PREV,         "Find previous search match"), \
980         \
981         REQ_GROUP("Option manipulation") \
982         REQ_(OPTIONS,           "Open option menu"), \
983         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
984         REQ_(TOGGLE_DATE,       "Toggle date display"), \
985         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
986         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
987         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
988         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
989         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
990         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
991         \
992         REQ_GROUP("Misc") \
993         REQ_(PROMPT,            "Bring up the prompt"), \
994         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
995         REQ_(SHOW_VERSION,      "Show version information"), \
996         REQ_(STOP_LOADING,      "Stop all loading views"), \
997         REQ_(EDIT,              "Open in editor"), \
998         REQ_(NONE,              "Do nothing")
1001 /* User action requests. */
1002 enum request {
1003 #define REQ_GROUP(help)
1004 #define REQ_(req, help) REQ_##req
1006         /* Offset all requests to avoid conflicts with ncurses getch values. */
1007         REQ_OFFSET = KEY_MAX + 1,
1008         REQ_INFO
1010 #undef  REQ_GROUP
1011 #undef  REQ_
1012 };
1014 struct request_info {
1015         enum request request;
1016         const char *name;
1017         int namelen;
1018         const char *help;
1019 };
1021 static const struct request_info req_info[] = {
1022 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1023 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1024         REQ_INFO
1025 #undef  REQ_GROUP
1026 #undef  REQ_
1027 };
1029 static enum request
1030 get_request(const char *name)
1032         int namelen = strlen(name);
1033         int i;
1035         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1036                 if (enum_equals(req_info[i], name, namelen))
1037                         return req_info[i].request;
1039         return REQ_NONE;
1043 /*
1044  * Options
1045  */
1047 /* Option and state variables. */
1048 static enum date opt_date               = DATE_DEFAULT;
1049 static enum author opt_author           = AUTHOR_DEFAULT;
1050 static bool opt_line_number             = FALSE;
1051 static bool opt_line_graphics           = TRUE;
1052 static bool opt_rev_graph               = FALSE;
1053 static bool opt_show_refs               = TRUE;
1054 static int opt_num_interval             = 5;
1055 static double opt_hscroll               = 0.50;
1056 static double opt_scale_split_view      = 2.0 / 3.0;
1057 static int opt_tab_size                 = 8;
1058 static int opt_author_cols              = 19;
1059 static char opt_path[SIZEOF_STR]        = "";
1060 static char opt_file[SIZEOF_STR]        = "";
1061 static char opt_ref[SIZEOF_REF]         = "";
1062 static char opt_head[SIZEOF_REF]        = "";
1063 static char opt_head_rev[SIZEOF_REV]    = "";
1064 static char opt_remote[SIZEOF_REF]      = "";
1065 static char opt_encoding[20]            = "UTF-8";
1066 static char opt_codeset[20]             = "UTF-8";
1067 static iconv_t opt_iconv_in             = ICONV_NONE;
1068 static iconv_t opt_iconv_out            = ICONV_NONE;
1069 static char opt_search[SIZEOF_STR]      = "";
1070 static char opt_cdup[SIZEOF_STR]        = "";
1071 static char opt_prefix[SIZEOF_STR]      = "";
1072 static char opt_git_dir[SIZEOF_STR]     = "";
1073 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1074 static char opt_editor[SIZEOF_STR]      = "";
1075 static FILE *opt_tty                    = NULL;
1077 #define is_initial_commit()     (!*opt_head_rev)
1078 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1079 #define mkdate(time)            string_date(time, opt_date)
1082 /*
1083  * Line-oriented content detection.
1084  */
1086 #define LINE_INFO \
1087 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1088 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1089 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1090 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1091 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1092 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1093 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1094 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1095 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1096 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1097 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1098 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1099 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1100 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1101 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1102 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1103 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1104 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1105 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1106 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1107 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1108 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1109 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1110 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1111 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1112 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1113 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1114 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1115 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1116 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1117 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1118 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1119 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1120 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1121 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1122 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1123 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1124 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1125 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1126 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1127 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1128 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1129 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1130 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1131 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1132 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1133 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1134 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1135 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1136 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1137 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1138 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1139 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1140 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1141 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1142 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1143 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1145 enum line_type {
1146 #define LINE(type, line, fg, bg, attr) \
1147         LINE_##type
1148         LINE_INFO,
1149         LINE_NONE
1150 #undef  LINE
1151 };
1153 struct line_info {
1154         const char *name;       /* Option name. */
1155         int namelen;            /* Size of option name. */
1156         const char *line;       /* The start of line to match. */
1157         int linelen;            /* Size of string to match. */
1158         int fg, bg, attr;       /* Color and text attributes for the lines. */
1159 };
1161 static struct line_info line_info[] = {
1162 #define LINE(type, line, fg, bg, attr) \
1163         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1164         LINE_INFO
1165 #undef  LINE
1166 };
1168 static enum line_type
1169 get_line_type(const char *line)
1171         int linelen = strlen(line);
1172         enum line_type type;
1174         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1175                 /* Case insensitive search matches Signed-off-by lines better. */
1176                 if (linelen >= line_info[type].linelen &&
1177                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1178                         return type;
1180         return LINE_DEFAULT;
1183 static inline int
1184 get_line_attr(enum line_type type)
1186         assert(type < ARRAY_SIZE(line_info));
1187         return COLOR_PAIR(type) | line_info[type].attr;
1190 static struct line_info *
1191 get_line_info(const char *name)
1193         size_t namelen = strlen(name);
1194         enum line_type type;
1196         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1197                 if (enum_equals(line_info[type], name, namelen))
1198                         return &line_info[type];
1200         return NULL;
1203 static void
1204 init_colors(void)
1206         int default_bg = line_info[LINE_DEFAULT].bg;
1207         int default_fg = line_info[LINE_DEFAULT].fg;
1208         enum line_type type;
1210         start_color();
1212         if (assume_default_colors(default_fg, default_bg) == ERR) {
1213                 default_bg = COLOR_BLACK;
1214                 default_fg = COLOR_WHITE;
1215         }
1217         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1218                 struct line_info *info = &line_info[type];
1219                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1220                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1222                 init_pair(type, fg, bg);
1223         }
1226 struct line {
1227         enum line_type type;
1229         /* State flags */
1230         unsigned int selected:1;
1231         unsigned int dirty:1;
1232         unsigned int cleareol:1;
1233         unsigned int other:16;
1235         void *data;             /* User data */
1236 };
1239 /*
1240  * Keys
1241  */
1243 struct keybinding {
1244         int alias;
1245         enum request request;
1246 };
1248 static const struct keybinding default_keybindings[] = {
1249         /* View switching */
1250         { 'm',          REQ_VIEW_MAIN },
1251         { 'd',          REQ_VIEW_DIFF },
1252         { 'l',          REQ_VIEW_LOG },
1253         { 't',          REQ_VIEW_TREE },
1254         { 'f',          REQ_VIEW_BLOB },
1255         { 'B',          REQ_VIEW_BLAME },
1256         { 'H',          REQ_VIEW_BRANCH },
1257         { 'p',          REQ_VIEW_PAGER },
1258         { 'h',          REQ_VIEW_HELP },
1259         { 'S',          REQ_VIEW_STATUS },
1260         { 'c',          REQ_VIEW_STAGE },
1262         /* View manipulation */
1263         { 'q',          REQ_VIEW_CLOSE },
1264         { KEY_TAB,      REQ_VIEW_NEXT },
1265         { KEY_RETURN,   REQ_ENTER },
1266         { KEY_UP,       REQ_PREVIOUS },
1267         { KEY_DOWN,     REQ_NEXT },
1268         { 'R',          REQ_REFRESH },
1269         { KEY_F(5),     REQ_REFRESH },
1270         { 'O',          REQ_MAXIMIZE },
1272         /* Cursor navigation */
1273         { 'k',          REQ_MOVE_UP },
1274         { 'j',          REQ_MOVE_DOWN },
1275         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1276         { KEY_END,      REQ_MOVE_LAST_LINE },
1277         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1278         { ' ',          REQ_MOVE_PAGE_DOWN },
1279         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1280         { 'b',          REQ_MOVE_PAGE_UP },
1281         { '-',          REQ_MOVE_PAGE_UP },
1283         /* Scrolling */
1284         { KEY_LEFT,     REQ_SCROLL_LEFT },
1285         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1286         { KEY_IC,       REQ_SCROLL_LINE_UP },
1287         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1288         { 'w',          REQ_SCROLL_PAGE_UP },
1289         { 's',          REQ_SCROLL_PAGE_DOWN },
1291         /* Searching */
1292         { '/',          REQ_SEARCH },
1293         { '?',          REQ_SEARCH_BACK },
1294         { 'n',          REQ_FIND_NEXT },
1295         { 'N',          REQ_FIND_PREV },
1297         /* Misc */
1298         { 'Q',          REQ_QUIT },
1299         { 'z',          REQ_STOP_LOADING },
1300         { 'v',          REQ_SHOW_VERSION },
1301         { 'r',          REQ_SCREEN_REDRAW },
1302         { 'o',          REQ_OPTIONS },
1303         { '.',          REQ_TOGGLE_LINENO },
1304         { 'D',          REQ_TOGGLE_DATE },
1305         { 'A',          REQ_TOGGLE_AUTHOR },
1306         { 'g',          REQ_TOGGLE_REV_GRAPH },
1307         { 'F',          REQ_TOGGLE_REFS },
1308         { 'I',          REQ_TOGGLE_SORT_ORDER },
1309         { 'i',          REQ_TOGGLE_SORT_FIELD },
1310         { ':',          REQ_PROMPT },
1311         { 'u',          REQ_STATUS_UPDATE },
1312         { '!',          REQ_STATUS_REVERT },
1313         { 'M',          REQ_STATUS_MERGE },
1314         { '@',          REQ_STAGE_NEXT },
1315         { ',',          REQ_PARENT },
1316         { 'e',          REQ_EDIT },
1317 };
1319 #define KEYMAP_INFO \
1320         KEYMAP_(GENERIC), \
1321         KEYMAP_(MAIN), \
1322         KEYMAP_(DIFF), \
1323         KEYMAP_(LOG), \
1324         KEYMAP_(TREE), \
1325         KEYMAP_(BLOB), \
1326         KEYMAP_(BLAME), \
1327         KEYMAP_(BRANCH), \
1328         KEYMAP_(PAGER), \
1329         KEYMAP_(HELP), \
1330         KEYMAP_(STATUS), \
1331         KEYMAP_(STAGE)
1333 enum keymap {
1334 #define KEYMAP_(name) KEYMAP_##name
1335         KEYMAP_INFO
1336 #undef  KEYMAP_
1337 };
1339 static const struct enum_map keymap_table[] = {
1340 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1341         KEYMAP_INFO
1342 #undef  KEYMAP_
1343 };
1345 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1347 struct keybinding_table {
1348         struct keybinding *data;
1349         size_t size;
1350 };
1352 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1354 static void
1355 add_keybinding(enum keymap keymap, enum request request, int key)
1357         struct keybinding_table *table = &keybindings[keymap];
1359         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1360         if (!table->data)
1361                 die("Failed to allocate keybinding");
1362         table->data[table->size].alias = key;
1363         table->data[table->size++].request = request;
1366 /* Looks for a key binding first in the given map, then in the generic map, and
1367  * lastly in the default keybindings. */
1368 static enum request
1369 get_keybinding(enum keymap keymap, int key)
1371         size_t i;
1373         for (i = 0; i < keybindings[keymap].size; i++)
1374                 if (keybindings[keymap].data[i].alias == key)
1375                         return keybindings[keymap].data[i].request;
1377         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1378                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1379                         return keybindings[KEYMAP_GENERIC].data[i].request;
1381         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1382                 if (default_keybindings[i].alias == key)
1383                         return default_keybindings[i].request;
1385         return (enum request) key;
1389 struct key {
1390         const char *name;
1391         int value;
1392 };
1394 static const struct key key_table[] = {
1395         { "Enter",      KEY_RETURN },
1396         { "Space",      ' ' },
1397         { "Backspace",  KEY_BACKSPACE },
1398         { "Tab",        KEY_TAB },
1399         { "Escape",     KEY_ESC },
1400         { "Left",       KEY_LEFT },
1401         { "Right",      KEY_RIGHT },
1402         { "Up",         KEY_UP },
1403         { "Down",       KEY_DOWN },
1404         { "Insert",     KEY_IC },
1405         { "Delete",     KEY_DC },
1406         { "Hash",       '#' },
1407         { "Home",       KEY_HOME },
1408         { "End",        KEY_END },
1409         { "PageUp",     KEY_PPAGE },
1410         { "PageDown",   KEY_NPAGE },
1411         { "F1",         KEY_F(1) },
1412         { "F2",         KEY_F(2) },
1413         { "F3",         KEY_F(3) },
1414         { "F4",         KEY_F(4) },
1415         { "F5",         KEY_F(5) },
1416         { "F6",         KEY_F(6) },
1417         { "F7",         KEY_F(7) },
1418         { "F8",         KEY_F(8) },
1419         { "F9",         KEY_F(9) },
1420         { "F10",        KEY_F(10) },
1421         { "F11",        KEY_F(11) },
1422         { "F12",        KEY_F(12) },
1423 };
1425 static int
1426 get_key_value(const char *name)
1428         int i;
1430         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1431                 if (!strcasecmp(key_table[i].name, name))
1432                         return key_table[i].value;
1434         if (strlen(name) == 1 && isprint(*name))
1435                 return (int) *name;
1437         return ERR;
1440 static const char *
1441 get_key_name(int key_value)
1443         static char key_char[] = "'X'";
1444         const char *seq = NULL;
1445         int key;
1447         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1448                 if (key_table[key].value == key_value)
1449                         seq = key_table[key].name;
1451         if (seq == NULL &&
1452             key_value < 127 &&
1453             isprint(key_value)) {
1454                 key_char[1] = (char) key_value;
1455                 seq = key_char;
1456         }
1458         return seq ? seq : "(no key)";
1461 static bool
1462 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1464         const char *sep = *pos > 0 ? ", " : "";
1465         const char *keyname = get_key_name(keybinding->alias);
1467         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1470 static bool
1471 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1472                            enum keymap keymap, bool all)
1474         int i;
1476         for (i = 0; i < keybindings[keymap].size; i++) {
1477                 if (keybindings[keymap].data[i].request == request) {
1478                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1479                                 return FALSE;
1480                         if (!all)
1481                                 break;
1482                 }
1483         }
1485         return TRUE;
1488 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1490 static const char *
1491 get_keys(enum keymap keymap, enum request request, bool all)
1493         static char buf[BUFSIZ];
1494         size_t pos = 0;
1495         int i;
1497         buf[pos] = 0;
1499         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1500                 return "Too many keybindings!";
1501         if (pos > 0 && !all)
1502                 return buf;
1504         if (keymap != KEYMAP_GENERIC) {
1505                 /* Only the generic keymap includes the default keybindings when
1506                  * listing all keys. */
1507                 if (all)
1508                         return buf;
1510                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1511                         return "Too many keybindings!";
1512                 if (pos)
1513                         return buf;
1514         }
1516         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1517                 if (default_keybindings[i].request == request) {
1518                         if (!append_key(buf, &pos, &default_keybindings[i]))
1519                                 return "Too many keybindings!";
1520                         if (!all)
1521                                 return buf;
1522                 }
1523         }
1525         return buf;
1528 struct run_request {
1529         enum keymap keymap;
1530         int key;
1531         const char *argv[SIZEOF_ARG];
1532 };
1534 static struct run_request *run_request;
1535 static size_t run_requests;
1537 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1539 static enum request
1540 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1542         struct run_request *req;
1544         if (argc >= ARRAY_SIZE(req->argv) - 1)
1545                 return REQ_NONE;
1547         if (!realloc_run_requests(&run_request, run_requests, 1))
1548                 return REQ_NONE;
1550         req = &run_request[run_requests];
1551         req->keymap = keymap;
1552         req->key = key;
1553         req->argv[0] = NULL;
1555         if (!format_argv(req->argv, argv, FORMAT_NONE))
1556                 return REQ_NONE;
1558         return REQ_NONE + ++run_requests;
1561 static struct run_request *
1562 get_run_request(enum request request)
1564         if (request <= REQ_NONE)
1565                 return NULL;
1566         return &run_request[request - REQ_NONE - 1];
1569 static void
1570 add_builtin_run_requests(void)
1572         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1573         const char *commit[] = { "git", "commit", NULL };
1574         const char *gc[] = { "git", "gc", NULL };
1575         struct {
1576                 enum keymap keymap;
1577                 int key;
1578                 int argc;
1579                 const char **argv;
1580         } reqs[] = {
1581                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1582                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1583                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1584         };
1585         int i;
1587         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1588                 enum request req;
1590                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1591                 if (req != REQ_NONE)
1592                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1593         }
1596 /*
1597  * User config file handling.
1598  */
1600 static int   config_lineno;
1601 static bool  config_errors;
1602 static const char *config_msg;
1604 static const struct enum_map color_map[] = {
1605 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1606         COLOR_MAP(DEFAULT),
1607         COLOR_MAP(BLACK),
1608         COLOR_MAP(BLUE),
1609         COLOR_MAP(CYAN),
1610         COLOR_MAP(GREEN),
1611         COLOR_MAP(MAGENTA),
1612         COLOR_MAP(RED),
1613         COLOR_MAP(WHITE),
1614         COLOR_MAP(YELLOW),
1615 };
1617 static const struct enum_map attr_map[] = {
1618 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1619         ATTR_MAP(NORMAL),
1620         ATTR_MAP(BLINK),
1621         ATTR_MAP(BOLD),
1622         ATTR_MAP(DIM),
1623         ATTR_MAP(REVERSE),
1624         ATTR_MAP(STANDOUT),
1625         ATTR_MAP(UNDERLINE),
1626 };
1628 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1630 static int parse_step(double *opt, const char *arg)
1632         *opt = atoi(arg);
1633         if (!strchr(arg, '%'))
1634                 return OK;
1636         /* "Shift down" so 100% and 1 does not conflict. */
1637         *opt = (*opt - 1) / 100;
1638         if (*opt >= 1.0) {
1639                 *opt = 0.99;
1640                 config_msg = "Step value larger than 100%";
1641                 return ERR;
1642         }
1643         if (*opt < 0.0) {
1644                 *opt = 1;
1645                 config_msg = "Invalid step value";
1646                 return ERR;
1647         }
1648         return OK;
1651 static int
1652 parse_int(int *opt, const char *arg, int min, int max)
1654         int value = atoi(arg);
1656         if (min <= value && value <= max) {
1657                 *opt = value;
1658                 return OK;
1659         }
1661         config_msg = "Integer value out of bound";
1662         return ERR;
1665 static bool
1666 set_color(int *color, const char *name)
1668         if (map_enum(color, color_map, name))
1669                 return TRUE;
1670         if (!prefixcmp(name, "color"))
1671                 return parse_int(color, name + 5, 0, 255) == OK;
1672         return FALSE;
1675 /* Wants: object fgcolor bgcolor [attribute] */
1676 static int
1677 option_color_command(int argc, const char *argv[])
1679         struct line_info *info;
1681         if (argc < 3) {
1682                 config_msg = "Wrong number of arguments given to color command";
1683                 return ERR;
1684         }
1686         info = get_line_info(argv[0]);
1687         if (!info) {
1688                 static const struct enum_map obsolete[] = {
1689                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1690                         ENUM_MAP("main-date",   LINE_DATE),
1691                         ENUM_MAP("main-author", LINE_AUTHOR),
1692                 };
1693                 int index;
1695                 if (!map_enum(&index, obsolete, argv[0])) {
1696                         config_msg = "Unknown color name";
1697                         return ERR;
1698                 }
1699                 info = &line_info[index];
1700         }
1702         if (!set_color(&info->fg, argv[1]) ||
1703             !set_color(&info->bg, argv[2])) {
1704                 config_msg = "Unknown color";
1705                 return ERR;
1706         }
1708         info->attr = 0;
1709         while (argc-- > 3) {
1710                 int attr;
1712                 if (!set_attribute(&attr, argv[argc])) {
1713                         config_msg = "Unknown attribute";
1714                         return ERR;
1715                 }
1716                 info->attr |= attr;
1717         }
1719         return OK;
1722 static int parse_bool(bool *opt, const char *arg)
1724         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1725                 ? TRUE : FALSE;
1726         return OK;
1729 static int parse_enum_do(unsigned int *opt, const char *arg,
1730                          const struct enum_map *map, size_t map_size)
1732         bool is_true;
1734         assert(map_size > 1);
1736         if (map_enum_do(map, map_size, (int *) opt, arg))
1737                 return OK;
1739         if (parse_bool(&is_true, arg) != OK)
1740                 return ERR;
1742         *opt = is_true ? map[1].value : map[0].value;
1743         return OK;
1746 #define parse_enum(opt, arg, map) \
1747         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1749 static int
1750 parse_string(char *opt, const char *arg, size_t optsize)
1752         int arglen = strlen(arg);
1754         switch (arg[0]) {
1755         case '\"':
1756         case '\'':
1757                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1758                         config_msg = "Unmatched quotation";
1759                         return ERR;
1760                 }
1761                 arg += 1; arglen -= 2;
1762         default:
1763                 string_ncopy_do(opt, optsize, arg, arglen);
1764                 return OK;
1765         }
1768 /* Wants: name = value */
1769 static int
1770 option_set_command(int argc, const char *argv[])
1772         if (argc != 3) {
1773                 config_msg = "Wrong number of arguments given to set command";
1774                 return ERR;
1775         }
1777         if (strcmp(argv[1], "=")) {
1778                 config_msg = "No value assigned";
1779                 return ERR;
1780         }
1782         if (!strcmp(argv[0], "show-author"))
1783                 return parse_enum(&opt_author, argv[2], author_map);
1785         if (!strcmp(argv[0], "show-date"))
1786                 return parse_enum(&opt_date, argv[2], date_map);
1788         if (!strcmp(argv[0], "show-rev-graph"))
1789                 return parse_bool(&opt_rev_graph, argv[2]);
1791         if (!strcmp(argv[0], "show-refs"))
1792                 return parse_bool(&opt_show_refs, argv[2]);
1794         if (!strcmp(argv[0], "show-line-numbers"))
1795                 return parse_bool(&opt_line_number, argv[2]);
1797         if (!strcmp(argv[0], "line-graphics"))
1798                 return parse_bool(&opt_line_graphics, argv[2]);
1800         if (!strcmp(argv[0], "line-number-interval"))
1801                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1803         if (!strcmp(argv[0], "author-width"))
1804                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1806         if (!strcmp(argv[0], "horizontal-scroll"))
1807                 return parse_step(&opt_hscroll, argv[2]);
1809         if (!strcmp(argv[0], "split-view-height"))
1810                 return parse_step(&opt_scale_split_view, argv[2]);
1812         if (!strcmp(argv[0], "tab-size"))
1813                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1815         if (!strcmp(argv[0], "commit-encoding"))
1816                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1818         config_msg = "Unknown variable name";
1819         return ERR;
1822 /* Wants: mode request key */
1823 static int
1824 option_bind_command(int argc, const char *argv[])
1826         enum request request;
1827         int keymap = -1;
1828         int key;
1830         if (argc < 3) {
1831                 config_msg = "Wrong number of arguments given to bind command";
1832                 return ERR;
1833         }
1835         if (set_keymap(&keymap, argv[0]) == ERR) {
1836                 config_msg = "Unknown key map";
1837                 return ERR;
1838         }
1840         key = get_key_value(argv[1]);
1841         if (key == ERR) {
1842                 config_msg = "Unknown key";
1843                 return ERR;
1844         }
1846         request = get_request(argv[2]);
1847         if (request == REQ_NONE) {
1848                 static const struct enum_map obsolete[] = {
1849                         ENUM_MAP("cherry-pick",         REQ_NONE),
1850                         ENUM_MAP("screen-resize",       REQ_NONE),
1851                         ENUM_MAP("tree-parent",         REQ_PARENT),
1852                 };
1853                 int alias;
1855                 if (map_enum(&alias, obsolete, argv[2])) {
1856                         if (alias != REQ_NONE)
1857                                 add_keybinding(keymap, alias, key);
1858                         config_msg = "Obsolete request name";
1859                         return ERR;
1860                 }
1861         }
1862         if (request == REQ_NONE && *argv[2]++ == '!')
1863                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1864         if (request == REQ_NONE) {
1865                 config_msg = "Unknown request name";
1866                 return ERR;
1867         }
1869         add_keybinding(keymap, request, key);
1871         return OK;
1874 static int
1875 set_option(const char *opt, char *value)
1877         const char *argv[SIZEOF_ARG];
1878         int argc = 0;
1880         if (!argv_from_string(argv, &argc, value)) {
1881                 config_msg = "Too many option arguments";
1882                 return ERR;
1883         }
1885         if (!strcmp(opt, "color"))
1886                 return option_color_command(argc, argv);
1888         if (!strcmp(opt, "set"))
1889                 return option_set_command(argc, argv);
1891         if (!strcmp(opt, "bind"))
1892                 return option_bind_command(argc, argv);
1894         config_msg = "Unknown option command";
1895         return ERR;
1898 static int
1899 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1901         int status = OK;
1903         config_lineno++;
1904         config_msg = "Internal error";
1906         /* Check for comment markers, since read_properties() will
1907          * only ensure opt and value are split at first " \t". */
1908         optlen = strcspn(opt, "#");
1909         if (optlen == 0)
1910                 return OK;
1912         if (opt[optlen] != 0) {
1913                 config_msg = "No option value";
1914                 status = ERR;
1916         }  else {
1917                 /* Look for comment endings in the value. */
1918                 size_t len = strcspn(value, "#");
1920                 if (len < valuelen) {
1921                         valuelen = len;
1922                         value[valuelen] = 0;
1923                 }
1925                 status = set_option(opt, value);
1926         }
1928         if (status == ERR) {
1929                 warn("Error on line %d, near '%.*s': %s",
1930                      config_lineno, (int) optlen, opt, config_msg);
1931                 config_errors = TRUE;
1932         }
1934         /* Always keep going if errors are encountered. */
1935         return OK;
1938 static void
1939 load_option_file(const char *path)
1941         struct io io = {};
1943         /* It's OK that the file doesn't exist. */
1944         if (!io_open(&io, "%s", path))
1945                 return;
1947         config_lineno = 0;
1948         config_errors = FALSE;
1950         if (io_load(&io, " \t", read_option) == ERR ||
1951             config_errors == TRUE)
1952                 warn("Errors while loading %s.", path);
1955 static int
1956 load_options(void)
1958         const char *home = getenv("HOME");
1959         const char *tigrc_user = getenv("TIGRC_USER");
1960         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1961         char buf[SIZEOF_STR];
1963         add_builtin_run_requests();
1965         if (!tigrc_system)
1966                 tigrc_system = SYSCONFDIR "/tigrc";
1967         load_option_file(tigrc_system);
1969         if (!tigrc_user) {
1970                 if (!home || !string_format(buf, "%s/.tigrc", home))
1971                         return ERR;
1972                 tigrc_user = buf;
1973         }
1974         load_option_file(tigrc_user);
1976         return OK;
1980 /*
1981  * The viewer
1982  */
1984 struct view;
1985 struct view_ops;
1987 /* The display array of active views and the index of the current view. */
1988 static struct view *display[2];
1989 static unsigned int current_view;
1991 #define foreach_displayed_view(view, i) \
1992         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1994 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1996 /* Current head and commit ID */
1997 static char ref_blob[SIZEOF_REF]        = "";
1998 static char ref_commit[SIZEOF_REF]      = "HEAD";
1999 static char ref_head[SIZEOF_REF]        = "HEAD";
2001 struct view {
2002         const char *name;       /* View name */
2003         const char *cmd_env;    /* Command line set via environment */
2004         const char *id;         /* Points to either of ref_{head,commit,blob} */
2006         struct view_ops *ops;   /* View operations */
2008         enum keymap keymap;     /* What keymap does this view have */
2009         bool git_dir;           /* Whether the view requires a git directory. */
2011         char ref[SIZEOF_REF];   /* Hovered commit reference */
2012         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2014         int height, width;      /* The width and height of the main window */
2015         WINDOW *win;            /* The main window */
2016         WINDOW *title;          /* The title window living below the main window */
2018         /* Navigation */
2019         unsigned long offset;   /* Offset of the window top */
2020         unsigned long yoffset;  /* Offset from the window side. */
2021         unsigned long lineno;   /* Current line number */
2022         unsigned long p_offset; /* Previous offset of the window top */
2023         unsigned long p_yoffset;/* Previous offset from the window side */
2024         unsigned long p_lineno; /* Previous current line number */
2025         bool p_restore;         /* Should the previous position be restored. */
2027         /* Searching */
2028         char grep[SIZEOF_STR];  /* Search string */
2029         regex_t *regex;         /* Pre-compiled regexp */
2031         /* If non-NULL, points to the view that opened this view. If this view
2032          * is closed tig will switch back to the parent view. */
2033         struct view *parent;
2035         /* Buffering */
2036         size_t lines;           /* Total number of lines */
2037         struct line *line;      /* Line index */
2038         unsigned int digits;    /* Number of digits in the lines member. */
2040         /* Drawing */
2041         struct line *curline;   /* Line currently being drawn. */
2042         enum line_type curtype; /* Attribute currently used for drawing. */
2043         unsigned long col;      /* Column when drawing. */
2044         bool has_scrolled;      /* View was scrolled. */
2046         /* Loading */
2047         struct io io;
2048         struct io *pipe;
2049         time_t start_time;
2050         time_t update_secs;
2051 };
2053 struct view_ops {
2054         /* What type of content being displayed. Used in the title bar. */
2055         const char *type;
2056         /* Default command arguments. */
2057         const char **argv;
2058         /* Open and reads in all view content. */
2059         bool (*open)(struct view *view);
2060         /* Read one line; updates view->line. */
2061         bool (*read)(struct view *view, char *data);
2062         /* Draw one line; @lineno must be < view->height. */
2063         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2064         /* Depending on view handle a special requests. */
2065         enum request (*request)(struct view *view, enum request request, struct line *line);
2066         /* Search for regexp in a line. */
2067         bool (*grep)(struct view *view, struct line *line);
2068         /* Select line */
2069         void (*select)(struct view *view, struct line *line);
2070         /* Prepare view for loading */
2071         bool (*prepare)(struct view *view);
2072 };
2074 static struct view_ops blame_ops;
2075 static struct view_ops blob_ops;
2076 static struct view_ops diff_ops;
2077 static struct view_ops help_ops;
2078 static struct view_ops log_ops;
2079 static struct view_ops main_ops;
2080 static struct view_ops pager_ops;
2081 static struct view_ops stage_ops;
2082 static struct view_ops status_ops;
2083 static struct view_ops tree_ops;
2084 static struct view_ops branch_ops;
2086 #define VIEW_STR(name, env, ref, ops, map, git) \
2087         { name, #env, ref, ops, map, git }
2089 #define VIEW_(id, name, ops, git, ref) \
2090         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2093 static struct view views[] = {
2094         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2095         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2096         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2097         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2098         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2099         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2100         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2101         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2102         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2103         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2104         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2105 };
2107 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2108 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2110 #define foreach_view(view, i) \
2111         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2113 #define view_is_displayed(view) \
2114         (view == display[0] || view == display[1])
2117 enum line_graphic {
2118         LINE_GRAPHIC_VLINE
2119 };
2121 static chtype line_graphics[] = {
2122         /* LINE_GRAPHIC_VLINE: */ '|'
2123 };
2125 static inline void
2126 set_view_attr(struct view *view, enum line_type type)
2128         if (!view->curline->selected && view->curtype != type) {
2129                 wattrset(view->win, get_line_attr(type));
2130                 wchgat(view->win, -1, 0, type, NULL);
2131                 view->curtype = type;
2132         }
2135 static int
2136 draw_chars(struct view *view, enum line_type type, const char *string,
2137            int max_len, bool use_tilde)
2139         static char out_buffer[BUFSIZ * 2];
2140         int len = 0;
2141         int col = 0;
2142         int trimmed = FALSE;
2143         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2145         if (max_len <= 0)
2146                 return 0;
2148         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2150         set_view_attr(view, type);
2151         if (len > 0) {
2152                 if (opt_iconv_out != ICONV_NONE) {
2153                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2154                         size_t inlen = len + 1;
2156                         char *outbuf = out_buffer;
2157                         size_t outlen = sizeof(out_buffer);
2159                         size_t ret;
2161                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2162                         if (ret != (size_t) -1) {
2163                                 string = out_buffer;
2164                                 len = sizeof(out_buffer) - outlen;
2165                         }
2166                 }
2168                 waddnstr(view->win, string, len);
2169         }
2170         if (trimmed && use_tilde) {
2171                 set_view_attr(view, LINE_DELIMITER);
2172                 waddch(view->win, '~');
2173                 col++;
2174         }
2176         return col;
2179 static int
2180 draw_space(struct view *view, enum line_type type, int max, int spaces)
2182         static char space[] = "                    ";
2183         int col = 0;
2185         spaces = MIN(max, spaces);
2187         while (spaces > 0) {
2188                 int len = MIN(spaces, sizeof(space) - 1);
2190                 col += draw_chars(view, type, space, len, FALSE);
2191                 spaces -= len;
2192         }
2194         return col;
2197 static bool
2198 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2200         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2201         return view->width + view->yoffset <= view->col;
2204 static bool
2205 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2207         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2208         int max = view->width + view->yoffset - view->col;
2209         int i;
2211         if (max < size)
2212                 size = max;
2214         set_view_attr(view, type);
2215         /* Using waddch() instead of waddnstr() ensures that
2216          * they'll be rendered correctly for the cursor line. */
2217         for (i = skip; i < size; i++)
2218                 waddch(view->win, graphic[i]);
2220         view->col += size;
2221         if (size < max && skip <= size)
2222                 waddch(view->win, ' ');
2223         view->col++;
2225         return view->width + view->yoffset <= view->col;
2228 static bool
2229 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2231         int max = MIN(view->width + view->yoffset - view->col, len);
2232         int col;
2234         if (text)
2235                 col = draw_chars(view, type, text, max - 1, trim);
2236         else
2237                 col = draw_space(view, type, max - 1, max - 1);
2239         view->col += col;
2240         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2241         return view->width + view->yoffset <= view->col;
2244 static bool
2245 draw_date(struct view *view, time_t *time)
2247         const char *date = time ? mkdate(time) : "";
2248         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2250         return draw_field(view, LINE_DATE, date, cols, FALSE);
2253 static bool
2254 draw_author(struct view *view, const char *author)
2256         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2257         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2259         if (abbreviate && author)
2260                 author = get_author_initials(author, opt_author_cols);
2262         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2265 static bool
2266 draw_mode(struct view *view, mode_t mode)
2268         const char *str;
2270         if (S_ISDIR(mode))
2271                 str = "drwxr-xr-x";
2272         else if (S_ISLNK(mode))
2273                 str = "lrwxrwxrwx";
2274         else if (S_ISGITLINK(mode))
2275                 str = "m---------";
2276         else if (S_ISREG(mode) && mode & S_IXUSR)
2277                 str = "-rwxr-xr-x";
2278         else if (S_ISREG(mode))
2279                 str = "-rw-r--r--";
2280         else
2281                 str = "----------";
2283         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2286 static bool
2287 draw_lineno(struct view *view, unsigned int lineno)
2289         char number[10];
2290         int digits3 = view->digits < 3 ? 3 : view->digits;
2291         int max = MIN(view->width + view->yoffset - view->col, digits3);
2292         char *text = NULL;
2294         lineno += view->offset + 1;
2295         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2296                 static char fmt[] = "%1ld";
2298                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2299                 if (string_format(number, fmt, lineno))
2300                         text = number;
2301         }
2302         if (text)
2303                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2304         else
2305                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2306         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2309 static bool
2310 draw_view_line(struct view *view, unsigned int lineno)
2312         struct line *line;
2313         bool selected = (view->offset + lineno == view->lineno);
2315         assert(view_is_displayed(view));
2317         if (view->offset + lineno >= view->lines)
2318                 return FALSE;
2320         line = &view->line[view->offset + lineno];
2322         wmove(view->win, lineno, 0);
2323         if (line->cleareol)
2324                 wclrtoeol(view->win);
2325         view->col = 0;
2326         view->curline = line;
2327         view->curtype = LINE_NONE;
2328         line->selected = FALSE;
2329         line->dirty = line->cleareol = 0;
2331         if (selected) {
2332                 set_view_attr(view, LINE_CURSOR);
2333                 line->selected = TRUE;
2334                 view->ops->select(view, line);
2335         }
2337         return view->ops->draw(view, line, lineno);
2340 static void
2341 redraw_view_dirty(struct view *view)
2343         bool dirty = FALSE;
2344         int lineno;
2346         for (lineno = 0; lineno < view->height; lineno++) {
2347                 if (view->offset + lineno >= view->lines)
2348                         break;
2349                 if (!view->line[view->offset + lineno].dirty)
2350                         continue;
2351                 dirty = TRUE;
2352                 if (!draw_view_line(view, lineno))
2353                         break;
2354         }
2356         if (!dirty)
2357                 return;
2358         wnoutrefresh(view->win);
2361 static void
2362 redraw_view_from(struct view *view, int lineno)
2364         assert(0 <= lineno && lineno < view->height);
2366         for (; lineno < view->height; lineno++) {
2367                 if (!draw_view_line(view, lineno))
2368                         break;
2369         }
2371         wnoutrefresh(view->win);
2374 static void
2375 redraw_view(struct view *view)
2377         werase(view->win);
2378         redraw_view_from(view, 0);
2382 static void
2383 update_view_title(struct view *view)
2385         char buf[SIZEOF_STR];
2386         char state[SIZEOF_STR];
2387         size_t bufpos = 0, statelen = 0;
2389         assert(view_is_displayed(view));
2391         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2392                 unsigned int view_lines = view->offset + view->height;
2393                 unsigned int lines = view->lines
2394                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2395                                    : 0;
2397                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2398                                    view->ops->type,
2399                                    view->lineno + 1,
2400                                    view->lines,
2401                                    lines);
2403         }
2405         if (view->pipe) {
2406                 time_t secs = time(NULL) - view->start_time;
2408                 /* Three git seconds are a long time ... */
2409                 if (secs > 2)
2410                         string_format_from(state, &statelen, " loading %lds", secs);
2411         }
2413         string_format_from(buf, &bufpos, "[%s]", view->name);
2414         if (*view->ref && bufpos < view->width) {
2415                 size_t refsize = strlen(view->ref);
2416                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2418                 if (minsize < view->width)
2419                         refsize = view->width - minsize + 7;
2420                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2421         }
2423         if (statelen && bufpos < view->width) {
2424                 string_format_from(buf, &bufpos, "%s", state);
2425         }
2427         if (view == display[current_view])
2428                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2429         else
2430                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2432         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2433         wclrtoeol(view->title);
2434         wnoutrefresh(view->title);
2437 static int
2438 apply_step(double step, int value)
2440         if (step >= 1)
2441                 return (int) step;
2442         value *= step + 0.01;
2443         return value ? value : 1;
2446 static void
2447 resize_display(void)
2449         int offset, i;
2450         struct view *base = display[0];
2451         struct view *view = display[1] ? display[1] : display[0];
2453         /* Setup window dimensions */
2455         getmaxyx(stdscr, base->height, base->width);
2457         /* Make room for the status window. */
2458         base->height -= 1;
2460         if (view != base) {
2461                 /* Horizontal split. */
2462                 view->width   = base->width;
2463                 view->height  = apply_step(opt_scale_split_view, base->height);
2464                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2465                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2466                 base->height -= view->height;
2468                 /* Make room for the title bar. */
2469                 view->height -= 1;
2470         }
2472         /* Make room for the title bar. */
2473         base->height -= 1;
2475         offset = 0;
2477         foreach_displayed_view (view, i) {
2478                 if (!view->win) {
2479                         view->win = newwin(view->height, 0, offset, 0);
2480                         if (!view->win)
2481                                 die("Failed to create %s view", view->name);
2483                         scrollok(view->win, FALSE);
2485                         view->title = newwin(1, 0, offset + view->height, 0);
2486                         if (!view->title)
2487                                 die("Failed to create title window");
2489                 } else {
2490                         wresize(view->win, view->height, view->width);
2491                         mvwin(view->win,   offset, 0);
2492                         mvwin(view->title, offset + view->height, 0);
2493                 }
2495                 offset += view->height + 1;
2496         }
2499 static void
2500 redraw_display(bool clear)
2502         struct view *view;
2503         int i;
2505         foreach_displayed_view (view, i) {
2506                 if (clear)
2507                         wclear(view->win);
2508                 redraw_view(view);
2509                 update_view_title(view);
2510         }
2513 static void
2514 toggle_enum_option_do(unsigned int *opt, const char *help,
2515                       const struct enum_map *map, size_t size)
2517         *opt = (*opt + 1) % size;
2518         redraw_display(FALSE);
2519         report("Displaying %s %s", enum_name(map[*opt]), help);
2522 #define toggle_enum_option(opt, help, map) \
2523         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2525 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2526 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2528 static void
2529 toggle_view_option(bool *option, const char *help)
2531         *option = !*option;
2532         redraw_display(FALSE);
2533         report("%sabling %s", *option ? "En" : "Dis", help);
2536 static void
2537 open_option_menu(void)
2539         const struct menu_item menu[] = {
2540                 { '.', "line numbers", &opt_line_number },
2541                 { 'D', "date display", &opt_date },
2542                 { 'A', "author display", &opt_author },
2543                 { 'g', "revision graph display", &opt_rev_graph },
2544                 { 'F', "reference display", &opt_show_refs },
2545                 { 0 }
2546         };
2547         int selected = 0;
2549         if (prompt_menu("Toggle option", menu, &selected)) {
2550                 if (menu[selected].data == &opt_date)
2551                         toggle_date();
2552                 else if (menu[selected].data == &opt_author)
2553                         toggle_author();
2554                 else
2555                         toggle_view_option(menu[selected].data, menu[selected].text);
2556         }
2559 static void
2560 maximize_view(struct view *view)
2562         memset(display, 0, sizeof(display));
2563         current_view = 0;
2564         display[current_view] = view;
2565         resize_display();
2566         redraw_display(FALSE);
2567         report("");
2571 /*
2572  * Navigation
2573  */
2575 static bool
2576 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2578         if (lineno >= view->lines)
2579                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2581         if (offset > lineno || offset + view->height <= lineno) {
2582                 unsigned long half = view->height / 2;
2584                 if (lineno > half)
2585                         offset = lineno - half;
2586                 else
2587                         offset = 0;
2588         }
2590         if (offset != view->offset || lineno != view->lineno) {
2591                 view->offset = offset;
2592                 view->lineno = lineno;
2593                 return TRUE;
2594         }
2596         return FALSE;
2599 /* Scrolling backend */
2600 static void
2601 do_scroll_view(struct view *view, int lines)
2603         bool redraw_current_line = FALSE;
2605         /* The rendering expects the new offset. */
2606         view->offset += lines;
2608         assert(0 <= view->offset && view->offset < view->lines);
2609         assert(lines);
2611         /* Move current line into the view. */
2612         if (view->lineno < view->offset) {
2613                 view->lineno = view->offset;
2614                 redraw_current_line = TRUE;
2615         } else if (view->lineno >= view->offset + view->height) {
2616                 view->lineno = view->offset + view->height - 1;
2617                 redraw_current_line = TRUE;
2618         }
2620         assert(view->offset <= view->lineno && view->lineno < view->lines);
2622         /* Redraw the whole screen if scrolling is pointless. */
2623         if (view->height < ABS(lines)) {
2624                 redraw_view(view);
2626         } else {
2627                 int line = lines > 0 ? view->height - lines : 0;
2628                 int end = line + ABS(lines);
2630                 scrollok(view->win, TRUE);
2631                 wscrl(view->win, lines);
2632                 scrollok(view->win, FALSE);
2634                 while (line < end && draw_view_line(view, line))
2635                         line++;
2637                 if (redraw_current_line)
2638                         draw_view_line(view, view->lineno - view->offset);
2639                 wnoutrefresh(view->win);
2640         }
2642         view->has_scrolled = TRUE;
2643         report("");
2646 /* Scroll frontend */
2647 static void
2648 scroll_view(struct view *view, enum request request)
2650         int lines = 1;
2652         assert(view_is_displayed(view));
2654         switch (request) {
2655         case REQ_SCROLL_LEFT:
2656                 if (view->yoffset == 0) {
2657                         report("Cannot scroll beyond the first column");
2658                         return;
2659                 }
2660                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2661                         view->yoffset = 0;
2662                 else
2663                         view->yoffset -= apply_step(opt_hscroll, view->width);
2664                 redraw_view_from(view, 0);
2665                 report("");
2666                 return;
2667         case REQ_SCROLL_RIGHT:
2668                 view->yoffset += apply_step(opt_hscroll, view->width);
2669                 redraw_view(view);
2670                 report("");
2671                 return;
2672         case REQ_SCROLL_PAGE_DOWN:
2673                 lines = view->height;
2674         case REQ_SCROLL_LINE_DOWN:
2675                 if (view->offset + lines > view->lines)
2676                         lines = view->lines - view->offset;
2678                 if (lines == 0 || view->offset + view->height >= view->lines) {
2679                         report("Cannot scroll beyond the last line");
2680                         return;
2681                 }
2682                 break;
2684         case REQ_SCROLL_PAGE_UP:
2685                 lines = view->height;
2686         case REQ_SCROLL_LINE_UP:
2687                 if (lines > view->offset)
2688                         lines = view->offset;
2690                 if (lines == 0) {
2691                         report("Cannot scroll beyond the first line");
2692                         return;
2693                 }
2695                 lines = -lines;
2696                 break;
2698         default:
2699                 die("request %d not handled in switch", request);
2700         }
2702         do_scroll_view(view, lines);
2705 /* Cursor moving */
2706 static void
2707 move_view(struct view *view, enum request request)
2709         int scroll_steps = 0;
2710         int steps;
2712         switch (request) {
2713         case REQ_MOVE_FIRST_LINE:
2714                 steps = -view->lineno;
2715                 break;
2717         case REQ_MOVE_LAST_LINE:
2718                 steps = view->lines - view->lineno - 1;
2719                 break;
2721         case REQ_MOVE_PAGE_UP:
2722                 steps = view->height > view->lineno
2723                       ? -view->lineno : -view->height;
2724                 break;
2726         case REQ_MOVE_PAGE_DOWN:
2727                 steps = view->lineno + view->height >= view->lines
2728                       ? view->lines - view->lineno - 1 : view->height;
2729                 break;
2731         case REQ_MOVE_UP:
2732                 steps = -1;
2733                 break;
2735         case REQ_MOVE_DOWN:
2736                 steps = 1;
2737                 break;
2739         default:
2740                 die("request %d not handled in switch", request);
2741         }
2743         if (steps <= 0 && view->lineno == 0) {
2744                 report("Cannot move beyond the first line");
2745                 return;
2747         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2748                 report("Cannot move beyond the last line");
2749                 return;
2750         }
2752         /* Move the current line */
2753         view->lineno += steps;
2754         assert(0 <= view->lineno && view->lineno < view->lines);
2756         /* Check whether the view needs to be scrolled */
2757         if (view->lineno < view->offset ||
2758             view->lineno >= view->offset + view->height) {
2759                 scroll_steps = steps;
2760                 if (steps < 0 && -steps > view->offset) {
2761                         scroll_steps = -view->offset;
2763                 } else if (steps > 0) {
2764                         if (view->lineno == view->lines - 1 &&
2765                             view->lines > view->height) {
2766                                 scroll_steps = view->lines - view->offset - 1;
2767                                 if (scroll_steps >= view->height)
2768                                         scroll_steps -= view->height - 1;
2769                         }
2770                 }
2771         }
2773         if (!view_is_displayed(view)) {
2774                 view->offset += scroll_steps;
2775                 assert(0 <= view->offset && view->offset < view->lines);
2776                 view->ops->select(view, &view->line[view->lineno]);
2777                 return;
2778         }
2780         /* Repaint the old "current" line if we be scrolling */
2781         if (ABS(steps) < view->height)
2782                 draw_view_line(view, view->lineno - steps - view->offset);
2784         if (scroll_steps) {
2785                 do_scroll_view(view, scroll_steps);
2786                 return;
2787         }
2789         /* Draw the current line */
2790         draw_view_line(view, view->lineno - view->offset);
2792         wnoutrefresh(view->win);
2793         report("");
2797 /*
2798  * Searching
2799  */
2801 static void search_view(struct view *view, enum request request);
2803 static bool
2804 grep_text(struct view *view, const char *text[])
2806         regmatch_t pmatch;
2807         size_t i;
2809         for (i = 0; text[i]; i++)
2810                 if (*text[i] &&
2811                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2812                         return TRUE;
2813         return FALSE;
2816 static void
2817 select_view_line(struct view *view, unsigned long lineno)
2819         unsigned long old_lineno = view->lineno;
2820         unsigned long old_offset = view->offset;
2822         if (goto_view_line(view, view->offset, lineno)) {
2823                 if (view_is_displayed(view)) {
2824                         if (old_offset != view->offset) {
2825                                 redraw_view(view);
2826                         } else {
2827                                 draw_view_line(view, old_lineno - view->offset);
2828                                 draw_view_line(view, view->lineno - view->offset);
2829                                 wnoutrefresh(view->win);
2830                         }
2831                 } else {
2832                         view->ops->select(view, &view->line[view->lineno]);
2833                 }
2834         }
2837 static void
2838 find_next(struct view *view, enum request request)
2840         unsigned long lineno = view->lineno;
2841         int direction;
2843         if (!*view->grep) {
2844                 if (!*opt_search)
2845                         report("No previous search");
2846                 else
2847                         search_view(view, request);
2848                 return;
2849         }
2851         switch (request) {
2852         case REQ_SEARCH:
2853         case REQ_FIND_NEXT:
2854                 direction = 1;
2855                 break;
2857         case REQ_SEARCH_BACK:
2858         case REQ_FIND_PREV:
2859                 direction = -1;
2860                 break;
2862         default:
2863                 return;
2864         }
2866         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2867                 lineno += direction;
2869         /* Note, lineno is unsigned long so will wrap around in which case it
2870          * will become bigger than view->lines. */
2871         for (; lineno < view->lines; lineno += direction) {
2872                 if (view->ops->grep(view, &view->line[lineno])) {
2873                         select_view_line(view, lineno);
2874                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2875                         return;
2876                 }
2877         }
2879         report("No match found for '%s'", view->grep);
2882 static void
2883 search_view(struct view *view, enum request request)
2885         int regex_err;
2887         if (view->regex) {
2888                 regfree(view->regex);
2889                 *view->grep = 0;
2890         } else {
2891                 view->regex = calloc(1, sizeof(*view->regex));
2892                 if (!view->regex)
2893                         return;
2894         }
2896         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2897         if (regex_err != 0) {
2898                 char buf[SIZEOF_STR] = "unknown error";
2900                 regerror(regex_err, view->regex, buf, sizeof(buf));
2901                 report("Search failed: %s", buf);
2902                 return;
2903         }
2905         string_copy(view->grep, opt_search);
2907         find_next(view, request);
2910 /*
2911  * Incremental updating
2912  */
2914 static void
2915 reset_view(struct view *view)
2917         int i;
2919         for (i = 0; i < view->lines; i++)
2920                 free(view->line[i].data);
2921         free(view->line);
2923         view->p_offset = view->offset;
2924         view->p_yoffset = view->yoffset;
2925         view->p_lineno = view->lineno;
2927         view->line = NULL;
2928         view->offset = 0;
2929         view->yoffset = 0;
2930         view->lines  = 0;
2931         view->lineno = 0;
2932         view->vid[0] = 0;
2933         view->update_secs = 0;
2936 static void
2937 free_argv(const char *argv[])
2939         int argc;
2941         for (argc = 0; argv[argc]; argc++)
2942                 free((void *) argv[argc]);
2945 static const char *
2946 format_arg(const char *name)
2948         static struct {
2949                 const char *name;
2950                 size_t namelen;
2951                 const char *value;
2952                 const char *value_if_empty;
2953         } vars[] = {
2954 #define FORMAT_VAR(name, value, value_if_empty) \
2955         { name, STRING_SIZE(name), value, value_if_empty }
2956                 FORMAT_VAR("%(directory)",      opt_path,       ""),
2957                 FORMAT_VAR("%(file)",           opt_file,       ""),
2958                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
2959                 FORMAT_VAR("%(head)",           ref_head,       ""),
2960                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
2961                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
2962         };
2963         int i;
2965         for (i = 0; i < ARRAY_SIZE(vars); i++)
2966                 if (!strncmp(name, vars[i].name, vars[i].namelen))
2967                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2969         return NULL;
2971 static bool
2972 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2974         char buf[SIZEOF_STR];
2975         int argc;
2976         bool noreplace = flags == FORMAT_NONE;
2978         free_argv(dst_argv);
2980         for (argc = 0; src_argv[argc]; argc++) {
2981                 const char *arg = src_argv[argc];
2982                 size_t bufpos = 0;
2984                 while (arg) {
2985                         char *next = strstr(arg, "%(");
2986                         int len = next - arg;
2987                         const char *value;
2989                         if (!next || noreplace) {
2990                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2991                                         noreplace = TRUE;
2992                                 len = strlen(arg);
2993                                 value = "";
2995                         } else {
2996                                 value = format_arg(next);
2998                                 if (!value) {
2999                                         report("Unknown replacement: `%s`", next);
3000                                         return FALSE;
3001                                 }
3002                         }
3004                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3005                                 return FALSE;
3007                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3008                 }
3010                 dst_argv[argc] = strdup(buf);
3011                 if (!dst_argv[argc])
3012                         break;
3013         }
3015         dst_argv[argc] = NULL;
3017         return src_argv[argc] == NULL;
3020 static bool
3021 restore_view_position(struct view *view)
3023         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3024                 return FALSE;
3026         /* Changing the view position cancels the restoring. */
3027         /* FIXME: Changing back to the first line is not detected. */
3028         if (view->offset != 0 || view->lineno != 0) {
3029                 view->p_restore = FALSE;
3030                 return FALSE;
3031         }
3033         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3034             view_is_displayed(view))
3035                 werase(view->win);
3037         view->yoffset = view->p_yoffset;
3038         view->p_restore = FALSE;
3040         return TRUE;
3043 static void
3044 end_update(struct view *view, bool force)
3046         if (!view->pipe)
3047                 return;
3048         while (!view->ops->read(view, NULL))
3049                 if (!force)
3050                         return;
3051         set_nonblocking_input(FALSE);
3052         if (force)
3053                 kill_io(view->pipe);
3054         done_io(view->pipe);
3055         view->pipe = NULL;
3058 static void
3059 setup_update(struct view *view, const char *vid)
3061         set_nonblocking_input(TRUE);
3062         reset_view(view);
3063         string_copy_rev(view->vid, vid);
3064         view->pipe = &view->io;
3065         view->start_time = time(NULL);
3068 static bool
3069 prepare_update(struct view *view, const char *argv[], const char *dir,
3070                enum format_flags flags)
3072         if (view->pipe)
3073                 end_update(view, TRUE);
3074         return init_io_rd(&view->io, argv, dir, flags);
3077 static bool
3078 prepare_update_file(struct view *view, const char *name)
3080         if (view->pipe)
3081                 end_update(view, TRUE);
3082         return io_open(&view->io, "%s", name);
3085 static bool
3086 begin_update(struct view *view, bool refresh)
3088         if (view->pipe)
3089                 end_update(view, TRUE);
3091         if (!refresh) {
3092                 if (view->ops->prepare) {
3093                         if (!view->ops->prepare(view))
3094                                 return FALSE;
3095                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3096                         return FALSE;
3097                 }
3099                 /* Put the current ref_* value to the view title ref
3100                  * member. This is needed by the blob view. Most other
3101                  * views sets it automatically after loading because the
3102                  * first line is a commit line. */
3103                 string_copy_rev(view->ref, view->id);
3104         }
3106         if (!start_io(&view->io))
3107                 return FALSE;
3109         setup_update(view, view->id);
3111         return TRUE;
3114 static bool
3115 update_view(struct view *view)
3117         char out_buffer[BUFSIZ * 2];
3118         char *line;
3119         /* Clear the view and redraw everything since the tree sorting
3120          * might have rearranged things. */
3121         bool redraw = view->lines == 0;
3122         bool can_read = TRUE;
3124         if (!view->pipe)
3125                 return TRUE;
3127         if (!io_can_read(view->pipe)) {
3128                 if (view->lines == 0 && view_is_displayed(view)) {
3129                         time_t secs = time(NULL) - view->start_time;
3131                         if (secs > 1 && secs > view->update_secs) {
3132                                 if (view->update_secs == 0)
3133                                         redraw_view(view);
3134                                 update_view_title(view);
3135                                 view->update_secs = secs;
3136                         }
3137                 }
3138                 return TRUE;
3139         }
3141         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3142                 if (opt_iconv_in != ICONV_NONE) {
3143                         ICONV_CONST char *inbuf = line;
3144                         size_t inlen = strlen(line) + 1;
3146                         char *outbuf = out_buffer;
3147                         size_t outlen = sizeof(out_buffer);
3149                         size_t ret;
3151                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3152                         if (ret != (size_t) -1)
3153                                 line = out_buffer;
3154                 }
3156                 if (!view->ops->read(view, line)) {
3157                         report("Allocation failure");
3158                         end_update(view, TRUE);
3159                         return FALSE;
3160                 }
3161         }
3163         {
3164                 unsigned long lines = view->lines;
3165                 int digits;
3167                 for (digits = 0; lines; digits++)
3168                         lines /= 10;
3170                 /* Keep the displayed view in sync with line number scaling. */
3171                 if (digits != view->digits) {
3172                         view->digits = digits;
3173                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3174                                 redraw = TRUE;
3175                 }
3176         }
3178         if (io_error(view->pipe)) {
3179                 report("Failed to read: %s", io_strerror(view->pipe));
3180                 end_update(view, TRUE);
3182         } else if (io_eof(view->pipe)) {
3183                 report("");
3184                 end_update(view, FALSE);
3185         }
3187         if (restore_view_position(view))
3188                 redraw = TRUE;
3190         if (!view_is_displayed(view))
3191                 return TRUE;
3193         if (redraw)
3194                 redraw_view_from(view, 0);
3195         else
3196                 redraw_view_dirty(view);
3198         /* Update the title _after_ the redraw so that if the redraw picks up a
3199          * commit reference in view->ref it'll be available here. */
3200         update_view_title(view);
3201         return TRUE;
3204 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3206 static struct line *
3207 add_line_data(struct view *view, void *data, enum line_type type)
3209         struct line *line;
3211         if (!realloc_lines(&view->line, view->lines, 1))
3212                 return NULL;
3214         line = &view->line[view->lines++];
3215         memset(line, 0, sizeof(*line));
3216         line->type = type;
3217         line->data = data;
3218         line->dirty = 1;
3220         return line;
3223 static struct line *
3224 add_line_text(struct view *view, const char *text, enum line_type type)
3226         char *data = text ? strdup(text) : NULL;
3228         return data ? add_line_data(view, data, type) : NULL;
3231 static struct line *
3232 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3234         char buf[SIZEOF_STR];
3235         va_list args;
3237         va_start(args, fmt);
3238         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3239                 buf[0] = 0;
3240         va_end(args);
3242         return buf[0] ? add_line_text(view, buf, type) : NULL;
3245 /*
3246  * View opening
3247  */
3249 enum open_flags {
3250         OPEN_DEFAULT = 0,       /* Use default view switching. */
3251         OPEN_SPLIT = 1,         /* Split current view. */
3252         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3253         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3254         OPEN_PREPARED = 32,     /* Open already prepared command. */
3255 };
3257 static void
3258 open_view(struct view *prev, enum request request, enum open_flags flags)
3260         bool split = !!(flags & OPEN_SPLIT);
3261         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3262         bool nomaximize = !!(flags & OPEN_REFRESH);
3263         struct view *view = VIEW(request);
3264         int nviews = displayed_views();
3265         struct view *base_view = display[0];
3267         if (view == prev && nviews == 1 && !reload) {
3268                 report("Already in %s view", view->name);
3269                 return;
3270         }
3272         if (view->git_dir && !opt_git_dir[0]) {
3273                 report("The %s view is disabled in pager view", view->name);
3274                 return;
3275         }
3277         if (split) {
3278                 display[1] = view;
3279                 current_view = 1;
3280         } else if (!nomaximize) {
3281                 /* Maximize the current view. */
3282                 memset(display, 0, sizeof(display));
3283                 current_view = 0;
3284                 display[current_view] = view;
3285         }
3287         /* No parent signals that this is the first loaded view. */
3288         if (prev && view != prev) {
3289                 view->parent = prev;
3290         }
3292         /* Resize the view when switching between split- and full-screen,
3293          * or when switching between two different full-screen views. */
3294         if (nviews != displayed_views() ||
3295             (nviews == 1 && base_view != display[0]))
3296                 resize_display();
3298         if (view->ops->open) {
3299                 if (view->pipe)
3300                         end_update(view, TRUE);
3301                 if (!view->ops->open(view)) {
3302                         report("Failed to load %s view", view->name);
3303                         return;
3304                 }
3305                 restore_view_position(view);
3307         } else if ((reload || strcmp(view->vid, view->id)) &&
3308                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3309                 report("Failed to load %s view", view->name);
3310                 return;
3311         }
3313         if (split && prev->lineno - prev->offset >= prev->height) {
3314                 /* Take the title line into account. */
3315                 int lines = prev->lineno - prev->offset - prev->height + 1;
3317                 /* Scroll the view that was split if the current line is
3318                  * outside the new limited view. */
3319                 do_scroll_view(prev, lines);
3320         }
3322         if (prev && view != prev && split && view_is_displayed(prev)) {
3323                 /* "Blur" the previous view. */
3324                 update_view_title(prev);
3325         }
3327         if (view->pipe && view->lines == 0) {
3328                 /* Clear the old view and let the incremental updating refill
3329                  * the screen. */
3330                 werase(view->win);
3331                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3332                 report("");
3333         } else if (view_is_displayed(view)) {
3334                 redraw_view(view);
3335                 report("");
3336         }
3339 static void
3340 open_external_viewer(const char *argv[], const char *dir)
3342         def_prog_mode();           /* save current tty modes */
3343         endwin();                  /* restore original tty modes */
3344         run_io_fg(argv, dir);
3345         fprintf(stderr, "Press Enter to continue");
3346         getc(opt_tty);
3347         reset_prog_mode();
3348         redraw_display(TRUE);
3351 static void
3352 open_mergetool(const char *file)
3354         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3356         open_external_viewer(mergetool_argv, opt_cdup);
3359 static void
3360 open_editor(bool from_root, const char *file)
3362         const char *editor_argv[] = { "vi", file, NULL };
3363         const char *editor;
3365         editor = getenv("GIT_EDITOR");
3366         if (!editor && *opt_editor)
3367                 editor = opt_editor;
3368         if (!editor)
3369                 editor = getenv("VISUAL");
3370         if (!editor)
3371                 editor = getenv("EDITOR");
3372         if (!editor)
3373                 editor = "vi";
3375         editor_argv[0] = editor;
3376         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3379 static void
3380 open_run_request(enum request request)
3382         struct run_request *req = get_run_request(request);
3383         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3385         if (!req) {
3386                 report("Unknown run request");
3387                 return;
3388         }
3390         if (format_argv(argv, req->argv, FORMAT_ALL))
3391                 open_external_viewer(argv, NULL);
3392         free_argv(argv);
3395 /*
3396  * User request switch noodle
3397  */
3399 static int
3400 view_driver(struct view *view, enum request request)
3402         int i;
3404         if (request == REQ_NONE)
3405                 return TRUE;
3407         if (request > REQ_NONE) {
3408                 open_run_request(request);
3409                 /* FIXME: When all views can refresh always do this. */
3410                 if (view == VIEW(REQ_VIEW_STATUS) ||
3411                     view == VIEW(REQ_VIEW_MAIN) ||
3412                     view == VIEW(REQ_VIEW_LOG) ||
3413                     view == VIEW(REQ_VIEW_BRANCH) ||
3414                     view == VIEW(REQ_VIEW_STAGE))
3415                         request = REQ_REFRESH;
3416                 else
3417                         return TRUE;
3418         }
3420         if (view && view->lines) {
3421                 request = view->ops->request(view, request, &view->line[view->lineno]);
3422                 if (request == REQ_NONE)
3423                         return TRUE;
3424         }
3426         switch (request) {
3427         case REQ_MOVE_UP:
3428         case REQ_MOVE_DOWN:
3429         case REQ_MOVE_PAGE_UP:
3430         case REQ_MOVE_PAGE_DOWN:
3431         case REQ_MOVE_FIRST_LINE:
3432         case REQ_MOVE_LAST_LINE:
3433                 move_view(view, request);
3434                 break;
3436         case REQ_SCROLL_LEFT:
3437         case REQ_SCROLL_RIGHT:
3438         case REQ_SCROLL_LINE_DOWN:
3439         case REQ_SCROLL_LINE_UP:
3440         case REQ_SCROLL_PAGE_DOWN:
3441         case REQ_SCROLL_PAGE_UP:
3442                 scroll_view(view, request);
3443                 break;
3445         case REQ_VIEW_BLAME:
3446                 if (!opt_file[0]) {
3447                         report("No file chosen, press %s to open tree view",
3448                                get_key(view->keymap, REQ_VIEW_TREE));
3449                         break;
3450                 }
3451                 open_view(view, request, OPEN_DEFAULT);
3452                 break;
3454         case REQ_VIEW_BLOB:
3455                 if (!ref_blob[0]) {
3456                         report("No file chosen, press %s to open tree view",
3457                                get_key(view->keymap, REQ_VIEW_TREE));
3458                         break;
3459                 }
3460                 open_view(view, request, OPEN_DEFAULT);
3461                 break;
3463         case REQ_VIEW_PAGER:
3464                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3465                         report("No pager content, press %s to run command from prompt",
3466                                get_key(view->keymap, REQ_PROMPT));
3467                         break;
3468                 }
3469                 open_view(view, request, OPEN_DEFAULT);
3470                 break;
3472         case REQ_VIEW_STAGE:
3473                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3474                         report("No stage content, press %s to open the status view and choose file",
3475                                get_key(view->keymap, REQ_VIEW_STATUS));
3476                         break;
3477                 }
3478                 open_view(view, request, OPEN_DEFAULT);
3479                 break;
3481         case REQ_VIEW_STATUS:
3482                 if (opt_is_inside_work_tree == FALSE) {
3483                         report("The status view requires a working tree");
3484                         break;
3485                 }
3486                 open_view(view, request, OPEN_DEFAULT);
3487                 break;
3489         case REQ_VIEW_MAIN:
3490         case REQ_VIEW_DIFF:
3491         case REQ_VIEW_LOG:
3492         case REQ_VIEW_TREE:
3493         case REQ_VIEW_HELP:
3494         case REQ_VIEW_BRANCH:
3495                 open_view(view, request, OPEN_DEFAULT);
3496                 break;
3498         case REQ_NEXT:
3499         case REQ_PREVIOUS:
3500                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3502                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3503                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3504                    (view == VIEW(REQ_VIEW_DIFF) &&
3505                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3506                    (view == VIEW(REQ_VIEW_STAGE) &&
3507                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3508                    (view == VIEW(REQ_VIEW_BLOB) &&
3509                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3510                    (view == VIEW(REQ_VIEW_MAIN) &&
3511                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3512                         int line;
3514                         view = view->parent;
3515                         line = view->lineno;
3516                         move_view(view, request);
3517                         if (view_is_displayed(view))
3518                                 update_view_title(view);
3519                         if (line != view->lineno)
3520                                 view->ops->request(view, REQ_ENTER,
3521                                                    &view->line[view->lineno]);
3523                 } else {
3524                         move_view(view, request);
3525                 }
3526                 break;
3528         case REQ_VIEW_NEXT:
3529         {
3530                 int nviews = displayed_views();
3531                 int next_view = (current_view + 1) % nviews;
3533                 if (next_view == current_view) {
3534                         report("Only one view is displayed");
3535                         break;
3536                 }
3538                 current_view = next_view;
3539                 /* Blur out the title of the previous view. */
3540                 update_view_title(view);
3541                 report("");
3542                 break;
3543         }
3544         case REQ_REFRESH:
3545                 report("Refreshing is not yet supported for the %s view", view->name);
3546                 break;
3548         case REQ_MAXIMIZE:
3549                 if (displayed_views() == 2)
3550                         maximize_view(view);
3551                 break;
3553         case REQ_OPTIONS:
3554                 open_option_menu();
3555                 break;
3557         case REQ_TOGGLE_LINENO:
3558                 toggle_view_option(&opt_line_number, "line numbers");
3559                 break;
3561         case REQ_TOGGLE_DATE:
3562                 toggle_date();
3563                 break;
3565         case REQ_TOGGLE_AUTHOR:
3566                 toggle_author();
3567                 break;
3569         case REQ_TOGGLE_REV_GRAPH:
3570                 toggle_view_option(&opt_rev_graph, "revision graph display");
3571                 break;
3573         case REQ_TOGGLE_REFS:
3574                 toggle_view_option(&opt_show_refs, "reference display");
3575                 break;
3577         case REQ_TOGGLE_SORT_FIELD:
3578         case REQ_TOGGLE_SORT_ORDER:
3579                 report("Sorting is not yet supported for the %s view", view->name);
3580                 break;
3582         case REQ_SEARCH:
3583         case REQ_SEARCH_BACK:
3584                 search_view(view, request);
3585                 break;
3587         case REQ_FIND_NEXT:
3588         case REQ_FIND_PREV:
3589                 find_next(view, request);
3590                 break;
3592         case REQ_STOP_LOADING:
3593                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3594                         view = &views[i];
3595                         if (view->pipe)
3596                                 report("Stopped loading the %s view", view->name),
3597                         end_update(view, TRUE);
3598                 }
3599                 break;
3601         case REQ_SHOW_VERSION:
3602                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3603                 return TRUE;
3605         case REQ_SCREEN_REDRAW:
3606                 redraw_display(TRUE);
3607                 break;
3609         case REQ_EDIT:
3610                 report("Nothing to edit");
3611                 break;
3613         case REQ_ENTER:
3614                 report("Nothing to enter");
3615                 break;
3617         case REQ_VIEW_CLOSE:
3618                 /* XXX: Mark closed views by letting view->parent point to the
3619                  * view itself. Parents to closed view should never be
3620                  * followed. */
3621                 if (view->parent &&
3622                     view->parent->parent != view->parent) {
3623                         maximize_view(view->parent);
3624                         view->parent = view;
3625                         break;
3626                 }
3627                 /* Fall-through */
3628         case REQ_QUIT:
3629                 return FALSE;
3631         default:
3632                 report("Unknown key, press %s for help",
3633                        get_key(view->keymap, REQ_VIEW_HELP));
3634                 return TRUE;
3635         }
3637         return TRUE;
3641 /*
3642  * View backend utilities
3643  */
3645 enum sort_field {
3646         ORDERBY_NAME,
3647         ORDERBY_DATE,
3648         ORDERBY_AUTHOR,
3649 };
3651 struct sort_state {
3652         const enum sort_field *fields;
3653         size_t size, current;
3654         bool reverse;
3655 };
3657 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3658 #define get_sort_field(state) ((state).fields[(state).current])
3659 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3661 static void
3662 sort_view(struct view *view, enum request request, struct sort_state *state,
3663           int (*compare)(const void *, const void *))
3665         switch (request) {
3666         case REQ_TOGGLE_SORT_FIELD:
3667                 state->current = (state->current + 1) % state->size;
3668                 break;
3670         case REQ_TOGGLE_SORT_ORDER:
3671                 state->reverse = !state->reverse;
3672                 break;
3673         default:
3674                 die("Not a sort request");
3675         }
3677         qsort(view->line, view->lines, sizeof(*view->line), compare);
3678         redraw_view(view);
3681 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3683 /* Small author cache to reduce memory consumption. It uses binary
3684  * search to lookup or find place to position new entries. No entries
3685  * are ever freed. */
3686 static const char *
3687 get_author(const char *name)
3689         static const char **authors;
3690         static size_t authors_size;
3691         int from = 0, to = authors_size - 1;
3693         while (from <= to) {
3694                 size_t pos = (to + from) / 2;
3695                 int cmp = strcmp(name, authors[pos]);
3697                 if (!cmp)
3698                         return authors[pos];
3700                 if (cmp < 0)
3701                         to = pos - 1;
3702                 else
3703                         from = pos + 1;
3704         }
3706         if (!realloc_authors(&authors, authors_size, 1))
3707                 return NULL;
3708         name = strdup(name);
3709         if (!name)
3710                 return NULL;
3712         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3713         authors[from] = name;
3714         authors_size++;
3716         return name;
3719 static void
3720 parse_timezone(time_t *time, const char *zone)
3722         long tz;
3724         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3725         tz += ('0' - zone[2]) * 60 * 60;
3726         tz += ('0' - zone[3]) * 60;
3727         tz += ('0' - zone[4]);
3729         if (zone[0] == '-')
3730                 tz = -tz;
3732         *time -= tz;
3735 /* Parse author lines where the name may be empty:
3736  *      author  <email@address.tld> 1138474660 +0100
3737  */
3738 static void
3739 parse_author_line(char *ident, const char **author, time_t *time)
3741         char *nameend = strchr(ident, '<');
3742         char *emailend = strchr(ident, '>');
3744         if (nameend && emailend)
3745                 *nameend = *emailend = 0;
3746         ident = chomp_string(ident);
3747         if (!*ident) {
3748                 if (nameend)
3749                         ident = chomp_string(nameend + 1);
3750                 if (!*ident)
3751                         ident = "Unknown";
3752         }
3754         *author = get_author(ident);
3756         /* Parse epoch and timezone */
3757         if (emailend && emailend[1] == ' ') {
3758                 char *secs = emailend + 2;
3759                 char *zone = strchr(secs, ' ');
3761                 *time = (time_t) atol(secs);
3763                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3764                         parse_timezone(time, zone + 1);
3765         }
3768 static bool
3769 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3771         char rev[SIZEOF_REV];
3772         const char *revlist_argv[] = {
3773                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3774         };
3775         struct menu_item *items;
3776         char text[SIZEOF_STR];
3777         bool ok = TRUE;
3778         int i;
3780         items = calloc(*parents + 1, sizeof(*items));
3781         if (!items)
3782                 return FALSE;
3784         for (i = 0; i < *parents; i++) {
3785                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3786                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3787                     !(items[i].text = strdup(text))) {
3788                         ok = FALSE;
3789                         break;
3790                 }
3791         }
3793         if (ok) {
3794                 *parents = 0;
3795                 ok = prompt_menu("Select parent", items, parents);
3796         }
3797         for (i = 0; items[i].text; i++)
3798                 free((char *) items[i].text);
3799         free(items);
3800         return ok;
3803 static bool
3804 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3806         char buf[SIZEOF_STR * 4];
3807         const char *revlist_argv[] = {
3808                 "git", "log", "--no-color", "-1",
3809                         "--pretty=format:%P", id, "--", path, NULL
3810         };
3811         int parents;
3813         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3814             (parents = strlen(buf) / 40) < 0) {
3815                 report("Failed to get parent information");
3816                 return FALSE;
3818         } else if (parents == 0) {
3819                 if (path)
3820                         report("Path '%s' does not exist in the parent", path);
3821                 else
3822                         report("The selected commit has no parents");
3823                 return FALSE;
3824         }
3826         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3827                 return FALSE;
3829         string_copy_rev(rev, &buf[41 * parents]);
3830         return TRUE;
3833 /*
3834  * Pager backend
3835  */
3837 static bool
3838 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3840         char text[SIZEOF_STR];
3842         if (opt_line_number && draw_lineno(view, lineno))
3843                 return TRUE;
3845         string_expand(text, sizeof(text), line->data, opt_tab_size);
3846         draw_text(view, line->type, text, TRUE);
3847         return TRUE;
3850 static bool
3851 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3853         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3854         char ref[SIZEOF_STR];
3856         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3857                 return TRUE;
3859         /* This is the only fatal call, since it can "corrupt" the buffer. */
3860         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3861                 return FALSE;
3863         return TRUE;
3866 static void
3867 add_pager_refs(struct view *view, struct line *line)
3869         char buf[SIZEOF_STR];
3870         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3871         struct ref_list *list;
3872         size_t bufpos = 0, i;
3873         const char *sep = "Refs: ";
3874         bool is_tag = FALSE;
3876         assert(line->type == LINE_COMMIT);
3878         list = get_ref_list(commit_id);
3879         if (!list) {
3880                 if (view == VIEW(REQ_VIEW_DIFF))
3881                         goto try_add_describe_ref;
3882                 return;
3883         }
3885         for (i = 0; i < list->size; i++) {
3886                 struct ref *ref = list->refs[i];
3887                 const char *fmt = ref->tag    ? "%s[%s]" :
3888                                   ref->remote ? "%s<%s>" : "%s%s";
3890                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3891                         return;
3892                 sep = ", ";
3893                 if (ref->tag)
3894                         is_tag = TRUE;
3895         }
3897         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3898 try_add_describe_ref:
3899                 /* Add <tag>-g<commit_id> "fake" reference. */
3900                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3901                         return;
3902         }
3904         if (bufpos == 0)
3905                 return;
3907         add_line_text(view, buf, LINE_PP_REFS);
3910 static bool
3911 pager_read(struct view *view, char *data)
3913         struct line *line;
3915         if (!data)
3916                 return TRUE;
3918         line = add_line_text(view, data, get_line_type(data));
3919         if (!line)
3920                 return FALSE;
3922         if (line->type == LINE_COMMIT &&
3923             (view == VIEW(REQ_VIEW_DIFF) ||
3924              view == VIEW(REQ_VIEW_LOG)))
3925                 add_pager_refs(view, line);
3927         return TRUE;
3930 static enum request
3931 pager_request(struct view *view, enum request request, struct line *line)
3933         int split = 0;
3935         if (request != REQ_ENTER)
3936                 return request;
3938         if (line->type == LINE_COMMIT &&
3939            (view == VIEW(REQ_VIEW_LOG) ||
3940             view == VIEW(REQ_VIEW_PAGER))) {
3941                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3942                 split = 1;
3943         }
3945         /* Always scroll the view even if it was split. That way
3946          * you can use Enter to scroll through the log view and
3947          * split open each commit diff. */
3948         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3950         /* FIXME: A minor workaround. Scrolling the view will call report("")
3951          * but if we are scrolling a non-current view this won't properly
3952          * update the view title. */
3953         if (split)
3954                 update_view_title(view);
3956         return REQ_NONE;
3959 static bool
3960 pager_grep(struct view *view, struct line *line)
3962         const char *text[] = { line->data, NULL };
3964         return grep_text(view, text);
3967 static void
3968 pager_select(struct view *view, struct line *line)
3970         if (line->type == LINE_COMMIT) {
3971                 char *text = (char *)line->data + STRING_SIZE("commit ");
3973                 if (view != VIEW(REQ_VIEW_PAGER))
3974                         string_copy_rev(view->ref, text);
3975                 string_copy_rev(ref_commit, text);
3976         }
3979 static struct view_ops pager_ops = {
3980         "line",
3981         NULL,
3982         NULL,
3983         pager_read,
3984         pager_draw,
3985         pager_request,
3986         pager_grep,
3987         pager_select,
3988 };
3990 static const char *log_argv[SIZEOF_ARG] = {
3991         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3992 };
3994 static enum request
3995 log_request(struct view *view, enum request request, struct line *line)
3997         switch (request) {
3998         case REQ_REFRESH:
3999                 load_refs();
4000                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4001                 return REQ_NONE;
4002         default:
4003                 return pager_request(view, request, line);
4004         }
4007 static struct view_ops log_ops = {
4008         "line",
4009         log_argv,
4010         NULL,
4011         pager_read,
4012         pager_draw,
4013         log_request,
4014         pager_grep,
4015         pager_select,
4016 };
4018 static const char *diff_argv[SIZEOF_ARG] = {
4019         "git", "show", "--pretty=fuller", "--no-color", "--root",
4020                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4021 };
4023 static struct view_ops diff_ops = {
4024         "line",
4025         diff_argv,
4026         NULL,
4027         pager_read,
4028         pager_draw,
4029         pager_request,
4030         pager_grep,
4031         pager_select,
4032 };
4034 /*
4035  * Help backend
4036  */
4038 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4040 static bool
4041 help_open_keymap_title(struct view *view, enum keymap keymap)
4043         struct line *line;
4045         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4046                                help_keymap_hidden[keymap] ? '+' : '-',
4047                                enum_name(keymap_table[keymap]));
4048         if (line)
4049                 line->other = keymap;
4051         return help_keymap_hidden[keymap];
4054 static void
4055 help_open_keymap(struct view *view, enum keymap keymap)
4057         const char *group = NULL;
4058         char buf[SIZEOF_STR];
4059         size_t bufpos;
4060         bool add_title = TRUE;
4061         int i;
4063         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4064                 const char *key = NULL;
4066                 if (req_info[i].request == REQ_NONE)
4067                         continue;
4069                 if (!req_info[i].request) {
4070                         group = req_info[i].help;
4071                         continue;
4072                 }
4074                 key = get_keys(keymap, req_info[i].request, TRUE);
4075                 if (!key || !*key)
4076                         continue;
4078                 if (add_title && help_open_keymap_title(view, keymap))
4079                         return;
4080                 add_title = false;
4082                 if (group) {
4083                         add_line_text(view, group, LINE_HELP_GROUP);
4084                         group = NULL;
4085                 }
4087                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4088                                 enum_name(req_info[i]), req_info[i].help);
4089         }
4091         group = "External commands:";
4093         for (i = 0; i < run_requests; i++) {
4094                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4095                 const char *key;
4096                 int argc;
4098                 if (!req || req->keymap != keymap)
4099                         continue;
4101                 key = get_key_name(req->key);
4102                 if (!*key)
4103                         key = "(no key defined)";
4105                 if (add_title && help_open_keymap_title(view, keymap))
4106                         return;
4107                 if (group) {
4108                         add_line_text(view, group, LINE_HELP_GROUP);
4109                         group = NULL;
4110                 }
4112                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4113                         if (!string_format_from(buf, &bufpos, "%s%s",
4114                                                 argc ? " " : "", req->argv[argc]))
4115                                 return;
4117                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4118         }
4121 static bool
4122 help_open(struct view *view)
4124         enum keymap keymap;
4126         reset_view(view);
4127         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4128         add_line_text(view, "", LINE_DEFAULT);
4130         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4131                 help_open_keymap(view, keymap);
4133         return TRUE;
4136 static enum request
4137 help_request(struct view *view, enum request request, struct line *line)
4139         switch (request) {
4140         case REQ_ENTER:
4141                 if (line->type == LINE_HELP_KEYMAP) {
4142                         help_keymap_hidden[line->other] =
4143                                 !help_keymap_hidden[line->other];
4144                         view->p_restore = TRUE;
4145                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4146                 }
4148                 return REQ_NONE;
4149         default:
4150                 return pager_request(view, request, line);
4151         }
4154 static struct view_ops help_ops = {
4155         "line",
4156         NULL,
4157         help_open,
4158         NULL,
4159         pager_draw,
4160         help_request,
4161         pager_grep,
4162         pager_select,
4163 };
4166 /*
4167  * Tree backend
4168  */
4170 struct tree_stack_entry {
4171         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4172         unsigned long lineno;           /* Line number to restore */
4173         char *name;                     /* Position of name in opt_path */
4174 };
4176 /* The top of the path stack. */
4177 static struct tree_stack_entry *tree_stack = NULL;
4178 unsigned long tree_lineno = 0;
4180 static void
4181 pop_tree_stack_entry(void)
4183         struct tree_stack_entry *entry = tree_stack;
4185         tree_lineno = entry->lineno;
4186         entry->name[0] = 0;
4187         tree_stack = entry->prev;
4188         free(entry);
4191 static void
4192 push_tree_stack_entry(const char *name, unsigned long lineno)
4194         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4195         size_t pathlen = strlen(opt_path);
4197         if (!entry)
4198                 return;
4200         entry->prev = tree_stack;
4201         entry->name = opt_path + pathlen;
4202         tree_stack = entry;
4204         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4205                 pop_tree_stack_entry();
4206                 return;
4207         }
4209         /* Move the current line to the first tree entry. */
4210         tree_lineno = 1;
4211         entry->lineno = lineno;
4214 /* Parse output from git-ls-tree(1):
4215  *
4216  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4217  */
4219 #define SIZEOF_TREE_ATTR \
4220         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4222 #define SIZEOF_TREE_MODE \
4223         STRING_SIZE("100644 ")
4225 #define TREE_ID_OFFSET \
4226         STRING_SIZE("100644 blob ")
4228 struct tree_entry {
4229         char id[SIZEOF_REV];
4230         mode_t mode;
4231         time_t time;                    /* Date from the author ident. */
4232         const char *author;             /* Author of the commit. */
4233         char name[1];
4234 };
4236 static const char *
4237 tree_path(const struct line *line)
4239         return ((struct tree_entry *) line->data)->name;
4242 static int
4243 tree_compare_entry(const struct line *line1, const struct line *line2)
4245         if (line1->type != line2->type)
4246                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4247         return strcmp(tree_path(line1), tree_path(line2));
4250 static const enum sort_field tree_sort_fields[] = {
4251         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4252 };
4253 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4255 static int
4256 tree_compare(const void *l1, const void *l2)
4258         const struct line *line1 = (const struct line *) l1;
4259         const struct line *line2 = (const struct line *) l2;
4260         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4261         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4263         if (line1->type == LINE_TREE_HEAD)
4264                 return -1;
4265         if (line2->type == LINE_TREE_HEAD)
4266                 return 1;
4268         switch (get_sort_field(tree_sort_state)) {
4269         case ORDERBY_DATE:
4270                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4272         case ORDERBY_AUTHOR:
4273                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4275         case ORDERBY_NAME:
4276         default:
4277                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4278         }
4282 static struct line *
4283 tree_entry(struct view *view, enum line_type type, const char *path,
4284            const char *mode, const char *id)
4286         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4287         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4289         if (!entry || !line) {
4290                 free(entry);
4291                 return NULL;
4292         }
4294         strncpy(entry->name, path, strlen(path));
4295         if (mode)
4296                 entry->mode = strtoul(mode, NULL, 8);
4297         if (id)
4298                 string_copy_rev(entry->id, id);
4300         return line;
4303 static bool
4304 tree_read_date(struct view *view, char *text, bool *read_date)
4306         static const char *author_name;
4307         static time_t author_time;
4309         if (!text && *read_date) {
4310                 *read_date = FALSE;
4311                 return TRUE;
4313         } else if (!text) {
4314                 char *path = *opt_path ? opt_path : ".";
4315                 /* Find next entry to process */
4316                 const char *log_file[] = {
4317                         "git", "log", "--no-color", "--pretty=raw",
4318                                 "--cc", "--raw", view->id, "--", path, NULL
4319                 };
4320                 struct io io = {};
4322                 if (!view->lines) {
4323                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4324                         report("Tree is empty");
4325                         return TRUE;
4326                 }
4328                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4329                         report("Failed to load tree data");
4330                         return TRUE;
4331                 }
4333                 done_io(view->pipe);
4334                 view->io = io;
4335                 *read_date = TRUE;
4336                 return FALSE;
4338         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4339                 parse_author_line(text + STRING_SIZE("author "),
4340                                   &author_name, &author_time);
4342         } else if (*text == ':') {
4343                 char *pos;
4344                 size_t annotated = 1;
4345                 size_t i;
4347                 pos = strchr(text, '\t');
4348                 if (!pos)
4349                         return TRUE;
4350                 text = pos + 1;
4351                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4352                         text += strlen(opt_path);
4353                 pos = strchr(text, '/');
4354                 if (pos)
4355                         *pos = 0;
4357                 for (i = 1; i < view->lines; i++) {
4358                         struct line *line = &view->line[i];
4359                         struct tree_entry *entry = line->data;
4361                         annotated += !!entry->author;
4362                         if (entry->author || strcmp(entry->name, text))
4363                                 continue;
4365                         entry->author = author_name;
4366                         entry->time = author_time;
4367                         line->dirty = 1;
4368                         break;
4369                 }
4371                 if (annotated == view->lines)
4372                         kill_io(view->pipe);
4373         }
4374         return TRUE;
4377 static bool
4378 tree_read(struct view *view, char *text)
4380         static bool read_date = FALSE;
4381         struct tree_entry *data;
4382         struct line *entry, *line;
4383         enum line_type type;
4384         size_t textlen = text ? strlen(text) : 0;
4385         char *path = text + SIZEOF_TREE_ATTR;
4387         if (read_date || !text)
4388                 return tree_read_date(view, text, &read_date);
4390         if (textlen <= SIZEOF_TREE_ATTR)
4391                 return FALSE;
4392         if (view->lines == 0 &&
4393             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4394                 return FALSE;
4396         /* Strip the path part ... */
4397         if (*opt_path) {
4398                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4399                 size_t striplen = strlen(opt_path);
4401                 if (pathlen > striplen)
4402                         memmove(path, path + striplen,
4403                                 pathlen - striplen + 1);
4405                 /* Insert "link" to parent directory. */
4406                 if (view->lines == 1 &&
4407                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4408                         return FALSE;
4409         }
4411         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4412         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4413         if (!entry)
4414                 return FALSE;
4415         data = entry->data;
4417         /* Skip "Directory ..." and ".." line. */
4418         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4419                 if (tree_compare_entry(line, entry) <= 0)
4420                         continue;
4422                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4424                 line->data = data;
4425                 line->type = type;
4426                 for (; line <= entry; line++)
4427                         line->dirty = line->cleareol = 1;
4428                 return TRUE;
4429         }
4431         if (tree_lineno > view->lineno) {
4432                 view->lineno = tree_lineno;
4433                 tree_lineno = 0;
4434         }
4436         return TRUE;
4439 static bool
4440 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4442         struct tree_entry *entry = line->data;
4444         if (line->type == LINE_TREE_HEAD) {
4445                 if (draw_text(view, line->type, "Directory path /", TRUE))
4446                         return TRUE;
4447         } else {
4448                 if (draw_mode(view, entry->mode))
4449                         return TRUE;
4451                 if (opt_author && draw_author(view, entry->author))
4452                         return TRUE;
4454                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4455                         return TRUE;
4456         }
4457         if (draw_text(view, line->type, entry->name, TRUE))
4458                 return TRUE;
4459         return TRUE;
4462 static void
4463 open_blob_editor()
4465         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4466         int fd = mkstemp(file);
4468         if (fd == -1)
4469                 report("Failed to create temporary file");
4470         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4471                 report("Failed to save blob data to file");
4472         else
4473                 open_editor(FALSE, file);
4474         if (fd != -1)
4475                 unlink(file);
4478 static enum request
4479 tree_request(struct view *view, enum request request, struct line *line)
4481         enum open_flags flags;
4483         switch (request) {
4484         case REQ_VIEW_BLAME:
4485                 if (line->type != LINE_TREE_FILE) {
4486                         report("Blame only supported for files");
4487                         return REQ_NONE;
4488                 }
4490                 string_copy(opt_ref, view->vid);
4491                 return request;
4493         case REQ_EDIT:
4494                 if (line->type != LINE_TREE_FILE) {
4495                         report("Edit only supported for files");
4496                 } else if (!is_head_commit(view->vid)) {
4497                         open_blob_editor();
4498                 } else {
4499                         open_editor(TRUE, opt_file);
4500                 }
4501                 return REQ_NONE;
4503         case REQ_TOGGLE_SORT_FIELD:
4504         case REQ_TOGGLE_SORT_ORDER:
4505                 sort_view(view, request, &tree_sort_state, tree_compare);
4506                 return REQ_NONE;
4508         case REQ_PARENT:
4509                 if (!*opt_path) {
4510                         /* quit view if at top of tree */
4511                         return REQ_VIEW_CLOSE;
4512                 }
4513                 /* fake 'cd  ..' */
4514                 line = &view->line[1];
4515                 break;
4517         case REQ_ENTER:
4518                 break;
4520         default:
4521                 return request;
4522         }
4524         /* Cleanup the stack if the tree view is at a different tree. */
4525         while (!*opt_path && tree_stack)
4526                 pop_tree_stack_entry();
4528         switch (line->type) {
4529         case LINE_TREE_DIR:
4530                 /* Depending on whether it is a subdirectory or parent link
4531                  * mangle the path buffer. */
4532                 if (line == &view->line[1] && *opt_path) {
4533                         pop_tree_stack_entry();
4535                 } else {
4536                         const char *basename = tree_path(line);
4538                         push_tree_stack_entry(basename, view->lineno);
4539                 }
4541                 /* Trees and subtrees share the same ID, so they are not not
4542                  * unique like blobs. */
4543                 flags = OPEN_RELOAD;
4544                 request = REQ_VIEW_TREE;
4545                 break;
4547         case LINE_TREE_FILE:
4548                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4549                 request = REQ_VIEW_BLOB;
4550                 break;
4552         default:
4553                 return REQ_NONE;
4554         }
4556         open_view(view, request, flags);
4557         if (request == REQ_VIEW_TREE)
4558                 view->lineno = tree_lineno;
4560         return REQ_NONE;
4563 static bool
4564 tree_grep(struct view *view, struct line *line)
4566         struct tree_entry *entry = line->data;
4567         const char *text[] = {
4568                 entry->name,
4569                 opt_author ? entry->author : "",
4570                 opt_date ? mkdate(&entry->time) : "",
4571                 NULL
4572         };
4574         return grep_text(view, text);
4577 static void
4578 tree_select(struct view *view, struct line *line)
4580         struct tree_entry *entry = line->data;
4582         if (line->type == LINE_TREE_FILE) {
4583                 string_copy_rev(ref_blob, entry->id);
4584                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4586         } else if (line->type != LINE_TREE_DIR) {
4587                 return;
4588         }
4590         string_copy_rev(view->ref, entry->id);
4593 static bool
4594 tree_prepare(struct view *view)
4596         if (view->lines == 0 && opt_prefix[0]) {
4597                 char *pos = opt_prefix;
4599                 while (pos && *pos) {
4600                         char *end = strchr(pos, '/');
4602                         if (end)
4603                                 *end = 0;
4604                         push_tree_stack_entry(pos, 0);
4605                         pos = end;
4606                         if (end) {
4607                                 *end = '/';
4608                                 pos++;
4609                         }
4610                 }
4612         } else if (strcmp(view->vid, view->id)) {
4613                 opt_path[0] = 0;
4614         }
4616         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4619 static const char *tree_argv[SIZEOF_ARG] = {
4620         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4621 };
4623 static struct view_ops tree_ops = {
4624         "file",
4625         tree_argv,
4626         NULL,
4627         tree_read,
4628         tree_draw,
4629         tree_request,
4630         tree_grep,
4631         tree_select,
4632         tree_prepare,
4633 };
4635 static bool
4636 blob_read(struct view *view, char *line)
4638         if (!line)
4639                 return TRUE;
4640         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4643 static enum request
4644 blob_request(struct view *view, enum request request, struct line *line)
4646         switch (request) {
4647         case REQ_EDIT:
4648                 open_blob_editor();
4649                 return REQ_NONE;
4650         default:
4651                 return pager_request(view, request, line);
4652         }
4655 static const char *blob_argv[SIZEOF_ARG] = {
4656         "git", "cat-file", "blob", "%(blob)", NULL
4657 };
4659 static struct view_ops blob_ops = {
4660         "line",
4661         blob_argv,
4662         NULL,
4663         blob_read,
4664         pager_draw,
4665         blob_request,
4666         pager_grep,
4667         pager_select,
4668 };
4670 /*
4671  * Blame backend
4672  *
4673  * Loading the blame view is a two phase job:
4674  *
4675  *  1. File content is read either using opt_file from the
4676  *     filesystem or using git-cat-file.
4677  *  2. Then blame information is incrementally added by
4678  *     reading output from git-blame.
4679  */
4681 static const char *blame_head_argv[] = {
4682         "git", "blame", "--incremental", "--", "%(file)", NULL
4683 };
4685 static const char *blame_ref_argv[] = {
4686         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4687 };
4689 static const char *blame_cat_file_argv[] = {
4690         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4691 };
4693 struct blame_commit {
4694         char id[SIZEOF_REV];            /* SHA1 ID. */
4695         char title[128];                /* First line of the commit message. */
4696         const char *author;             /* Author of the commit. */
4697         time_t time;                    /* Date from the author ident. */
4698         char filename[128];             /* Name of file. */
4699         bool has_previous;              /* Was a "previous" line detected. */
4700 };
4702 struct blame {
4703         struct blame_commit *commit;
4704         unsigned long lineno;
4705         char text[1];
4706 };
4708 static bool
4709 blame_open(struct view *view)
4711         char path[SIZEOF_STR];
4713         if (!view->parent && *opt_prefix) {
4714                 string_copy(path, opt_file);
4715                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4716                         return FALSE;
4717         }
4719         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4720                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4721                         return FALSE;
4722         }
4724         setup_update(view, opt_file);
4725         string_format(view->ref, "%s ...", opt_file);
4727         return TRUE;
4730 static struct blame_commit *
4731 get_blame_commit(struct view *view, const char *id)
4733         size_t i;
4735         for (i = 0; i < view->lines; i++) {
4736                 struct blame *blame = view->line[i].data;
4738                 if (!blame->commit)
4739                         continue;
4741                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4742                         return blame->commit;
4743         }
4745         {
4746                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4748                 if (commit)
4749                         string_ncopy(commit->id, id, SIZEOF_REV);
4750                 return commit;
4751         }
4754 static bool
4755 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4757         const char *pos = *posref;
4759         *posref = NULL;
4760         pos = strchr(pos + 1, ' ');
4761         if (!pos || !isdigit(pos[1]))
4762                 return FALSE;
4763         *number = atoi(pos + 1);
4764         if (*number < min || *number > max)
4765                 return FALSE;
4767         *posref = pos;
4768         return TRUE;
4771 static struct blame_commit *
4772 parse_blame_commit(struct view *view, const char *text, int *blamed)
4774         struct blame_commit *commit;
4775         struct blame *blame;
4776         const char *pos = text + SIZEOF_REV - 2;
4777         size_t orig_lineno = 0;
4778         size_t lineno;
4779         size_t group;
4781         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4782                 return NULL;
4784         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4785             !parse_number(&pos, &lineno, 1, view->lines) ||
4786             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4787                 return NULL;
4789         commit = get_blame_commit(view, text);
4790         if (!commit)
4791                 return NULL;
4793         *blamed += group;
4794         while (group--) {
4795                 struct line *line = &view->line[lineno + group - 1];
4797                 blame = line->data;
4798                 blame->commit = commit;
4799                 blame->lineno = orig_lineno + group - 1;
4800                 line->dirty = 1;
4801         }
4803         return commit;
4806 static bool
4807 blame_read_file(struct view *view, const char *line, bool *read_file)
4809         if (!line) {
4810                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4811                 struct io io = {};
4813                 if (view->lines == 0 && !view->parent)
4814                         die("No blame exist for %s", view->vid);
4816                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4817                         report("Failed to load blame data");
4818                         return TRUE;
4819                 }
4821                 done_io(view->pipe);
4822                 view->io = io;
4823                 *read_file = FALSE;
4824                 return FALSE;
4826         } else {
4827                 size_t linelen = strlen(line);
4828                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4830                 if (!blame)
4831                         return FALSE;
4833                 blame->commit = NULL;
4834                 strncpy(blame->text, line, linelen);
4835                 blame->text[linelen] = 0;
4836                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4837         }
4840 static bool
4841 match_blame_header(const char *name, char **line)
4843         size_t namelen = strlen(name);
4844         bool matched = !strncmp(name, *line, namelen);
4846         if (matched)
4847                 *line += namelen;
4849         return matched;
4852 static bool
4853 blame_read(struct view *view, char *line)
4855         static struct blame_commit *commit = NULL;
4856         static int blamed = 0;
4857         static bool read_file = TRUE;
4859         if (read_file)
4860                 return blame_read_file(view, line, &read_file);
4862         if (!line) {
4863                 /* Reset all! */
4864                 commit = NULL;
4865                 blamed = 0;
4866                 read_file = TRUE;
4867                 string_format(view->ref, "%s", view->vid);
4868                 if (view_is_displayed(view)) {
4869                         update_view_title(view);
4870                         redraw_view_from(view, 0);
4871                 }
4872                 return TRUE;
4873         }
4875         if (!commit) {
4876                 commit = parse_blame_commit(view, line, &blamed);
4877                 string_format(view->ref, "%s %2d%%", view->vid,
4878                               view->lines ? blamed * 100 / view->lines : 0);
4880         } else if (match_blame_header("author ", &line)) {
4881                 commit->author = get_author(line);
4883         } else if (match_blame_header("author-time ", &line)) {
4884                 commit->time = (time_t) atol(line);
4886         } else if (match_blame_header("author-tz ", &line)) {
4887                 parse_timezone(&commit->time, line);
4889         } else if (match_blame_header("summary ", &line)) {
4890                 string_ncopy(commit->title, line, strlen(line));
4892         } else if (match_blame_header("previous ", &line)) {
4893                 commit->has_previous = TRUE;
4895         } else if (match_blame_header("filename ", &line)) {
4896                 string_ncopy(commit->filename, line, strlen(line));
4897                 commit = NULL;
4898         }
4900         return TRUE;
4903 static bool
4904 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4906         struct blame *blame = line->data;
4907         time_t *time = NULL;
4908         const char *id = NULL, *author = NULL;
4909         char text[SIZEOF_STR];
4911         if (blame->commit && *blame->commit->filename) {
4912                 id = blame->commit->id;
4913                 author = blame->commit->author;
4914                 time = &blame->commit->time;
4915         }
4917         if (opt_date && draw_date(view, time))
4918                 return TRUE;
4920         if (opt_author && draw_author(view, author))
4921                 return TRUE;
4923         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4924                 return TRUE;
4926         if (draw_lineno(view, lineno))
4927                 return TRUE;
4929         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4930         draw_text(view, LINE_DEFAULT, text, TRUE);
4931         return TRUE;
4934 static bool
4935 check_blame_commit(struct blame *blame, bool check_null_id)
4937         if (!blame->commit)
4938                 report("Commit data not loaded yet");
4939         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4940                 report("No commit exist for the selected line");
4941         else
4942                 return TRUE;
4943         return FALSE;
4946 static void
4947 setup_blame_parent_line(struct view *view, struct blame *blame)
4949         const char *diff_tree_argv[] = {
4950                 "git", "diff-tree", "-U0", blame->commit->id,
4951                         "--", blame->commit->filename, NULL
4952         };
4953         struct io io = {};
4954         int parent_lineno = -1;
4955         int blamed_lineno = -1;
4956         char *line;
4958         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4959                 return;
4961         while ((line = io_get(&io, '\n', TRUE))) {
4962                 if (*line == '@') {
4963                         char *pos = strchr(line, '+');
4965                         parent_lineno = atoi(line + 4);
4966                         if (pos)
4967                                 blamed_lineno = atoi(pos + 1);
4969                 } else if (*line == '+' && parent_lineno != -1) {
4970                         if (blame->lineno == blamed_lineno - 1 &&
4971                             !strcmp(blame->text, line + 1)) {
4972                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4973                                 break;
4974                         }
4975                         blamed_lineno++;
4976                 }
4977         }
4979         done_io(&io);
4982 static enum request
4983 blame_request(struct view *view, enum request request, struct line *line)
4985         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4986         struct blame *blame = line->data;
4988         switch (request) {
4989         case REQ_VIEW_BLAME:
4990                 if (check_blame_commit(blame, TRUE)) {
4991                         string_copy(opt_ref, blame->commit->id);
4992                         string_copy(opt_file, blame->commit->filename);
4993                         if (blame->lineno)
4994                                 view->lineno = blame->lineno;
4995                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4996                 }
4997                 break;
4999         case REQ_PARENT:
5000                 if (check_blame_commit(blame, TRUE) &&
5001                     select_commit_parent(blame->commit->id, opt_ref,
5002                                          blame->commit->filename)) {
5003                         string_copy(opt_file, blame->commit->filename);
5004                         setup_blame_parent_line(view, blame);
5005                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5006                 }
5007                 break;
5009         case REQ_ENTER:
5010                 if (!check_blame_commit(blame, FALSE))
5011                         break;
5013                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5014                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5015                         break;
5017                 if (!strcmp(blame->commit->id, NULL_ID)) {
5018                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5019                         const char *diff_index_argv[] = {
5020                                 "git", "diff-index", "--root", "--patch-with-stat",
5021                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5022                         };
5024                         if (!blame->commit->has_previous) {
5025                                 diff_index_argv[1] = "diff";
5026                                 diff_index_argv[2] = "--no-color";
5027                                 diff_index_argv[6] = "--";
5028                                 diff_index_argv[7] = "/dev/null";
5029                         }
5031                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5032                                 report("Failed to allocate diff command");
5033                                 break;
5034                         }
5035                         flags |= OPEN_PREPARED;
5036                 }
5038                 open_view(view, REQ_VIEW_DIFF, flags);
5039                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5040                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5041                 break;
5043         default:
5044                 return request;
5045         }
5047         return REQ_NONE;
5050 static bool
5051 blame_grep(struct view *view, struct line *line)
5053         struct blame *blame = line->data;
5054         struct blame_commit *commit = blame->commit;
5055         const char *text[] = {
5056                 blame->text,
5057                 commit ? commit->title : "",
5058                 commit ? commit->id : "",
5059                 commit && opt_author ? commit->author : "",
5060                 commit && opt_date ? mkdate(&commit->time) : "",
5061                 NULL
5062         };
5064         return grep_text(view, text);
5067 static void
5068 blame_select(struct view *view, struct line *line)
5070         struct blame *blame = line->data;
5071         struct blame_commit *commit = blame->commit;
5073         if (!commit)
5074                 return;
5076         if (!strcmp(commit->id, NULL_ID))
5077                 string_ncopy(ref_commit, "HEAD", 4);
5078         else
5079                 string_copy_rev(ref_commit, commit->id);
5082 static struct view_ops blame_ops = {
5083         "line",
5084         NULL,
5085         blame_open,
5086         blame_read,
5087         blame_draw,
5088         blame_request,
5089         blame_grep,
5090         blame_select,
5091 };
5093 /*
5094  * Branch backend
5095  */
5097 struct branch {
5098         const char *author;             /* Author of the last commit. */
5099         time_t time;                    /* Date of the last activity. */
5100         const struct ref *ref;          /* Name and commit ID information. */
5101 };
5103 static const struct ref branch_all;
5105 static const enum sort_field branch_sort_fields[] = {
5106         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5107 };
5108 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5110 static int
5111 branch_compare(const void *l1, const void *l2)
5113         const struct branch *branch1 = ((const struct line *) l1)->data;
5114         const struct branch *branch2 = ((const struct line *) l2)->data;
5116         switch (get_sort_field(branch_sort_state)) {
5117         case ORDERBY_DATE:
5118                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5120         case ORDERBY_AUTHOR:
5121                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5123         case ORDERBY_NAME:
5124         default:
5125                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5126         }
5129 static bool
5130 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5132         struct branch *branch = line->data;
5133         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5135         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5136                 return TRUE;
5138         if (opt_author && draw_author(view, branch->author))
5139                 return TRUE;
5141         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5142         return TRUE;
5145 static enum request
5146 branch_request(struct view *view, enum request request, struct line *line)
5148         struct branch *branch = line->data;
5150         switch (request) {
5151         case REQ_REFRESH:
5152                 load_refs();
5153                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5154                 return REQ_NONE;
5156         case REQ_TOGGLE_SORT_FIELD:
5157         case REQ_TOGGLE_SORT_ORDER:
5158                 sort_view(view, request, &branch_sort_state, branch_compare);
5159                 return REQ_NONE;
5161         case REQ_ENTER:
5162                 if (branch->ref == &branch_all) {
5163                         const char *all_branches_argv[] = {
5164                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5165                                       "--topo-order", "--all", NULL
5166                         };
5167                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5169                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5170                                 report("Failed to load view of all branches");
5171                                 return REQ_NONE;
5172                         }
5173                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5174                 } else {
5175                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5176                 }
5177                 return REQ_NONE;
5179         default:
5180                 return request;
5181         }
5184 static bool
5185 branch_read(struct view *view, char *line)
5187         static char id[SIZEOF_REV];
5188         struct branch *reference;
5189         size_t i;
5191         if (!line)
5192                 return TRUE;
5194         switch (get_line_type(line)) {
5195         case LINE_COMMIT:
5196                 string_copy_rev(id, line + STRING_SIZE("commit "));
5197                 return TRUE;
5199         case LINE_AUTHOR:
5200                 for (i = 0, reference = NULL; i < view->lines; i++) {
5201                         struct branch *branch = view->line[i].data;
5203                         if (strcmp(branch->ref->id, id))
5204                                 continue;
5206                         view->line[i].dirty = TRUE;
5207                         if (reference) {
5208                                 branch->author = reference->author;
5209                                 branch->time = reference->time;
5210                                 continue;
5211                         }
5213                         parse_author_line(line + STRING_SIZE("author "),
5214                                           &branch->author, &branch->time);
5215                         reference = branch;
5216                 }
5217                 return TRUE;
5219         default:
5220                 return TRUE;
5221         }
5225 static bool
5226 branch_open_visitor(void *data, const struct ref *ref)
5228         struct view *view = data;
5229         struct branch *branch;
5231         if (ref->tag || ref->ltag || ref->remote)
5232                 return TRUE;
5234         branch = calloc(1, sizeof(*branch));
5235         if (!branch)
5236                 return FALSE;
5238         branch->ref = ref;
5239         return !!add_line_data(view, branch, LINE_DEFAULT);
5242 static bool
5243 branch_open(struct view *view)
5245         const char *branch_log[] = {
5246                 "git", "log", "--no-color", "--pretty=raw",
5247                         "--simplify-by-decoration", "--all", NULL
5248         };
5250         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5251                 report("Failed to load branch data");
5252                 return TRUE;
5253         }
5255         setup_update(view, view->id);
5256         branch_open_visitor(view, &branch_all);
5257         foreach_ref(branch_open_visitor, view);
5258         view->p_restore = TRUE;
5260         return TRUE;
5263 static bool
5264 branch_grep(struct view *view, struct line *line)
5266         struct branch *branch = line->data;
5267         const char *text[] = {
5268                 branch->ref->name,
5269                 branch->author,
5270                 NULL
5271         };
5273         return grep_text(view, text);
5276 static void
5277 branch_select(struct view *view, struct line *line)
5279         struct branch *branch = line->data;
5281         string_copy_rev(view->ref, branch->ref->id);
5282         string_copy_rev(ref_commit, branch->ref->id);
5283         string_copy_rev(ref_head, branch->ref->id);
5286 static struct view_ops branch_ops = {
5287         "branch",
5288         NULL,
5289         branch_open,
5290         branch_read,
5291         branch_draw,
5292         branch_request,
5293         branch_grep,
5294         branch_select,
5295 };
5297 /*
5298  * Status backend
5299  */
5301 struct status {
5302         char status;
5303         struct {
5304                 mode_t mode;
5305                 char rev[SIZEOF_REV];
5306                 char name[SIZEOF_STR];
5307         } old;
5308         struct {
5309                 mode_t mode;
5310                 char rev[SIZEOF_REV];
5311                 char name[SIZEOF_STR];
5312         } new;
5313 };
5315 static char status_onbranch[SIZEOF_STR];
5316 static struct status stage_status;
5317 static enum line_type stage_line_type;
5318 static size_t stage_chunks;
5319 static int *stage_chunk;
5321 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5323 /* This should work even for the "On branch" line. */
5324 static inline bool
5325 status_has_none(struct view *view, struct line *line)
5327         return line < view->line + view->lines && !line[1].data;
5330 /* Get fields from the diff line:
5331  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5332  */
5333 static inline bool
5334 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5336         const char *old_mode = buf +  1;
5337         const char *new_mode = buf +  8;
5338         const char *old_rev  = buf + 15;
5339         const char *new_rev  = buf + 56;
5340         const char *status   = buf + 97;
5342         if (bufsize < 98 ||
5343             old_mode[-1] != ':' ||
5344             new_mode[-1] != ' ' ||
5345             old_rev[-1]  != ' ' ||
5346             new_rev[-1]  != ' ' ||
5347             status[-1]   != ' ')
5348                 return FALSE;
5350         file->status = *status;
5352         string_copy_rev(file->old.rev, old_rev);
5353         string_copy_rev(file->new.rev, new_rev);
5355         file->old.mode = strtoul(old_mode, NULL, 8);
5356         file->new.mode = strtoul(new_mode, NULL, 8);
5358         file->old.name[0] = file->new.name[0] = 0;
5360         return TRUE;
5363 static bool
5364 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5366         struct status *unmerged = NULL;
5367         char *buf;
5368         struct io io = {};
5370         if (!run_io(&io, argv, opt_cdup, IO_RD))
5371                 return FALSE;
5373         add_line_data(view, NULL, type);
5375         while ((buf = io_get(&io, 0, TRUE))) {
5376                 struct status *file = unmerged;
5378                 if (!file) {
5379                         file = calloc(1, sizeof(*file));
5380                         if (!file || !add_line_data(view, file, type))
5381                                 goto error_out;
5382                 }
5384                 /* Parse diff info part. */
5385                 if (status) {
5386                         file->status = status;
5387                         if (status == 'A')
5388                                 string_copy(file->old.rev, NULL_ID);
5390                 } else if (!file->status || file == unmerged) {
5391                         if (!status_get_diff(file, buf, strlen(buf)))
5392                                 goto error_out;
5394                         buf = io_get(&io, 0, TRUE);
5395                         if (!buf)
5396                                 break;
5398                         /* Collapse all modified entries that follow an
5399                          * associated unmerged entry. */
5400                         if (unmerged == file) {
5401                                 unmerged->status = 'U';
5402                                 unmerged = NULL;
5403                         } else if (file->status == 'U') {
5404                                 unmerged = file;
5405                         }
5406                 }
5408                 /* Grab the old name for rename/copy. */
5409                 if (!*file->old.name &&
5410                     (file->status == 'R' || file->status == 'C')) {
5411                         string_ncopy(file->old.name, buf, strlen(buf));
5413                         buf = io_get(&io, 0, TRUE);
5414                         if (!buf)
5415                                 break;
5416                 }
5418                 /* git-ls-files just delivers a NUL separated list of
5419                  * file names similar to the second half of the
5420                  * git-diff-* output. */
5421                 string_ncopy(file->new.name, buf, strlen(buf));
5422                 if (!*file->old.name)
5423                         string_copy(file->old.name, file->new.name);
5424                 file = NULL;
5425         }
5427         if (io_error(&io)) {
5428 error_out:
5429                 done_io(&io);
5430                 return FALSE;
5431         }
5433         if (!view->line[view->lines - 1].data)
5434                 add_line_data(view, NULL, LINE_STAT_NONE);
5436         done_io(&io);
5437         return TRUE;
5440 /* Don't show unmerged entries in the staged section. */
5441 static const char *status_diff_index_argv[] = {
5442         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5443                              "--cached", "-M", "HEAD", NULL
5444 };
5446 static const char *status_diff_files_argv[] = {
5447         "git", "diff-files", "-z", NULL
5448 };
5450 static const char *status_list_other_argv[] = {
5451         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5452 };
5454 static const char *status_list_no_head_argv[] = {
5455         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5456 };
5458 static const char *update_index_argv[] = {
5459         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5460 };
5462 /* Restore the previous line number to stay in the context or select a
5463  * line with something that can be updated. */
5464 static void
5465 status_restore(struct view *view)
5467         if (view->p_lineno >= view->lines)
5468                 view->p_lineno = view->lines - 1;
5469         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5470                 view->p_lineno++;
5471         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5472                 view->p_lineno--;
5474         /* If the above fails, always skip the "On branch" line. */
5475         if (view->p_lineno < view->lines)
5476                 view->lineno = view->p_lineno;
5477         else
5478                 view->lineno = 1;
5480         if (view->lineno < view->offset)
5481                 view->offset = view->lineno;
5482         else if (view->offset + view->height <= view->lineno)
5483                 view->offset = view->lineno - view->height + 1;
5485         view->p_restore = FALSE;
5488 static void
5489 status_update_onbranch(void)
5491         static const char *paths[][2] = {
5492                 { "rebase-apply/rebasing",      "Rebasing" },
5493                 { "rebase-apply/applying",      "Applying mailbox" },
5494                 { "rebase-apply/",              "Rebasing mailbox" },
5495                 { "rebase-merge/interactive",   "Interactive rebase" },
5496                 { "rebase-merge/",              "Rebase merge" },
5497                 { "MERGE_HEAD",                 "Merging" },
5498                 { "BISECT_LOG",                 "Bisecting" },
5499                 { "HEAD",                       "On branch" },
5500         };
5501         char buf[SIZEOF_STR];
5502         struct stat stat;
5503         int i;
5505         if (is_initial_commit()) {
5506                 string_copy(status_onbranch, "Initial commit");
5507                 return;
5508         }
5510         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5511                 char *head = opt_head;
5513                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5514                     lstat(buf, &stat) < 0)
5515                         continue;
5517                 if (!*opt_head) {
5518                         struct io io = {};
5520                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5521                             io_read_buf(&io, buf, sizeof(buf))) {
5522                                 head = buf;
5523                                 if (!prefixcmp(head, "refs/heads/"))
5524                                         head += STRING_SIZE("refs/heads/");
5525                         }
5526                 }
5528                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5529                         string_copy(status_onbranch, opt_head);
5530                 return;
5531         }
5533         string_copy(status_onbranch, "Not currently on any branch");
5536 /* First parse staged info using git-diff-index(1), then parse unstaged
5537  * info using git-diff-files(1), and finally untracked files using
5538  * git-ls-files(1). */
5539 static bool
5540 status_open(struct view *view)
5542         reset_view(view);
5544         add_line_data(view, NULL, LINE_STAT_HEAD);
5545         status_update_onbranch();
5547         run_io_bg(update_index_argv);
5549         if (is_initial_commit()) {
5550                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5551                         return FALSE;
5552         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5553                 return FALSE;
5554         }
5556         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5557             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5558                 return FALSE;
5560         /* Restore the exact position or use the specialized restore
5561          * mode? */
5562         if (!view->p_restore)
5563                 status_restore(view);
5564         return TRUE;
5567 static bool
5568 status_draw(struct view *view, struct line *line, unsigned int lineno)
5570         struct status *status = line->data;
5571         enum line_type type;
5572         const char *text;
5574         if (!status) {
5575                 switch (line->type) {
5576                 case LINE_STAT_STAGED:
5577                         type = LINE_STAT_SECTION;
5578                         text = "Changes to be committed:";
5579                         break;
5581                 case LINE_STAT_UNSTAGED:
5582                         type = LINE_STAT_SECTION;
5583                         text = "Changed but not updated:";
5584                         break;
5586                 case LINE_STAT_UNTRACKED:
5587                         type = LINE_STAT_SECTION;
5588                         text = "Untracked files:";
5589                         break;
5591                 case LINE_STAT_NONE:
5592                         type = LINE_DEFAULT;
5593                         text = "  (no files)";
5594                         break;
5596                 case LINE_STAT_HEAD:
5597                         type = LINE_STAT_HEAD;
5598                         text = status_onbranch;
5599                         break;
5601                 default:
5602                         return FALSE;
5603                 }
5604         } else {
5605                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5607                 buf[0] = status->status;
5608                 if (draw_text(view, line->type, buf, TRUE))
5609                         return TRUE;
5610                 type = LINE_DEFAULT;
5611                 text = status->new.name;
5612         }
5614         draw_text(view, type, text, TRUE);
5615         return TRUE;
5618 static enum request
5619 status_load_error(struct view *view, struct view *stage, const char *path)
5621         if (displayed_views() == 2 || display[current_view] != view)
5622                 maximize_view(view);
5623         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5624         return REQ_NONE;
5627 static enum request
5628 status_enter(struct view *view, struct line *line)
5630         struct status *status = line->data;
5631         const char *oldpath = status ? status->old.name : NULL;
5632         /* Diffs for unmerged entries are empty when passing the new
5633          * path, so leave it empty. */
5634         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5635         const char *info;
5636         enum open_flags split;
5637         struct view *stage = VIEW(REQ_VIEW_STAGE);
5639         if (line->type == LINE_STAT_NONE ||
5640             (!status && line[1].type == LINE_STAT_NONE)) {
5641                 report("No file to diff");
5642                 return REQ_NONE;
5643         }
5645         switch (line->type) {
5646         case LINE_STAT_STAGED:
5647                 if (is_initial_commit()) {
5648                         const char *no_head_diff_argv[] = {
5649                                 "git", "diff", "--no-color", "--patch-with-stat",
5650                                         "--", "/dev/null", newpath, NULL
5651                         };
5653                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5654                                 return status_load_error(view, stage, newpath);
5655                 } else {
5656                         const char *index_show_argv[] = {
5657                                 "git", "diff-index", "--root", "--patch-with-stat",
5658                                         "-C", "-M", "--cached", "HEAD", "--",
5659                                         oldpath, newpath, NULL
5660                         };
5662                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5663                                 return status_load_error(view, stage, newpath);
5664                 }
5666                 if (status)
5667                         info = "Staged changes to %s";
5668                 else
5669                         info = "Staged changes";
5670                 break;
5672         case LINE_STAT_UNSTAGED:
5673         {
5674                 const char *files_show_argv[] = {
5675                         "git", "diff-files", "--root", "--patch-with-stat",
5676                                 "-C", "-M", "--", oldpath, newpath, NULL
5677                 };
5679                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5680                         return status_load_error(view, stage, newpath);
5681                 if (status)
5682                         info = "Unstaged changes to %s";
5683                 else
5684                         info = "Unstaged changes";
5685                 break;
5686         }
5687         case LINE_STAT_UNTRACKED:
5688                 if (!newpath) {
5689                         report("No file to show");
5690                         return REQ_NONE;
5691                 }
5693                 if (!suffixcmp(status->new.name, -1, "/")) {
5694                         report("Cannot display a directory");
5695                         return REQ_NONE;
5696                 }
5698                 if (!prepare_update_file(stage, newpath))
5699                         return status_load_error(view, stage, newpath);
5700                 info = "Untracked file %s";
5701                 break;
5703         case LINE_STAT_HEAD:
5704                 return REQ_NONE;
5706         default:
5707                 die("line type %d not handled in switch", line->type);
5708         }
5710         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5711         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5712         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5713                 if (status) {
5714                         stage_status = *status;
5715                 } else {
5716                         memset(&stage_status, 0, sizeof(stage_status));
5717                 }
5719                 stage_line_type = line->type;
5720                 stage_chunks = 0;
5721                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5722         }
5724         return REQ_NONE;
5727 static bool
5728 status_exists(struct status *status, enum line_type type)
5730         struct view *view = VIEW(REQ_VIEW_STATUS);
5731         unsigned long lineno;
5733         for (lineno = 0; lineno < view->lines; lineno++) {
5734                 struct line *line = &view->line[lineno];
5735                 struct status *pos = line->data;
5737                 if (line->type != type)
5738                         continue;
5739                 if (!pos && (!status || !status->status) && line[1].data) {
5740                         select_view_line(view, lineno);
5741                         return TRUE;
5742                 }
5743                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5744                         select_view_line(view, lineno);
5745                         return TRUE;
5746                 }
5747         }
5749         return FALSE;
5753 static bool
5754 status_update_prepare(struct io *io, enum line_type type)
5756         const char *staged_argv[] = {
5757                 "git", "update-index", "-z", "--index-info", NULL
5758         };
5759         const char *others_argv[] = {
5760                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5761         };
5763         switch (type) {
5764         case LINE_STAT_STAGED:
5765                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5767         case LINE_STAT_UNSTAGED:
5768         case LINE_STAT_UNTRACKED:
5769                 return run_io(io, others_argv, opt_cdup, IO_WR);
5771         default:
5772                 die("line type %d not handled in switch", type);
5773                 return FALSE;
5774         }
5777 static bool
5778 status_update_write(struct io *io, struct status *status, enum line_type type)
5780         char buf[SIZEOF_STR];
5781         size_t bufsize = 0;
5783         switch (type) {
5784         case LINE_STAT_STAGED:
5785                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5786                                         status->old.mode,
5787                                         status->old.rev,
5788                                         status->old.name, 0))
5789                         return FALSE;
5790                 break;
5792         case LINE_STAT_UNSTAGED:
5793         case LINE_STAT_UNTRACKED:
5794                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5795                         return FALSE;
5796                 break;
5798         default:
5799                 die("line type %d not handled in switch", type);
5800         }
5802         return io_write(io, buf, bufsize);
5805 static bool
5806 status_update_file(struct status *status, enum line_type type)
5808         struct io io = {};
5809         bool result;
5811         if (!status_update_prepare(&io, type))
5812                 return FALSE;
5814         result = status_update_write(&io, status, type);
5815         return done_io(&io) && result;
5818 static bool
5819 status_update_files(struct view *view, struct line *line)
5821         char buf[sizeof(view->ref)];
5822         struct io io = {};
5823         bool result = TRUE;
5824         struct line *pos = view->line + view->lines;
5825         int files = 0;
5826         int file, done;
5827         int cursor_y = -1, cursor_x = -1;
5829         if (!status_update_prepare(&io, line->type))
5830                 return FALSE;
5832         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5833                 files++;
5835         string_copy(buf, view->ref);
5836         getsyx(cursor_y, cursor_x);
5837         for (file = 0, done = 5; result && file < files; line++, file++) {
5838                 int almost_done = file * 100 / files;
5840                 if (almost_done > done) {
5841                         done = almost_done;
5842                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5843                                       file, files, done);
5844                         update_view_title(view);
5845                         setsyx(cursor_y, cursor_x);
5846                         doupdate();
5847                 }
5848                 result = status_update_write(&io, line->data, line->type);
5849         }
5850         string_copy(view->ref, buf);
5852         return done_io(&io) && result;
5855 static bool
5856 status_update(struct view *view)
5858         struct line *line = &view->line[view->lineno];
5860         assert(view->lines);
5862         if (!line->data) {
5863                 /* This should work even for the "On branch" line. */
5864                 if (line < view->line + view->lines && !line[1].data) {
5865                         report("Nothing to update");
5866                         return FALSE;
5867                 }
5869                 if (!status_update_files(view, line + 1)) {
5870                         report("Failed to update file status");
5871                         return FALSE;
5872                 }
5874         } else if (!status_update_file(line->data, line->type)) {
5875                 report("Failed to update file status");
5876                 return FALSE;
5877         }
5879         return TRUE;
5882 static bool
5883 status_revert(struct status *status, enum line_type type, bool has_none)
5885         if (!status || type != LINE_STAT_UNSTAGED) {
5886                 if (type == LINE_STAT_STAGED) {
5887                         report("Cannot revert changes to staged files");
5888                 } else if (type == LINE_STAT_UNTRACKED) {
5889                         report("Cannot revert changes to untracked files");
5890                 } else if (has_none) {
5891                         report("Nothing to revert");
5892                 } else {
5893                         report("Cannot revert changes to multiple files");
5894                 }
5896         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5897                 char mode[10] = "100644";
5898                 const char *reset_argv[] = {
5899                         "git", "update-index", "--cacheinfo", mode,
5900                                 status->old.rev, status->old.name, NULL
5901                 };
5902                 const char *checkout_argv[] = {
5903                         "git", "checkout", "--", status->old.name, NULL
5904                 };
5906                 if (status->status == 'U') {
5907                         string_format(mode, "%5o", status->old.mode);
5909                         if (status->old.mode == 0 && status->new.mode == 0) {
5910                                 reset_argv[2] = "--force-remove";
5911                                 reset_argv[3] = status->old.name;
5912                                 reset_argv[4] = NULL;
5913                         }
5915                         if (!run_io_fg(reset_argv, opt_cdup))
5916                                 return FALSE;
5917                         if (status->old.mode == 0 && status->new.mode == 0)
5918                                 return TRUE;
5919                 }
5921                 return run_io_fg(checkout_argv, opt_cdup);
5922         }
5924         return FALSE;
5927 static enum request
5928 status_request(struct view *view, enum request request, struct line *line)
5930         struct status *status = line->data;
5932         switch (request) {
5933         case REQ_STATUS_UPDATE:
5934                 if (!status_update(view))
5935                         return REQ_NONE;
5936                 break;
5938         case REQ_STATUS_REVERT:
5939                 if (!status_revert(status, line->type, status_has_none(view, line)))
5940                         return REQ_NONE;
5941                 break;
5943         case REQ_STATUS_MERGE:
5944                 if (!status || status->status != 'U') {
5945                         report("Merging only possible for files with unmerged status ('U').");
5946                         return REQ_NONE;
5947                 }
5948                 open_mergetool(status->new.name);
5949                 break;
5951         case REQ_EDIT:
5952                 if (!status)
5953                         return request;
5954                 if (status->status == 'D') {
5955                         report("File has been deleted.");
5956                         return REQ_NONE;
5957                 }
5959                 open_editor(status->status != '?', status->new.name);
5960                 break;
5962         case REQ_VIEW_BLAME:
5963                 if (status)
5964                         opt_ref[0] = 0;
5965                 return request;
5967         case REQ_ENTER:
5968                 /* After returning the status view has been split to
5969                  * show the stage view. No further reloading is
5970                  * necessary. */
5971                 return status_enter(view, line);
5973         case REQ_REFRESH:
5974                 /* Simply reload the view. */
5975                 break;
5977         default:
5978                 return request;
5979         }
5981         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5983         return REQ_NONE;
5986 static void
5987 status_select(struct view *view, struct line *line)
5989         struct status *status = line->data;
5990         char file[SIZEOF_STR] = "all files";
5991         const char *text;
5992         const char *key;
5994         if (status && !string_format(file, "'%s'", status->new.name))
5995                 return;
5997         if (!status && line[1].type == LINE_STAT_NONE)
5998                 line++;
6000         switch (line->type) {
6001         case LINE_STAT_STAGED:
6002                 text = "Press %s to unstage %s for commit";
6003                 break;
6005         case LINE_STAT_UNSTAGED:
6006                 text = "Press %s to stage %s for commit";
6007                 break;
6009         case LINE_STAT_UNTRACKED:
6010                 text = "Press %s to stage %s for addition";
6011                 break;
6013         case LINE_STAT_HEAD:
6014         case LINE_STAT_NONE:
6015                 text = "Nothing to update";
6016                 break;
6018         default:
6019                 die("line type %d not handled in switch", line->type);
6020         }
6022         if (status && status->status == 'U') {
6023                 text = "Press %s to resolve conflict in %s";
6024                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6026         } else {
6027                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6028         }
6030         string_format(view->ref, text, key, file);
6031         if (status)
6032                 string_copy(opt_file, status->new.name);
6035 static bool
6036 status_grep(struct view *view, struct line *line)
6038         struct status *status = line->data;
6040         if (status) {
6041                 const char buf[2] = { status->status, 0 };
6042                 const char *text[] = { status->new.name, buf, NULL };
6044                 return grep_text(view, text);
6045         }
6047         return FALSE;
6050 static struct view_ops status_ops = {
6051         "file",
6052         NULL,
6053         status_open,
6054         NULL,
6055         status_draw,
6056         status_request,
6057         status_grep,
6058         status_select,
6059 };
6062 static bool
6063 stage_diff_write(struct io *io, struct line *line, struct line *end)
6065         while (line < end) {
6066                 if (!io_write(io, line->data, strlen(line->data)) ||
6067                     !io_write(io, "\n", 1))
6068                         return FALSE;
6069                 line++;
6070                 if (line->type == LINE_DIFF_CHUNK ||
6071                     line->type == LINE_DIFF_HEADER)
6072                         break;
6073         }
6075         return TRUE;
6078 static struct line *
6079 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6081         for (; view->line < line; line--)
6082                 if (line->type == type)
6083                         return line;
6085         return NULL;
6088 static bool
6089 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6091         const char *apply_argv[SIZEOF_ARG] = {
6092                 "git", "apply", "--whitespace=nowarn", NULL
6093         };
6094         struct line *diff_hdr;
6095         struct io io = {};
6096         int argc = 3;
6098         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6099         if (!diff_hdr)
6100                 return FALSE;
6102         if (!revert)
6103                 apply_argv[argc++] = "--cached";
6104         if (revert || stage_line_type == LINE_STAT_STAGED)
6105                 apply_argv[argc++] = "-R";
6106         apply_argv[argc++] = "-";
6107         apply_argv[argc++] = NULL;
6108         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6109                 return FALSE;
6111         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6112             !stage_diff_write(&io, chunk, view->line + view->lines))
6113                 chunk = NULL;
6115         done_io(&io);
6116         run_io_bg(update_index_argv);
6118         return chunk ? TRUE : FALSE;
6121 static bool
6122 stage_update(struct view *view, struct line *line)
6124         struct line *chunk = NULL;
6126         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6127                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6129         if (chunk) {
6130                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6131                         report("Failed to apply chunk");
6132                         return FALSE;
6133                 }
6135         } else if (!stage_status.status) {
6136                 view = VIEW(REQ_VIEW_STATUS);
6138                 for (line = view->line; line < view->line + view->lines; line++)
6139                         if (line->type == stage_line_type)
6140                                 break;
6142                 if (!status_update_files(view, line + 1)) {
6143                         report("Failed to update files");
6144                         return FALSE;
6145                 }
6147         } else if (!status_update_file(&stage_status, stage_line_type)) {
6148                 report("Failed to update file");
6149                 return FALSE;
6150         }
6152         return TRUE;
6155 static bool
6156 stage_revert(struct view *view, struct line *line)
6158         struct line *chunk = NULL;
6160         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6161                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6163         if (chunk) {
6164                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6165                         return FALSE;
6167                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6168                         report("Failed to revert chunk");
6169                         return FALSE;
6170                 }
6171                 return TRUE;
6173         } else {
6174                 return status_revert(stage_status.status ? &stage_status : NULL,
6175                                      stage_line_type, FALSE);
6176         }
6180 static void
6181 stage_next(struct view *view, struct line *line)
6183         int i;
6185         if (!stage_chunks) {
6186                 for (line = view->line; line < view->line + view->lines; line++) {
6187                         if (line->type != LINE_DIFF_CHUNK)
6188                                 continue;
6190                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6191                                 report("Allocation failure");
6192                                 return;
6193                         }
6195                         stage_chunk[stage_chunks++] = line - view->line;
6196                 }
6197         }
6199         for (i = 0; i < stage_chunks; i++) {
6200                 if (stage_chunk[i] > view->lineno) {
6201                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6202                         report("Chunk %d of %d", i + 1, stage_chunks);
6203                         return;
6204                 }
6205         }
6207         report("No next chunk found");
6210 static enum request
6211 stage_request(struct view *view, enum request request, struct line *line)
6213         switch (request) {
6214         case REQ_STATUS_UPDATE:
6215                 if (!stage_update(view, line))
6216                         return REQ_NONE;
6217                 break;
6219         case REQ_STATUS_REVERT:
6220                 if (!stage_revert(view, line))
6221                         return REQ_NONE;
6222                 break;
6224         case REQ_STAGE_NEXT:
6225                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6226                         report("File is untracked; press %s to add",
6227                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6228                         return REQ_NONE;
6229                 }
6230                 stage_next(view, line);
6231                 return REQ_NONE;
6233         case REQ_EDIT:
6234                 if (!stage_status.new.name[0])
6235                         return request;
6236                 if (stage_status.status == 'D') {
6237                         report("File has been deleted.");
6238                         return REQ_NONE;
6239                 }
6241                 open_editor(stage_status.status != '?', stage_status.new.name);
6242                 break;
6244         case REQ_REFRESH:
6245                 /* Reload everything ... */
6246                 break;
6248         case REQ_VIEW_BLAME:
6249                 if (stage_status.new.name[0]) {
6250                         string_copy(opt_file, stage_status.new.name);
6251                         opt_ref[0] = 0;
6252                 }
6253                 return request;
6255         case REQ_ENTER:
6256                 return pager_request(view, request, line);
6258         default:
6259                 return request;
6260         }
6262         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6263         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6265         /* Check whether the staged entry still exists, and close the
6266          * stage view if it doesn't. */
6267         if (!status_exists(&stage_status, stage_line_type)) {
6268                 status_restore(VIEW(REQ_VIEW_STATUS));
6269                 return REQ_VIEW_CLOSE;
6270         }
6272         if (stage_line_type == LINE_STAT_UNTRACKED) {
6273                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6274                         report("Cannot display a directory");
6275                         return REQ_NONE;
6276                 }
6278                 if (!prepare_update_file(view, stage_status.new.name)) {
6279                         report("Failed to open file: %s", strerror(errno));
6280                         return REQ_NONE;
6281                 }
6282         }
6283         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6285         return REQ_NONE;
6288 static struct view_ops stage_ops = {
6289         "line",
6290         NULL,
6291         NULL,
6292         pager_read,
6293         pager_draw,
6294         stage_request,
6295         pager_grep,
6296         pager_select,
6297 };
6300 /*
6301  * Revision graph
6302  */
6304 struct commit {
6305         char id[SIZEOF_REV];            /* SHA1 ID. */
6306         char title[128];                /* First line of the commit message. */
6307         const char *author;             /* Author of the commit. */
6308         time_t time;                    /* Date from the author ident. */
6309         struct ref_list *refs;          /* Repository references. */
6310         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6311         size_t graph_size;              /* The width of the graph array. */
6312         bool has_parents;               /* Rewritten --parents seen. */
6313 };
6315 /* Size of rev graph with no  "padding" columns */
6316 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6318 struct rev_graph {
6319         struct rev_graph *prev, *next, *parents;
6320         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6321         size_t size;
6322         struct commit *commit;
6323         size_t pos;
6324         unsigned int boundary:1;
6325 };
6327 /* Parents of the commit being visualized. */
6328 static struct rev_graph graph_parents[4];
6330 /* The current stack of revisions on the graph. */
6331 static struct rev_graph graph_stacks[4] = {
6332         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6333         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6334         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6335         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6336 };
6338 static inline bool
6339 graph_parent_is_merge(struct rev_graph *graph)
6341         return graph->parents->size > 1;
6344 static inline void
6345 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6347         struct commit *commit = graph->commit;
6349         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6350                 commit->graph[commit->graph_size++] = symbol;
6353 static void
6354 clear_rev_graph(struct rev_graph *graph)
6356         graph->boundary = 0;
6357         graph->size = graph->pos = 0;
6358         graph->commit = NULL;
6359         memset(graph->parents, 0, sizeof(*graph->parents));
6362 static void
6363 done_rev_graph(struct rev_graph *graph)
6365         if (graph_parent_is_merge(graph) &&
6366             graph->pos < graph->size - 1 &&
6367             graph->next->size == graph->size + graph->parents->size - 1) {
6368                 size_t i = graph->pos + graph->parents->size - 1;
6370                 graph->commit->graph_size = i * 2;
6371                 while (i < graph->next->size - 1) {
6372                         append_to_rev_graph(graph, ' ');
6373                         append_to_rev_graph(graph, '\\');
6374                         i++;
6375                 }
6376         }
6378         clear_rev_graph(graph);
6381 static void
6382 push_rev_graph(struct rev_graph *graph, const char *parent)
6384         int i;
6386         /* "Collapse" duplicate parents lines.
6387          *
6388          * FIXME: This needs to also update update the drawn graph but
6389          * for now it just serves as a method for pruning graph lines. */
6390         for (i = 0; i < graph->size; i++)
6391                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6392                         return;
6394         if (graph->size < SIZEOF_REVITEMS) {
6395                 string_copy_rev(graph->rev[graph->size++], parent);
6396         }
6399 static chtype
6400 get_rev_graph_symbol(struct rev_graph *graph)
6402         chtype symbol;
6404         if (graph->boundary)
6405                 symbol = REVGRAPH_BOUND;
6406         else if (graph->parents->size == 0)
6407                 symbol = REVGRAPH_INIT;
6408         else if (graph_parent_is_merge(graph))
6409                 symbol = REVGRAPH_MERGE;
6410         else if (graph->pos >= graph->size)
6411                 symbol = REVGRAPH_BRANCH;
6412         else
6413                 symbol = REVGRAPH_COMMIT;
6415         return symbol;
6418 static void
6419 draw_rev_graph(struct rev_graph *graph)
6421         struct rev_filler {
6422                 chtype separator, line;
6423         };
6424         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6425         static struct rev_filler fillers[] = {
6426                 { ' ',  '|' },
6427                 { '`',  '.' },
6428                 { '\'', ' ' },
6429                 { '/',  ' ' },
6430         };
6431         chtype symbol = get_rev_graph_symbol(graph);
6432         struct rev_filler *filler;
6433         size_t i;
6435         if (opt_line_graphics)
6436                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6438         filler = &fillers[DEFAULT];
6440         for (i = 0; i < graph->pos; i++) {
6441                 append_to_rev_graph(graph, filler->line);
6442                 if (graph_parent_is_merge(graph->prev) &&
6443                     graph->prev->pos == i)
6444                         filler = &fillers[RSHARP];
6446                 append_to_rev_graph(graph, filler->separator);
6447         }
6449         /* Place the symbol for this revision. */
6450         append_to_rev_graph(graph, symbol);
6452         if (graph->prev->size > graph->size)
6453                 filler = &fillers[RDIAG];
6454         else
6455                 filler = &fillers[DEFAULT];
6457         i++;
6459         for (; i < graph->size; i++) {
6460                 append_to_rev_graph(graph, filler->separator);
6461                 append_to_rev_graph(graph, filler->line);
6462                 if (graph_parent_is_merge(graph->prev) &&
6463                     i < graph->prev->pos + graph->parents->size)
6464                         filler = &fillers[RSHARP];
6465                 if (graph->prev->size > graph->size)
6466                         filler = &fillers[LDIAG];
6467         }
6469         if (graph->prev->size > graph->size) {
6470                 append_to_rev_graph(graph, filler->separator);
6471                 if (filler->line != ' ')
6472                         append_to_rev_graph(graph, filler->line);
6473         }
6476 /* Prepare the next rev graph */
6477 static void
6478 prepare_rev_graph(struct rev_graph *graph)
6480         size_t i;
6482         /* First, traverse all lines of revisions up to the active one. */
6483         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6484                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6485                         break;
6487                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6488         }
6490         /* Interleave the new revision parent(s). */
6491         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6492                 push_rev_graph(graph->next, graph->parents->rev[i]);
6494         /* Lastly, put any remaining revisions. */
6495         for (i = graph->pos + 1; i < graph->size; i++)
6496                 push_rev_graph(graph->next, graph->rev[i]);
6499 static void
6500 update_rev_graph(struct view *view, struct rev_graph *graph)
6502         /* If this is the finalizing update ... */
6503         if (graph->commit)
6504                 prepare_rev_graph(graph);
6506         /* Graph visualization needs a one rev look-ahead,
6507          * so the first update doesn't visualize anything. */
6508         if (!graph->prev->commit)
6509                 return;
6511         if (view->lines > 2)
6512                 view->line[view->lines - 3].dirty = 1;
6513         if (view->lines > 1)
6514                 view->line[view->lines - 2].dirty = 1;
6515         draw_rev_graph(graph->prev);
6516         done_rev_graph(graph->prev->prev);
6520 /*
6521  * Main view backend
6522  */
6524 static const char *main_argv[SIZEOF_ARG] = {
6525         "git", "log", "--no-color", "--pretty=raw", "--parents",
6526                       "--topo-order", "%(head)", NULL
6527 };
6529 static bool
6530 main_draw(struct view *view, struct line *line, unsigned int lineno)
6532         struct commit *commit = line->data;
6534         if (!commit->author)
6535                 return FALSE;
6537         if (opt_date && draw_date(view, &commit->time))
6538                 return TRUE;
6540         if (opt_author && draw_author(view, commit->author))
6541                 return TRUE;
6543         if (opt_rev_graph && commit->graph_size &&
6544             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6545                 return TRUE;
6547         if (opt_show_refs && commit->refs) {
6548                 size_t i;
6550                 for (i = 0; i < commit->refs->size; i++) {
6551                         struct ref *ref = commit->refs->refs[i];
6552                         enum line_type type;
6554                         if (ref->head)
6555                                 type = LINE_MAIN_HEAD;
6556                         else if (ref->ltag)
6557                                 type = LINE_MAIN_LOCAL_TAG;
6558                         else if (ref->tag)
6559                                 type = LINE_MAIN_TAG;
6560                         else if (ref->tracked)
6561                                 type = LINE_MAIN_TRACKED;
6562                         else if (ref->remote)
6563                                 type = LINE_MAIN_REMOTE;
6564                         else
6565                                 type = LINE_MAIN_REF;
6567                         if (draw_text(view, type, "[", TRUE) ||
6568                             draw_text(view, type, ref->name, TRUE) ||
6569                             draw_text(view, type, "]", TRUE))
6570                                 return TRUE;
6572                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6573                                 return TRUE;
6574                 }
6575         }
6577         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6578         return TRUE;
6581 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6582 static bool
6583 main_read(struct view *view, char *line)
6585         static struct rev_graph *graph = graph_stacks;
6586         enum line_type type;
6587         struct commit *commit;
6589         if (!line) {
6590                 int i;
6592                 if (!view->lines && !view->parent)
6593                         die("No revisions match the given arguments.");
6594                 if (view->lines > 0) {
6595                         commit = view->line[view->lines - 1].data;
6596                         view->line[view->lines - 1].dirty = 1;
6597                         if (!commit->author) {
6598                                 view->lines--;
6599                                 free(commit);
6600                                 graph->commit = NULL;
6601                         }
6602                 }
6603                 update_rev_graph(view, graph);
6605                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6606                         clear_rev_graph(&graph_stacks[i]);
6607                 return TRUE;
6608         }
6610         type = get_line_type(line);
6611         if (type == LINE_COMMIT) {
6612                 commit = calloc(1, sizeof(struct commit));
6613                 if (!commit)
6614                         return FALSE;
6616                 line += STRING_SIZE("commit ");
6617                 if (*line == '-') {
6618                         graph->boundary = 1;
6619                         line++;
6620                 }
6622                 string_copy_rev(commit->id, line);
6623                 commit->refs = get_ref_list(commit->id);
6624                 graph->commit = commit;
6625                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6627                 while ((line = strchr(line, ' '))) {
6628                         line++;
6629                         push_rev_graph(graph->parents, line);
6630                         commit->has_parents = TRUE;
6631                 }
6632                 return TRUE;
6633         }
6635         if (!view->lines)
6636                 return TRUE;
6637         commit = view->line[view->lines - 1].data;
6639         switch (type) {
6640         case LINE_PARENT:
6641                 if (commit->has_parents)
6642                         break;
6643                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6644                 break;
6646         case LINE_AUTHOR:
6647                 parse_author_line(line + STRING_SIZE("author "),
6648                                   &commit->author, &commit->time);
6649                 update_rev_graph(view, graph);
6650                 graph = graph->next;
6651                 break;
6653         default:
6654                 /* Fill in the commit title if it has not already been set. */
6655                 if (commit->title[0])
6656                         break;
6658                 /* Require titles to start with a non-space character at the
6659                  * offset used by git log. */
6660                 if (strncmp(line, "    ", 4))
6661                         break;
6662                 line += 4;
6663                 /* Well, if the title starts with a whitespace character,
6664                  * try to be forgiving.  Otherwise we end up with no title. */
6665                 while (isspace(*line))
6666                         line++;
6667                 if (*line == '\0')
6668                         break;
6669                 /* FIXME: More graceful handling of titles; append "..." to
6670                  * shortened titles, etc. */
6672                 string_expand(commit->title, sizeof(commit->title), line, 1);
6673                 view->line[view->lines - 1].dirty = 1;
6674         }
6676         return TRUE;
6679 static enum request
6680 main_request(struct view *view, enum request request, struct line *line)
6682         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6684         switch (request) {
6685         case REQ_ENTER:
6686                 open_view(view, REQ_VIEW_DIFF, flags);
6687                 break;
6688         case REQ_REFRESH:
6689                 load_refs();
6690                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6691                 break;
6692         default:
6693                 return request;
6694         }
6696         return REQ_NONE;
6699 static bool
6700 grep_refs(struct ref_list *list, regex_t *regex)
6702         regmatch_t pmatch;
6703         size_t i;
6705         if (!opt_show_refs || !list)
6706                 return FALSE;
6708         for (i = 0; i < list->size; i++) {
6709                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6710                         return TRUE;
6711         }
6713         return FALSE;
6716 static bool
6717 main_grep(struct view *view, struct line *line)
6719         struct commit *commit = line->data;
6720         const char *text[] = {
6721                 commit->title,
6722                 opt_author ? commit->author : "",
6723                 opt_date ? mkdate(&commit->time) : "",
6724                 NULL
6725         };
6727         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6730 static void
6731 main_select(struct view *view, struct line *line)
6733         struct commit *commit = line->data;
6735         string_copy_rev(view->ref, commit->id);
6736         string_copy_rev(ref_commit, view->ref);
6739 static struct view_ops main_ops = {
6740         "commit",
6741         main_argv,
6742         NULL,
6743         main_read,
6744         main_draw,
6745         main_request,
6746         main_grep,
6747         main_select,
6748 };
6751 /*
6752  * Unicode / UTF-8 handling
6753  *
6754  * NOTE: Much of the following code for dealing with Unicode is derived from
6755  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6756  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6757  */
6759 static inline int
6760 unicode_width(unsigned long c)
6762         if (c >= 0x1100 &&
6763            (c <= 0x115f                         /* Hangul Jamo */
6764             || c == 0x2329
6765             || c == 0x232a
6766             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6767                                                 /* CJK ... Yi */
6768             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6769             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6770             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6771             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6772             || (c >= 0xffe0  && c <= 0xffe6)
6773             || (c >= 0x20000 && c <= 0x2fffd)
6774             || (c >= 0x30000 && c <= 0x3fffd)))
6775                 return 2;
6777         if (c == '\t')
6778                 return opt_tab_size;
6780         return 1;
6783 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6784  * Illegal bytes are set one. */
6785 static const unsigned char utf8_bytes[256] = {
6786         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6787         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6788         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6789         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6790         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6791         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6792         2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
6793         3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
6794 };
6796 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6797 static inline unsigned long
6798 utf8_to_unicode(const char *string, size_t length)
6800         unsigned long unicode;
6802         switch (length) {
6803         case 1:
6804                 unicode  =   string[0];
6805                 break;
6806         case 2:
6807                 unicode  =  (string[0] & 0x1f) << 6;
6808                 unicode +=  (string[1] & 0x3f);
6809                 break;
6810         case 3:
6811                 unicode  =  (string[0] & 0x0f) << 12;
6812                 unicode += ((string[1] & 0x3f) << 6);
6813                 unicode +=  (string[2] & 0x3f);
6814                 break;
6815         case 4:
6816                 unicode  =  (string[0] & 0x0f) << 18;
6817                 unicode += ((string[1] & 0x3f) << 12);
6818                 unicode += ((string[2] & 0x3f) << 6);
6819                 unicode +=  (string[3] & 0x3f);
6820                 break;
6821         case 5:
6822                 unicode  =  (string[0] & 0x0f) << 24;
6823                 unicode += ((string[1] & 0x3f) << 18);
6824                 unicode += ((string[2] & 0x3f) << 12);
6825                 unicode += ((string[3] & 0x3f) << 6);
6826                 unicode +=  (string[4] & 0x3f);
6827                 break;
6828         case 6:
6829                 unicode  =  (string[0] & 0x01) << 30;
6830                 unicode += ((string[1] & 0x3f) << 24);
6831                 unicode += ((string[2] & 0x3f) << 18);
6832                 unicode += ((string[3] & 0x3f) << 12);
6833                 unicode += ((string[4] & 0x3f) << 6);
6834                 unicode +=  (string[5] & 0x3f);
6835                 break;
6836         default:
6837                 die("Invalid Unicode length");
6838         }
6840         /* Invalid characters could return the special 0xfffd value but NUL
6841          * should be just as good. */
6842         return unicode > 0xffff ? 0 : unicode;
6845 /* Calculates how much of string can be shown within the given maximum width
6846  * and sets trimmed parameter to non-zero value if all of string could not be
6847  * shown. If the reserve flag is TRUE, it will reserve at least one
6848  * trailing character, which can be useful when drawing a delimiter.
6849  *
6850  * Returns the number of bytes to output from string to satisfy max_width. */
6851 static size_t
6852 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6854         const char *string = *start;
6855         const char *end = strchr(string, '\0');
6856         unsigned char last_bytes = 0;
6857         size_t last_ucwidth = 0;
6859         *width = 0;
6860         *trimmed = 0;
6862         while (string < end) {
6863                 int c = *(unsigned char *) string;
6864                 unsigned char bytes = utf8_bytes[c];
6865                 size_t ucwidth;
6866                 unsigned long unicode;
6868                 if (string + bytes > end)
6869                         break;
6871                 /* Change representation to figure out whether
6872                  * it is a single- or double-width character. */
6874                 unicode = utf8_to_unicode(string, bytes);
6875                 /* FIXME: Graceful handling of invalid Unicode character. */
6876                 if (!unicode)
6877                         break;
6879                 ucwidth = unicode_width(unicode);
6880                 if (skip > 0) {
6881                         skip -= ucwidth <= skip ? ucwidth : skip;
6882                         *start += bytes;
6883                 }
6884                 *width  += ucwidth;
6885                 if (*width > max_width) {
6886                         *trimmed = 1;
6887                         *width -= ucwidth;
6888                         if (reserve && *width == max_width) {
6889                                 string -= last_bytes;
6890                                 *width -= last_ucwidth;
6891                         }
6892                         break;
6893                 }
6895                 string  += bytes;
6896                 last_bytes = ucwidth ? bytes : 0;
6897                 last_ucwidth = ucwidth;
6898         }
6900         return string - *start;
6904 /*
6905  * Status management
6906  */
6908 /* Whether or not the curses interface has been initialized. */
6909 static bool cursed = FALSE;
6911 /* Terminal hacks and workarounds. */
6912 static bool use_scroll_redrawwin;
6913 static bool use_scroll_status_wclear;
6915 /* The status window is used for polling keystrokes. */
6916 static WINDOW *status_win;
6918 /* Reading from the prompt? */
6919 static bool input_mode = FALSE;
6921 static bool status_empty = FALSE;
6923 /* Update status and title window. */
6924 static void
6925 report(const char *msg, ...)
6927         struct view *view = display[current_view];
6929         if (input_mode)
6930                 return;
6932         if (!view) {
6933                 char buf[SIZEOF_STR];
6934                 va_list args;
6936                 va_start(args, msg);
6937                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6938                         buf[sizeof(buf) - 1] = 0;
6939                         buf[sizeof(buf) - 2] = '.';
6940                         buf[sizeof(buf) - 3] = '.';
6941                         buf[sizeof(buf) - 4] = '.';
6942                 }
6943                 va_end(args);
6944                 die("%s", buf);
6945         }
6947         if (!status_empty || *msg) {
6948                 va_list args;
6950                 va_start(args, msg);
6952                 wmove(status_win, 0, 0);
6953                 if (view->has_scrolled && use_scroll_status_wclear)
6954                         wclear(status_win);
6955                 if (*msg) {
6956                         vwprintw(status_win, msg, args);
6957                         status_empty = FALSE;
6958                 } else {
6959                         status_empty = TRUE;
6960                 }
6961                 wclrtoeol(status_win);
6962                 wnoutrefresh(status_win);
6964                 va_end(args);
6965         }
6967         update_view_title(view);
6970 /* Controls when nodelay should be in effect when polling user input. */
6971 static void
6972 set_nonblocking_input(bool loading)
6974         static unsigned int loading_views;
6976         if ((loading == FALSE && loading_views-- == 1) ||
6977             (loading == TRUE  && loading_views++ == 0))
6978                 nodelay(status_win, loading);
6981 static void
6982 init_display(void)
6984         const char *term;
6985         int x, y;
6987         /* Initialize the curses library */
6988         if (isatty(STDIN_FILENO)) {
6989                 cursed = !!initscr();
6990                 opt_tty = stdin;
6991         } else {
6992                 /* Leave stdin and stdout alone when acting as a pager. */
6993                 opt_tty = fopen("/dev/tty", "r+");
6994                 if (!opt_tty)
6995                         die("Failed to open /dev/tty");
6996                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6997         }
6999         if (!cursed)
7000                 die("Failed to initialize curses");
7002         nonl();         /* Disable conversion and detect newlines from input. */
7003         cbreak();       /* Take input chars one at a time, no wait for \n */
7004         noecho();       /* Don't echo input */
7005         leaveok(stdscr, FALSE);
7007         if (has_colors())
7008                 init_colors();
7010         getmaxyx(stdscr, y, x);
7011         status_win = newwin(1, 0, y - 1, 0);
7012         if (!status_win)
7013                 die("Failed to create status window");
7015         /* Enable keyboard mapping */
7016         keypad(status_win, TRUE);
7017         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7019         TABSIZE = opt_tab_size;
7020         if (opt_line_graphics) {
7021                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
7022         }
7024         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7025         if (term && !strcmp(term, "gnome-terminal")) {
7026                 /* In the gnome-terminal-emulator, the message from
7027                  * scrolling up one line when impossible followed by
7028                  * scrolling down one line causes corruption of the
7029                  * status line. This is fixed by calling wclear. */
7030                 use_scroll_status_wclear = TRUE;
7031                 use_scroll_redrawwin = FALSE;
7033         } else if (term && !strcmp(term, "xrvt-xpm")) {
7034                 /* No problems with full optimizations in xrvt-(unicode)
7035                  * and aterm. */
7036                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7038         } else {
7039                 /* When scrolling in (u)xterm the last line in the
7040                  * scrolling direction will update slowly. */
7041                 use_scroll_redrawwin = TRUE;
7042                 use_scroll_status_wclear = FALSE;
7043         }
7046 static int
7047 get_input(int prompt_position)
7049         struct view *view;
7050         int i, key, cursor_y, cursor_x;
7052         if (prompt_position)
7053                 input_mode = TRUE;
7055         while (TRUE) {
7056                 foreach_view (view, i) {
7057                         update_view(view);
7058                         if (view_is_displayed(view) && view->has_scrolled &&
7059                             use_scroll_redrawwin)
7060                                 redrawwin(view->win);
7061                         view->has_scrolled = FALSE;
7062                 }
7064                 /* Update the cursor position. */
7065                 if (prompt_position) {
7066                         getbegyx(status_win, cursor_y, cursor_x);
7067                         cursor_x = prompt_position;
7068                 } else {
7069                         view = display[current_view];
7070                         getbegyx(view->win, cursor_y, cursor_x);
7071                         cursor_x = view->width - 1;
7072                         cursor_y += view->lineno - view->offset;
7073                 }
7074                 setsyx(cursor_y, cursor_x);
7076                 /* Refresh, accept single keystroke of input */
7077                 doupdate();
7078                 key = wgetch(status_win);
7080                 /* wgetch() with nodelay() enabled returns ERR when
7081                  * there's no input. */
7082                 if (key == ERR) {
7084                 } else if (key == KEY_RESIZE) {
7085                         int height, width;
7087                         getmaxyx(stdscr, height, width);
7089                         wresize(status_win, 1, width);
7090                         mvwin(status_win, height - 1, 0);
7091                         wnoutrefresh(status_win);
7092                         resize_display();
7093                         redraw_display(TRUE);
7095                 } else {
7096                         input_mode = FALSE;
7097                         return key;
7098                 }
7099         }
7102 static char *
7103 prompt_input(const char *prompt, input_handler handler, void *data)
7105         enum input_status status = INPUT_OK;
7106         static char buf[SIZEOF_STR];
7107         size_t pos = 0;
7109         buf[pos] = 0;
7111         while (status == INPUT_OK || status == INPUT_SKIP) {
7112                 int key;
7114                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7115                 wclrtoeol(status_win);
7117                 key = get_input(pos + 1);
7118                 switch (key) {
7119                 case KEY_RETURN:
7120                 case KEY_ENTER:
7121                 case '\n':
7122                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7123                         break;
7125                 case KEY_BACKSPACE:
7126                         if (pos > 0)
7127                                 buf[--pos] = 0;
7128                         else
7129                                 status = INPUT_CANCEL;
7130                         break;
7132                 case KEY_ESC:
7133                         status = INPUT_CANCEL;
7134                         break;
7136                 default:
7137                         if (pos >= sizeof(buf)) {
7138                                 report("Input string too long");
7139                                 return NULL;
7140                         }
7142                         status = handler(data, buf, key);
7143                         if (status == INPUT_OK)
7144                                 buf[pos++] = (char) key;
7145                 }
7146         }
7148         /* Clear the status window */
7149         status_empty = FALSE;
7150         report("");
7152         if (status == INPUT_CANCEL)
7153                 return NULL;
7155         buf[pos++] = 0;
7157         return buf;
7160 static enum input_status
7161 prompt_yesno_handler(void *data, char *buf, int c)
7163         if (c == 'y' || c == 'Y')
7164                 return INPUT_STOP;
7165         if (c == 'n' || c == 'N')
7166                 return INPUT_CANCEL;
7167         return INPUT_SKIP;
7170 static bool
7171 prompt_yesno(const char *prompt)
7173         char prompt2[SIZEOF_STR];
7175         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7176                 return FALSE;
7178         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7181 static enum input_status
7182 read_prompt_handler(void *data, char *buf, int c)
7184         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7187 static char *
7188 read_prompt(const char *prompt)
7190         return prompt_input(prompt, read_prompt_handler, NULL);
7193 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7195         enum input_status status = INPUT_OK;
7196         int size = 0;
7198         while (items[size].text)
7199                 size++;
7201         while (status == INPUT_OK) {
7202                 const struct menu_item *item = &items[*selected];
7203                 int key;
7204                 int i;
7206                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7207                           prompt, *selected + 1, size);
7208                 if (item->hotkey)
7209                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7210                 wprintw(status_win, "%s", item->text);
7211                 wclrtoeol(status_win);
7213                 key = get_input(COLS - 1);
7214                 switch (key) {
7215                 case KEY_RETURN:
7216                 case KEY_ENTER:
7217                 case '\n':
7218                         status = INPUT_STOP;
7219                         break;
7221                 case KEY_LEFT:
7222                 case KEY_UP:
7223                         *selected = *selected - 1;
7224                         if (*selected < 0)
7225                                 *selected = size - 1;
7226                         break;
7228                 case KEY_RIGHT:
7229                 case KEY_DOWN:
7230                         *selected = (*selected + 1) % size;
7231                         break;
7233                 case KEY_ESC:
7234                         status = INPUT_CANCEL;
7235                         break;
7237                 default:
7238                         for (i = 0; items[i].text; i++)
7239                                 if (items[i].hotkey == key) {
7240                                         *selected = i;
7241                                         status = INPUT_STOP;
7242                                         break;
7243                                 }
7244                 }
7245         }
7247         /* Clear the status window */
7248         status_empty = FALSE;
7249         report("");
7251         return status != INPUT_CANCEL;
7254 /*
7255  * Repository properties
7256  */
7258 static struct ref **refs = NULL;
7259 static size_t refs_size = 0;
7261 static struct ref_list **ref_lists = NULL;
7262 static size_t ref_lists_size = 0;
7264 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7265 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7266 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7268 static int
7269 compare_refs(const void *ref1_, const void *ref2_)
7271         const struct ref *ref1 = *(const struct ref **)ref1_;
7272         const struct ref *ref2 = *(const struct ref **)ref2_;
7274         if (ref1->tag != ref2->tag)
7275                 return ref2->tag - ref1->tag;
7276         if (ref1->ltag != ref2->ltag)
7277                 return ref2->ltag - ref2->ltag;
7278         if (ref1->head != ref2->head)
7279                 return ref2->head - ref1->head;
7280         if (ref1->tracked != ref2->tracked)
7281                 return ref2->tracked - ref1->tracked;
7282         if (ref1->remote != ref2->remote)
7283                 return ref2->remote - ref1->remote;
7284         return strcmp(ref1->name, ref2->name);
7287 static void
7288 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7290         size_t i;
7292         for (i = 0; i < refs_size; i++)
7293                 if (!visitor(data, refs[i]))
7294                         break;
7297 static struct ref_list *
7298 get_ref_list(const char *id)
7300         struct ref_list *list;
7301         size_t i;
7303         for (i = 0; i < ref_lists_size; i++)
7304                 if (!strcmp(id, ref_lists[i]->id))
7305                         return ref_lists[i];
7307         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7308                 return NULL;
7309         list = calloc(1, sizeof(*list));
7310         if (!list)
7311                 return NULL;
7313         for (i = 0; i < refs_size; i++) {
7314                 if (!strcmp(id, refs[i]->id) &&
7315                     realloc_refs_list(&list->refs, list->size, 1))
7316                         list->refs[list->size++] = refs[i];
7317         }
7319         if (!list->refs) {
7320                 free(list);
7321                 return NULL;
7322         }
7324         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7325         ref_lists[ref_lists_size++] = list;
7326         return list;
7329 static int
7330 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7332         struct ref *ref = NULL;
7333         bool tag = FALSE;
7334         bool ltag = FALSE;
7335         bool remote = FALSE;
7336         bool tracked = FALSE;
7337         bool head = FALSE;
7338         int from = 0, to = refs_size - 1;
7340         if (!prefixcmp(name, "refs/tags/")) {
7341                 if (!suffixcmp(name, namelen, "^{}")) {
7342                         namelen -= 3;
7343                         name[namelen] = 0;
7344                 } else {
7345                         ltag = TRUE;
7346                 }
7348                 tag = TRUE;
7349                 namelen -= STRING_SIZE("refs/tags/");
7350                 name    += STRING_SIZE("refs/tags/");
7352         } else if (!prefixcmp(name, "refs/remotes/")) {
7353                 remote = TRUE;
7354                 namelen -= STRING_SIZE("refs/remotes/");
7355                 name    += STRING_SIZE("refs/remotes/");
7356                 tracked  = !strcmp(opt_remote, name);
7358         } else if (!prefixcmp(name, "refs/heads/")) {
7359                 namelen -= STRING_SIZE("refs/heads/");
7360                 name    += STRING_SIZE("refs/heads/");
7361                 head     = !strncmp(opt_head, name, namelen);
7363         } else if (!strcmp(name, "HEAD")) {
7364                 string_ncopy(opt_head_rev, id, idlen);
7365                 return OK;
7366         }
7368         /* If we are reloading or it's an annotated tag, replace the
7369          * previous SHA1 with the resolved commit id; relies on the fact
7370          * git-ls-remote lists the commit id of an annotated tag right
7371          * before the commit id it points to. */
7372         while (from <= to) {
7373                 size_t pos = (to + from) / 2;
7374                 int cmp = strcmp(name, refs[pos]->name);
7376                 if (!cmp) {
7377                         ref = refs[pos];
7378                         break;
7379                 }
7381                 if (cmp < 0)
7382                         to = pos - 1;
7383                 else
7384                         from = pos + 1;
7385         }
7387         if (!ref) {
7388                 if (!realloc_refs(&refs, refs_size, 1))
7389                         return ERR;
7390                 ref = calloc(1, sizeof(*ref) + namelen);
7391                 if (!ref)
7392                         return ERR;
7393                 memmove(refs + from + 1, refs + from,
7394                         (refs_size - from) * sizeof(*refs));
7395                 refs[from] = ref;
7396                 strncpy(ref->name, name, namelen);
7397                 refs_size++;
7398         }
7400         ref->head = head;
7401         ref->tag = tag;
7402         ref->ltag = ltag;
7403         ref->remote = remote;
7404         ref->tracked = tracked;
7405         string_copy_rev(ref->id, id);
7407         return OK;
7410 static int
7411 load_refs(void)
7413         const char *head_argv[] = {
7414                 "git", "symbolic-ref", "HEAD", NULL
7415         };
7416         static const char *ls_remote_argv[SIZEOF_ARG] = {
7417                 "git", "ls-remote", opt_git_dir, NULL
7418         };
7419         static bool init = FALSE;
7420         size_t i;
7422         if (!init) {
7423                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7424                 init = TRUE;
7425         }
7427         if (!*opt_git_dir)
7428                 return OK;
7430         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7431             !prefixcmp(opt_head, "refs/heads/")) {
7432                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7434                 memmove(opt_head, offset, strlen(offset) + 1);
7435         }
7437         for (i = 0; i < refs_size; i++)
7438                 refs[i]->id[0] = 0;
7440         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7441                 return ERR;
7443         /* Update the ref lists to reflect changes. */
7444         for (i = 0; i < ref_lists_size; i++) {
7445                 struct ref_list *list = ref_lists[i];
7446                 size_t old, new;
7448                 for (old = new = 0; old < list->size; old++)
7449                         if (!strcmp(list->id, list->refs[old]->id))
7450                                 list->refs[new++] = list->refs[old];
7451                 list->size = new;
7452         }
7454         return OK;
7457 static void
7458 set_remote_branch(const char *name, const char *value, size_t valuelen)
7460         if (!strcmp(name, ".remote")) {
7461                 string_ncopy(opt_remote, value, valuelen);
7463         } else if (*opt_remote && !strcmp(name, ".merge")) {
7464                 size_t from = strlen(opt_remote);
7466                 if (!prefixcmp(value, "refs/heads/"))
7467                         value += STRING_SIZE("refs/heads/");
7469                 if (!string_format_from(opt_remote, &from, "/%s", value))
7470                         opt_remote[0] = 0;
7471         }
7474 static void
7475 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7477         const char *argv[SIZEOF_ARG] = { name, "=" };
7478         int argc = 1 + (cmd == option_set_command);
7479         int error = ERR;
7481         if (!argv_from_string(argv, &argc, value))
7482                 config_msg = "Too many option arguments";
7483         else
7484                 error = cmd(argc, argv);
7486         if (error == ERR)
7487                 warn("Option 'tig.%s': %s", name, config_msg);
7490 static bool
7491 set_environment_variable(const char *name, const char *value)
7493         size_t len = strlen(name) + 1 + strlen(value) + 1;
7494         char *env = malloc(len);
7496         if (env &&
7497             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7498             putenv(env) == 0)
7499                 return TRUE;
7500         free(env);
7501         return FALSE;
7504 static void
7505 set_work_tree(const char *value)
7507         char cwd[SIZEOF_STR];
7509         if (!getcwd(cwd, sizeof(cwd)))
7510                 die("Failed to get cwd path: %s", strerror(errno));
7511         if (chdir(opt_git_dir) < 0)
7512                 die("Failed to chdir(%s): %s", strerror(errno));
7513         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7514                 die("Failed to get git path: %s", strerror(errno));
7515         if (chdir(cwd) < 0)
7516                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7517         if (chdir(value) < 0)
7518                 die("Failed to chdir(%s): %s", value, strerror(errno));
7519         if (!getcwd(cwd, sizeof(cwd)))
7520                 die("Failed to get cwd path: %s", strerror(errno));
7521         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7522                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7523         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7524                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7525         opt_is_inside_work_tree = TRUE;
7528 static int
7529 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7531         if (!strcmp(name, "i18n.commitencoding"))
7532                 string_ncopy(opt_encoding, value, valuelen);
7534         else if (!strcmp(name, "core.editor"))
7535                 string_ncopy(opt_editor, value, valuelen);
7537         else if (!strcmp(name, "core.worktree"))
7538                 set_work_tree(value);
7540         else if (!prefixcmp(name, "tig.color."))
7541                 set_repo_config_option(name + 10, value, option_color_command);
7543         else if (!prefixcmp(name, "tig.bind."))
7544                 set_repo_config_option(name + 9, value, option_bind_command);
7546         else if (!prefixcmp(name, "tig."))
7547                 set_repo_config_option(name + 4, value, option_set_command);
7549         else if (*opt_head && !prefixcmp(name, "branch.") &&
7550                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7551                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7553         return OK;
7556 static int
7557 load_git_config(void)
7559         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7561         return run_io_load(config_list_argv, "=", read_repo_config_option);
7564 static int
7565 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7567         if (!opt_git_dir[0]) {
7568                 string_ncopy(opt_git_dir, name, namelen);
7570         } else if (opt_is_inside_work_tree == -1) {
7571                 /* This can be 3 different values depending on the
7572                  * version of git being used. If git-rev-parse does not
7573                  * understand --is-inside-work-tree it will simply echo
7574                  * the option else either "true" or "false" is printed.
7575                  * Default to true for the unknown case. */
7576                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7578         } else if (*name == '.') {
7579                 string_ncopy(opt_cdup, name, namelen);
7581         } else {
7582                 string_ncopy(opt_prefix, name, namelen);
7583         }
7585         return OK;
7588 static int
7589 load_repo_info(void)
7591         const char *rev_parse_argv[] = {
7592                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7593                         "--show-cdup", "--show-prefix", NULL
7594         };
7596         return run_io_load(rev_parse_argv, "=", read_repo_info);
7600 /*
7601  * Main
7602  */
7604 static const char usage[] =
7605 "tig " TIG_VERSION " (" __DATE__ ")\n"
7606 "\n"
7607 "Usage: tig        [options] [revs] [--] [paths]\n"
7608 "   or: tig show   [options] [revs] [--] [paths]\n"
7609 "   or: tig blame  [rev] path\n"
7610 "   or: tig status\n"
7611 "   or: tig <      [git command output]\n"
7612 "\n"
7613 "Options:\n"
7614 "  -v, --version   Show version and exit\n"
7615 "  -h, --help      Show help message and exit";
7617 static void __NORETURN
7618 quit(int sig)
7620         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7621         if (cursed)
7622                 endwin();
7623         exit(0);
7626 static void __NORETURN
7627 die(const char *err, ...)
7629         va_list args;
7631         endwin();
7633         va_start(args, err);
7634         fputs("tig: ", stderr);
7635         vfprintf(stderr, err, args);
7636         fputs("\n", stderr);
7637         va_end(args);
7639         exit(1);
7642 static void
7643 warn(const char *msg, ...)
7645         va_list args;
7647         va_start(args, msg);
7648         fputs("tig warning: ", stderr);
7649         vfprintf(stderr, msg, args);
7650         fputs("\n", stderr);
7651         va_end(args);
7654 static enum request
7655 parse_options(int argc, const char *argv[])
7657         enum request request = REQ_VIEW_MAIN;
7658         const char *subcommand;
7659         bool seen_dashdash = FALSE;
7660         /* XXX: This is vulnerable to the user overriding options
7661          * required for the main view parser. */
7662         const char *custom_argv[SIZEOF_ARG] = {
7663                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7664                         "--topo-order", NULL
7665         };
7666         int i, j = 6;
7668         if (!isatty(STDIN_FILENO)) {
7669                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7670                 return REQ_VIEW_PAGER;
7671         }
7673         if (argc <= 1)
7674                 return REQ_NONE;
7676         subcommand = argv[1];
7677         if (!strcmp(subcommand, "status")) {
7678                 if (argc > 2)
7679                         warn("ignoring arguments after `%s'", subcommand);
7680                 return REQ_VIEW_STATUS;
7682         } else if (!strcmp(subcommand, "blame")) {
7683                 if (argc <= 2 || argc > 4)
7684                         die("invalid number of options to blame\n\n%s", usage);
7686                 i = 2;
7687                 if (argc == 4) {
7688                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7689                         i++;
7690                 }
7692                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7693                 return REQ_VIEW_BLAME;
7695         } else if (!strcmp(subcommand, "show")) {
7696                 request = REQ_VIEW_DIFF;
7698         } else {
7699                 subcommand = NULL;
7700         }
7702         if (subcommand) {
7703                 custom_argv[1] = subcommand;
7704                 j = 2;
7705         }
7707         for (i = 1 + !!subcommand; i < argc; i++) {
7708                 const char *opt = argv[i];
7710                 if (seen_dashdash || !strcmp(opt, "--")) {
7711                         seen_dashdash = TRUE;
7713                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7714                         printf("tig version %s\n", TIG_VERSION);
7715                         quit(0);
7717                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7718                         printf("%s\n", usage);
7719                         quit(0);
7720                 }
7722                 custom_argv[j++] = opt;
7723                 if (j >= ARRAY_SIZE(custom_argv))
7724                         die("command too long");
7725         }
7727         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7728                 die("Failed to format arguments");
7730         return request;
7733 int
7734 main(int argc, const char *argv[])
7736         enum request request = parse_options(argc, argv);
7737         struct view *view;
7738         size_t i;
7740         signal(SIGINT, quit);
7741         signal(SIGPIPE, SIG_IGN);
7743         if (setlocale(LC_ALL, "")) {
7744                 char *codeset = nl_langinfo(CODESET);
7746                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7747         }
7749         if (load_repo_info() == ERR)
7750                 die("Failed to load repo info.");
7752         if (load_options() == ERR)
7753                 die("Failed to load user config.");
7755         if (load_git_config() == ERR)
7756                 die("Failed to load repo config.");
7758         /* Require a git repository unless when running in pager mode. */
7759         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7760                 die("Not a git repository");
7762         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7763                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7764                 if (opt_iconv_in == ICONV_NONE)
7765                         die("Failed to initialize character set conversion");
7766         }
7768         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7769                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7770                 if (opt_iconv_out == ICONV_NONE)
7771                         die("Failed to initialize character set conversion");
7772         }
7774         if (load_refs() == ERR)
7775                 die("Failed to load refs.");
7777         foreach_view (view, i)
7778                 argv_from_env(view->ops->argv, view->cmd_env);
7780         init_display();
7782         if (request != REQ_NONE)
7783                 open_view(NULL, request, OPEN_PREPARED);
7784         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7786         while (view_driver(display[current_view], request)) {
7787                 int key = get_input(0);
7789                 view = display[current_view];
7790                 request = get_keybinding(view->keymap, key);
7792                 /* Some low-level request handling. This keeps access to
7793                  * status_win restricted. */
7794                 switch (request) {
7795                 case REQ_PROMPT:
7796                 {
7797                         char *cmd = read_prompt(":");
7799                         if (cmd && isdigit(*cmd)) {
7800                                 int lineno = view->lineno + 1;
7802                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7803                                         select_view_line(view, lineno - 1);
7804                                         report("");
7805                                 } else {
7806                                         report("Unable to parse '%s' as a line number", cmd);
7807                                 }
7809                         } else if (cmd) {
7810                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7811                                 const char *argv[SIZEOF_ARG] = { "git" };
7812                                 int argc = 1;
7814                                 /* When running random commands, initially show the
7815                                  * command in the title. However, it maybe later be
7816                                  * overwritten if a commit line is selected. */
7817                                 string_ncopy(next->ref, cmd, strlen(cmd));
7819                                 if (!argv_from_string(argv, &argc, cmd)) {
7820                                         report("Too many arguments");
7821                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7822                                         report("Failed to format command");
7823                                 } else {
7824                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7825                                 }
7826                         }
7828                         request = REQ_NONE;
7829                         break;
7830                 }
7831                 case REQ_SEARCH:
7832                 case REQ_SEARCH_BACK:
7833                 {
7834                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7835                         char *search = read_prompt(prompt);
7837                         if (search)
7838                                 string_ncopy(opt_search, search, strlen(search));
7839                         else if (*opt_search)
7840                                 request = request == REQ_SEARCH ?
7841                                         REQ_FIND_NEXT :
7842                                         REQ_FIND_PREV;
7843                         else
7844                                 request = REQ_NONE;
7845                         break;
7846                 }
7847                 default:
7848                         break;
7849                 }
7850         }
7852         quit(0);
7854         return 0;