Code

Fix author abbreviation to handle multi-byte and multi-column characters
[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);
73 static inline unsigned char utf8_char_length(const char *string, const char *end);
75 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
76 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
77 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
79 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
80 #define STRING_SIZE(x)  (sizeof(x) - 1)
82 #define SIZEOF_STR      1024    /* Default string size. */
83 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
84 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
85 #define SIZEOF_ARG      32      /* Default argument array size. */
87 /* Revision graph */
89 #define REVGRAPH_INIT   'I'
90 #define REVGRAPH_MERGE  'M'
91 #define REVGRAPH_BRANCH '+'
92 #define REVGRAPH_COMMIT '*'
93 #define REVGRAPH_BOUND  '^'
95 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
97 /* This color name can be used to refer to the default term colors. */
98 #define COLOR_DEFAULT   (-1)
100 #define ICONV_NONE      ((iconv_t) -1)
101 #ifndef ICONV_CONST
102 #define ICONV_CONST     /* nothing */
103 #endif
105 /* The format and size of the date column in the main view. */
106 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
107 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
108 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
110 #define ID_COLS         8
111 #define AUTHOR_COLS     19
113 #define MIN_VIEW_HEIGHT 4
115 #define NULL_ID         "0000000000000000000000000000000000000000"
117 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
119 /* Some ASCII-shorthands fitted into the ncurses namespace. */
120 #define KEY_TAB         '\t'
121 #define KEY_RETURN      '\r'
122 #define KEY_ESC         27
125 struct ref {
126         char id[SIZEOF_REV];    /* Commit SHA1 ID */
127         unsigned int head:1;    /* Is it the current HEAD? */
128         unsigned int tag:1;     /* Is it a tag? */
129         unsigned int ltag:1;    /* If so, is the tag local? */
130         unsigned int remote:1;  /* Is it a remote ref? */
131         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
132         char name[1];           /* Ref name; tag or head names are shortened. */
133 };
135 struct ref_list {
136         char id[SIZEOF_REV];    /* Commit SHA1 ID */
137         size_t size;            /* Number of refs. */
138         struct ref **refs;      /* References for this ID. */
139 };
141 static struct ref_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146         FORMAT_ALL,             /* Perform replacement in all arguments. */
147         FORMAT_DASH,            /* Perform replacement up until "--". */
148         FORMAT_NONE             /* No replacement should be performed. */
149 };
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154         INPUT_OK,
155         INPUT_SKIP,
156         INPUT_STOP,
157         INPUT_CANCEL
158 };
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166         int hotkey;
167         const char *text;
168         void *data;
169 };
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
173 /*
174  * Allocation helpers ... Entering macro hell to never be seen again.
175  */
177 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
178 static type *                                                                   \
179 name(type **mem, size_t size, size_t increase)                                  \
180 {                                                                               \
181         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
182         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183         type *tmp = *mem;                                                       \
184                                                                                 \
185         if (mem == NULL || num_chunks != num_chunks_new) {                      \
186                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187                 if (tmp)                                                        \
188                         *mem = tmp;                                             \
189         }                                                                       \
190                                                                                 \
191         return tmp;                                                             \
194 /*
195  * String helpers
196  */
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
201         if (srclen > dstlen - 1)
202                 srclen = dstlen - 1;
204         strncpy(dst, src, srclen);
205         dst[srclen] = 0;
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214         string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
225         size_t size, pos;
227         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228                 if (src[pos] == '\t') {
229                         size_t expanded = tabsize - (size % tabsize);
231                         if (expanded + size >= dstlen - 1)
232                                 expanded = dstlen - size - 1;
233                         memcpy(dst + size, "        ", expanded);
234                         size += expanded;
235                 } else {
236                         dst[size++] = src[pos];
237                 }
238         }
240         dst[size] = 0;
243 static char *
244 chomp_string(char *name)
246         int namelen;
248         while (isspace(*name))
249                 name++;
251         namelen = strlen(name) - 1;
252         while (namelen > 0 && isspace(name[namelen]))
253                 name[namelen--] = 0;
255         return name;
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
261         va_list args;
262         size_t pos = bufpos ? *bufpos : 0;
264         va_start(args, fmt);
265         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266         va_end(args);
268         if (bufpos)
269                 *bufpos = pos;
271         return pos >= bufsize ? FALSE : TRUE;
274 #define string_format(buf, fmt, args...) \
275         string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278         string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
283         size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287         /* Diff-Header == DIFF_HEADER */
288         for (i = 0; i < len; i++) {
289                 if (toupper(str1[i]) == toupper(str2[i]))
290                         continue;
292                 if (string_enum_sep(str1[i]) &&
293                     string_enum_sep(str2[i]))
294                         continue;
296                 return str1[i] - str2[i];
297         }
299         return 0;
302 #define enum_equals(entry, str, len) \
303         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
305 struct enum_map {
306         const char *name;
307         int namelen;
308         int value;
309 };
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
313 static char *
314 enum_map_name(const char *name, size_t namelen)
316         static char buf[SIZEOF_STR];
317         int bufpos;
319         for (bufpos = 0; bufpos <= namelen; bufpos++) {
320                 buf[bufpos] = tolower(name[bufpos]);
321                 if (buf[bufpos] == '_')
322                         buf[bufpos] = '-';
323         }
325         buf[bufpos] = 0;
326         return buf;
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
331 static bool
332 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
334         size_t namelen = strlen(name);
335         int i;
337         for (i = 0; i < map_size; i++)
338                 if (enum_equals(map[i], name, namelen)) {
339                         *value = map[i].value;
340                         return TRUE;
341                 }
343         return FALSE;
346 #define map_enum(attr, map, name) \
347         map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350         strncmp(str1, str2, STRING_SIZE(str2))
352 static inline int
353 suffixcmp(const char *str, int slen, const char *suffix)
355         size_t len = slen >= 0 ? slen : strlen(str);
356         size_t suffixlen = strlen(suffix);
358         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
362 /*
363  * What value of "tz" was in effect back then at "time" in the
364  * local timezone?
365  */
366 static int local_tzoffset(time_t time)
368         time_t t, t_local;
369         struct tm tm;
370         int offset, eastwest; 
372         t = time;
373         localtime_r(&t, &tm);
374         t_local = mktime(&tm);
376         if (t_local < t) {
377                 eastwest = -1;
378                 offset = t - t_local;
379         } else {
380                 eastwest = 1;
381                 offset = t_local - t;
382         }
383         offset /= 60; /* in minutes */
384         offset = (offset % 60) + ((offset / 60) * 100);
385         return offset * eastwest;
388 #define DATE_INFO \
389         DATE_(NO), \
390         DATE_(DEFAULT), \
391         DATE_(RELATIVE), \
392         DATE_(SHORT)
394 enum date {
395 #define DATE_(name) DATE_##name
396         DATE_INFO
397 #undef  DATE_
398 };
400 static const struct enum_map date_map[] = {
401 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
402         DATE_INFO
403 #undef  DATE_
404 };
406 static const char *
407 string_date(const time_t *time, enum date date)
409         static char buf[DATE_COLS + 1];
410         static const struct enum_map reldate[] = {
411                 { "second", 1,                  60 * 2 },
412                 { "minute", 60,                 60 * 60 * 2 },
413                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
414                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
415                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
416                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
417         };
418         struct tm tm;
420         if (date == DATE_RELATIVE) {
421                 struct timeval now;
422                 time_t date = *time + local_tzoffset(*time);
423                 time_t seconds;
424                 int i;
426                 gettimeofday(&now, NULL);
427                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
428                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
429                         if (seconds >= reldate[i].value)
430                                 continue;
432                         seconds /= reldate[i].namelen;
433                         if (!string_format(buf, "%ld %s%s %s",
434                                            seconds, reldate[i].name,
435                                            seconds > 1 ? "s" : "",
436                                            now.tv_sec >= date ? "ago" : "ahead"))
437                                 break;
438                         return buf;
439                 }
440         }
442         gmtime_r(time, &tm);
443         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
447 #define AUTHOR_VALUES \
448         AUTHOR_(NO), \
449         AUTHOR_(FULL), \
450         AUTHOR_(ABBREVIATED)
452 enum author {
453 #define AUTHOR_(name) AUTHOR_##name
454         AUTHOR_VALUES,
455 #undef  AUTHOR_
456         AUTHOR_DEFAULT = AUTHOR_FULL
457 };
459 static const struct enum_map author_map[] = {
460 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
461         AUTHOR_VALUES
462 #undef  AUTHOR_
463 };
465 static const char *
466 get_author_initials(const char *author)
468         static char initials[AUTHOR_COLS * 6 + 1];
469         size_t pos = 0;
470         const char *end = strchr(author, '\0');
472 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
474         memset(initials, 0, sizeof(initials));
475         while (author < end) {
476                 unsigned char bytes;
477                 size_t i;
479                 while (is_initial_sep(*author))
480                         author++;
482                 bytes = utf8_char_length(author, end);
483                 if (bytes < sizeof(initials) - 1 - pos) {
484                         while (bytes--) {
485                                 initials[pos++] = *author++;
486                         }
487                 }
489                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
490                         if (i < sizeof(initials) - 1)
491                                 initials[i++] = *author;
492                 }
494                 initials[i++] = 0;
495         }
497         return initials;
501 static bool
502 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
504         int valuelen;
506         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
507                 bool advance = cmd[valuelen] != 0;
509                 cmd[valuelen] = 0;
510                 argv[(*argc)++] = chomp_string(cmd);
511                 cmd = chomp_string(cmd + valuelen + advance);
512         }
514         if (*argc < SIZEOF_ARG)
515                 argv[*argc] = NULL;
516         return *argc < SIZEOF_ARG;
519 static void
520 argv_from_env(const char **argv, const char *name)
522         char *env = argv ? getenv(name) : NULL;
523         int argc = 0;
525         if (env && *env)
526                 env = strdup(env);
527         if (env && !argv_from_string(argv, &argc, env))
528                 die("Too many arguments in the `%s` environment variable", name);
532 /*
533  * Executing external commands.
534  */
536 enum io_type {
537         IO_FD,                  /* File descriptor based IO. */
538         IO_BG,                  /* Execute command in the background. */
539         IO_FG,                  /* Execute command with same std{in,out,err}. */
540         IO_RD,                  /* Read only fork+exec IO. */
541         IO_WR,                  /* Write only fork+exec IO. */
542         IO_AP,                  /* Append fork+exec output to file. */
543 };
545 struct io {
546         enum io_type type;      /* The requested type of pipe. */
547         const char *dir;        /* Directory from which to execute. */
548         pid_t pid;              /* Pipe for reading or writing. */
549         int pipe;               /* Pipe end for reading or writing. */
550         int error;              /* Error status. */
551         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
552         char *buf;              /* Read buffer. */
553         size_t bufalloc;        /* Allocated buffer size. */
554         size_t bufsize;         /* Buffer content size. */
555         char *bufpos;           /* Current buffer position. */
556         unsigned int eof:1;     /* Has end of file been reached. */
557 };
559 static void
560 reset_io(struct io *io)
562         io->pipe = -1;
563         io->pid = 0;
564         io->buf = io->bufpos = NULL;
565         io->bufalloc = io->bufsize = 0;
566         io->error = 0;
567         io->eof = 0;
570 static void
571 init_io(struct io *io, const char *dir, enum io_type type)
573         reset_io(io);
574         io->type = type;
575         io->dir = dir;
578 static bool
579 init_io_rd(struct io *io, const char *argv[], const char *dir,
580                 enum format_flags flags)
582         init_io(io, dir, IO_RD);
583         return format_argv(io->argv, argv, flags);
586 static bool
587 io_open(struct io *io, const char *fmt, ...)
589         char name[SIZEOF_STR] = "";
590         bool fits;
591         va_list args;
593         init_io(io, NULL, IO_FD);
595         va_start(args, fmt);
596         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
597         va_end(args);
599         if (!fits) {
600                 io->error = ENAMETOOLONG;
601                 return FALSE;
602         }
603         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
604         if (io->pipe == -1)
605                 io->error = errno;
606         return io->pipe != -1;
609 static bool
610 kill_io(struct io *io)
612         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
615 static bool
616 done_io(struct io *io)
618         pid_t pid = io->pid;
620         if (io->pipe != -1)
621                 close(io->pipe);
622         free(io->buf);
623         reset_io(io);
625         while (pid > 0) {
626                 int status;
627                 pid_t waiting = waitpid(pid, &status, 0);
629                 if (waiting < 0) {
630                         if (errno == EINTR)
631                                 continue;
632                         report("waitpid failed (%s)", strerror(errno));
633                         return FALSE;
634                 }
636                 return waiting == pid &&
637                        !WIFSIGNALED(status) &&
638                        WIFEXITED(status) &&
639                        !WEXITSTATUS(status);
640         }
642         return TRUE;
645 static bool
646 start_io(struct io *io)
648         int pipefds[2] = { -1, -1 };
650         if (io->type == IO_FD)
651                 return TRUE;
653         if ((io->type == IO_RD || io->type == IO_WR) &&
654             pipe(pipefds) < 0)
655                 return FALSE;
656         else if (io->type == IO_AP)
657                 pipefds[1] = io->pipe;
659         if ((io->pid = fork())) {
660                 if (pipefds[!(io->type == IO_WR)] != -1)
661                         close(pipefds[!(io->type == IO_WR)]);
662                 if (io->pid != -1) {
663                         io->pipe = pipefds[!!(io->type == IO_WR)];
664                         return TRUE;
665                 }
667         } else {
668                 if (io->type != IO_FG) {
669                         int devnull = open("/dev/null", O_RDWR);
670                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
671                         int writefd = (io->type == IO_RD || io->type == IO_AP)
672                                                         ? pipefds[1] : devnull;
674                         dup2(readfd,  STDIN_FILENO);
675                         dup2(writefd, STDOUT_FILENO);
676                         dup2(devnull, STDERR_FILENO);
678                         close(devnull);
679                         if (pipefds[0] != -1)
680                                 close(pipefds[0]);
681                         if (pipefds[1] != -1)
682                                 close(pipefds[1]);
683                 }
685                 if (io->dir && *io->dir && chdir(io->dir) == -1)
686                         die("Failed to change directory: %s", strerror(errno));
688                 execvp(io->argv[0], (char *const*) io->argv);
689                 die("Failed to execute program: %s", strerror(errno));
690         }
692         if (pipefds[!!(io->type == IO_WR)] != -1)
693                 close(pipefds[!!(io->type == IO_WR)]);
694         return FALSE;
697 static bool
698 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
700         init_io(io, dir, type);
701         if (!format_argv(io->argv, argv, FORMAT_NONE))
702                 return FALSE;
703         return start_io(io);
706 static int
707 run_io_do(struct io *io)
709         return start_io(io) && done_io(io);
712 static int
713 run_io_bg(const char **argv)
715         struct io io = {};
717         init_io(&io, NULL, IO_BG);
718         if (!format_argv(io.argv, argv, FORMAT_NONE))
719                 return FALSE;
720         return run_io_do(&io);
723 static bool
724 run_io_fg(const char **argv, const char *dir)
726         struct io io = {};
728         init_io(&io, dir, IO_FG);
729         if (!format_argv(io.argv, argv, FORMAT_NONE))
730                 return FALSE;
731         return run_io_do(&io);
734 static bool
735 run_io_append(const char **argv, enum format_flags flags, int fd)
737         struct io io = {};
739         init_io(&io, NULL, IO_AP);
740         io.pipe = fd;
741         if (format_argv(io.argv, argv, flags))
742                 return run_io_do(&io);
743         close(fd);
744         return FALSE;
747 static bool
748 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
750         return init_io_rd(io, argv, dir, flags) && start_io(io);
753 static bool
754 io_eof(struct io *io)
756         return io->eof;
759 static int
760 io_error(struct io *io)
762         return io->error;
765 static char *
766 io_strerror(struct io *io)
768         return strerror(io->error);
771 static bool
772 io_can_read(struct io *io)
774         struct timeval tv = { 0, 500 };
775         fd_set fds;
777         FD_ZERO(&fds);
778         FD_SET(io->pipe, &fds);
780         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
783 static ssize_t
784 io_read(struct io *io, void *buf, size_t bufsize)
786         do {
787                 ssize_t readsize = read(io->pipe, buf, bufsize);
789                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
790                         continue;
791                 else if (readsize == -1)
792                         io->error = errno;
793                 else if (readsize == 0)
794                         io->eof = 1;
795                 return readsize;
796         } while (1);
799 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
801 static char *
802 io_get(struct io *io, int c, bool can_read)
804         char *eol;
805         ssize_t readsize;
807         while (TRUE) {
808                 if (io->bufsize > 0) {
809                         eol = memchr(io->bufpos, c, io->bufsize);
810                         if (eol) {
811                                 char *line = io->bufpos;
813                                 *eol = 0;
814                                 io->bufpos = eol + 1;
815                                 io->bufsize -= io->bufpos - line;
816                                 return line;
817                         }
818                 }
820                 if (io_eof(io)) {
821                         if (io->bufsize) {
822                                 io->bufpos[io->bufsize] = 0;
823                                 io->bufsize = 0;
824                                 return io->bufpos;
825                         }
826                         return NULL;
827                 }
829                 if (!can_read)
830                         return NULL;
832                 if (io->bufsize > 0 && io->bufpos > io->buf)
833                         memmove(io->buf, io->bufpos, io->bufsize);
835                 if (io->bufalloc == io->bufsize) {
836                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
837                                 return NULL;
838                         io->bufalloc += BUFSIZ;
839                 }
841                 io->bufpos = io->buf;
842                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
843                 if (io_error(io))
844                         return NULL;
845                 io->bufsize += readsize;
846         }
849 static bool
850 io_write(struct io *io, const void *buf, size_t bufsize)
852         size_t written = 0;
854         while (!io_error(io) && written < bufsize) {
855                 ssize_t size;
857                 size = write(io->pipe, buf + written, bufsize - written);
858                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
859                         continue;
860                 else if (size == -1)
861                         io->error = errno;
862                 else
863                         written += size;
864         }
866         return written == bufsize;
869 static bool
870 io_read_buf(struct io *io, char buf[], size_t bufsize)
872         char *result = io_get(io, '\n', TRUE);
874         if (result) {
875                 result = chomp_string(result);
876                 string_ncopy_do(buf, bufsize, result, strlen(result));
877         }
879         return done_io(io) && result;
882 static bool
883 run_io_buf(const char **argv, char buf[], size_t bufsize)
885         struct io io = {};
887         return run_io_rd(&io, argv, NULL, FORMAT_NONE)
888             && io_read_buf(&io, buf, bufsize);
891 static int
892 io_load(struct io *io, const char *separators,
893         int (*read_property)(char *, size_t, char *, size_t))
895         char *name;
896         int state = OK;
898         if (!start_io(io))
899                 return ERR;
901         while (state == OK && (name = io_get(io, '\n', TRUE))) {
902                 char *value;
903                 size_t namelen;
904                 size_t valuelen;
906                 name = chomp_string(name);
907                 namelen = strcspn(name, separators);
909                 if (name[namelen]) {
910                         name[namelen] = 0;
911                         value = chomp_string(name + namelen + 1);
912                         valuelen = strlen(value);
914                 } else {
915                         value = "";
916                         valuelen = 0;
917                 }
919                 state = read_property(name, namelen, value, valuelen);
920         }
922         if (state != ERR && io_error(io))
923                 state = ERR;
924         done_io(io);
926         return state;
929 static int
930 run_io_load(const char **argv, const char *separators,
931             int (*read_property)(char *, size_t, char *, size_t))
933         struct io io = {};
935         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
936                 ? io_load(&io, separators, read_property) : ERR;
940 /*
941  * User requests
942  */
944 #define REQ_INFO \
945         /* XXX: Keep the view request first and in sync with views[]. */ \
946         REQ_GROUP("View switching") \
947         REQ_(VIEW_MAIN,         "Show main view"), \
948         REQ_(VIEW_DIFF,         "Show diff view"), \
949         REQ_(VIEW_LOG,          "Show log view"), \
950         REQ_(VIEW_TREE,         "Show tree view"), \
951         REQ_(VIEW_BLOB,         "Show blob view"), \
952         REQ_(VIEW_BLAME,        "Show blame view"), \
953         REQ_(VIEW_BRANCH,       "Show branch view"), \
954         REQ_(VIEW_HELP,         "Show help page"), \
955         REQ_(VIEW_PAGER,        "Show pager view"), \
956         REQ_(VIEW_STATUS,       "Show status view"), \
957         REQ_(VIEW_STAGE,        "Show stage view"), \
958         \
959         REQ_GROUP("View manipulation") \
960         REQ_(ENTER,             "Enter current line and scroll"), \
961         REQ_(NEXT,              "Move to next"), \
962         REQ_(PREVIOUS,          "Move to previous"), \
963         REQ_(PARENT,            "Move to parent"), \
964         REQ_(VIEW_NEXT,         "Move focus to next view"), \
965         REQ_(REFRESH,           "Reload and refresh"), \
966         REQ_(MAXIMIZE,          "Maximize the current view"), \
967         REQ_(VIEW_CLOSE,        "Close the current view"), \
968         REQ_(QUIT,              "Close all views and quit"), \
969         \
970         REQ_GROUP("View specific requests") \
971         REQ_(STATUS_UPDATE,     "Update file status"), \
972         REQ_(STATUS_REVERT,     "Revert file changes"), \
973         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
974         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
975         \
976         REQ_GROUP("Cursor navigation") \
977         REQ_(MOVE_UP,           "Move cursor one line up"), \
978         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
979         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
980         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
981         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
982         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
983         \
984         REQ_GROUP("Scrolling") \
985         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
986         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
987         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
988         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
989         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
990         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
991         \
992         REQ_GROUP("Searching") \
993         REQ_(SEARCH,            "Search the view"), \
994         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
995         REQ_(FIND_NEXT,         "Find next search match"), \
996         REQ_(FIND_PREV,         "Find previous search match"), \
997         \
998         REQ_GROUP("Option manipulation") \
999         REQ_(OPTIONS,           "Open option menu"), \
1000         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1001         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1002         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1003         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1004         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1005         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1006         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1007         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1008         \
1009         REQ_GROUP("Misc") \
1010         REQ_(PROMPT,            "Bring up the prompt"), \
1011         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1012         REQ_(SHOW_VERSION,      "Show version information"), \
1013         REQ_(STOP_LOADING,      "Stop all loading views"), \
1014         REQ_(EDIT,              "Open in editor"), \
1015         REQ_(NONE,              "Do nothing")
1018 /* User action requests. */
1019 enum request {
1020 #define REQ_GROUP(help)
1021 #define REQ_(req, help) REQ_##req
1023         /* Offset all requests to avoid conflicts with ncurses getch values. */
1024         REQ_OFFSET = KEY_MAX + 1,
1025         REQ_INFO
1027 #undef  REQ_GROUP
1028 #undef  REQ_
1029 };
1031 struct request_info {
1032         enum request request;
1033         const char *name;
1034         int namelen;
1035         const char *help;
1036 };
1038 static const struct request_info req_info[] = {
1039 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1040 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1041         REQ_INFO
1042 #undef  REQ_GROUP
1043 #undef  REQ_
1044 };
1046 static enum request
1047 get_request(const char *name)
1049         int namelen = strlen(name);
1050         int i;
1052         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1053                 if (enum_equals(req_info[i], name, namelen))
1054                         return req_info[i].request;
1056         return REQ_NONE;
1060 /*
1061  * Options
1062  */
1064 /* Option and state variables. */
1065 static enum date opt_date               = DATE_DEFAULT;
1066 static enum author opt_author           = AUTHOR_DEFAULT;
1067 static bool opt_line_number             = FALSE;
1068 static bool opt_line_graphics           = TRUE;
1069 static bool opt_rev_graph               = FALSE;
1070 static bool opt_show_refs               = TRUE;
1071 static int opt_num_interval             = 5;
1072 static double opt_hscroll               = 0.50;
1073 static double opt_scale_split_view      = 2.0 / 3.0;
1074 static int opt_tab_size                 = 8;
1075 static int opt_author_cols              = AUTHOR_COLS;
1076 static char opt_path[SIZEOF_STR]        = "";
1077 static char opt_file[SIZEOF_STR]        = "";
1078 static char opt_ref[SIZEOF_REF]         = "";
1079 static char opt_head[SIZEOF_REF]        = "";
1080 static char opt_head_rev[SIZEOF_REV]    = "";
1081 static char opt_remote[SIZEOF_REF]      = "";
1082 static char opt_encoding[20]            = "UTF-8";
1083 static char opt_codeset[20]             = "UTF-8";
1084 static iconv_t opt_iconv_in             = ICONV_NONE;
1085 static iconv_t opt_iconv_out            = ICONV_NONE;
1086 static char opt_search[SIZEOF_STR]      = "";
1087 static char opt_cdup[SIZEOF_STR]        = "";
1088 static char opt_prefix[SIZEOF_STR]      = "";
1089 static char opt_git_dir[SIZEOF_STR]     = "";
1090 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1091 static char opt_editor[SIZEOF_STR]      = "";
1092 static FILE *opt_tty                    = NULL;
1094 #define is_initial_commit()     (!*opt_head_rev)
1095 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1096 #define mkdate(time)            string_date(time, opt_date)
1099 /*
1100  * Line-oriented content detection.
1101  */
1103 #define LINE_INFO \
1104 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1105 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1106 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1107 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1108 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1109 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1110 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1111 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1112 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1113 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1114 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1115 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1116 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1117 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1118 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1119 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1120 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1121 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1122 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1123 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1124 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1125 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1126 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1127 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1128 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1129 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1130 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1131 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1132 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1133 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1134 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1135 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1136 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1137 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1138 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1139 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1140 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1141 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1142 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1143 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1144 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1145 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1146 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1147 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1148 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1149 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1150 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1151 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1152 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1153 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1154 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1155 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1156 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1157 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1158 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1159 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1160 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1162 enum line_type {
1163 #define LINE(type, line, fg, bg, attr) \
1164         LINE_##type
1165         LINE_INFO,
1166         LINE_NONE
1167 #undef  LINE
1168 };
1170 struct line_info {
1171         const char *name;       /* Option name. */
1172         int namelen;            /* Size of option name. */
1173         const char *line;       /* The start of line to match. */
1174         int linelen;            /* Size of string to match. */
1175         int fg, bg, attr;       /* Color and text attributes for the lines. */
1176 };
1178 static struct line_info line_info[] = {
1179 #define LINE(type, line, fg, bg, attr) \
1180         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1181         LINE_INFO
1182 #undef  LINE
1183 };
1185 static enum line_type
1186 get_line_type(const char *line)
1188         int linelen = strlen(line);
1189         enum line_type type;
1191         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1192                 /* Case insensitive search matches Signed-off-by lines better. */
1193                 if (linelen >= line_info[type].linelen &&
1194                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1195                         return type;
1197         return LINE_DEFAULT;
1200 static inline int
1201 get_line_attr(enum line_type type)
1203         assert(type < ARRAY_SIZE(line_info));
1204         return COLOR_PAIR(type) | line_info[type].attr;
1207 static struct line_info *
1208 get_line_info(const char *name)
1210         size_t namelen = strlen(name);
1211         enum line_type type;
1213         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1214                 if (enum_equals(line_info[type], name, namelen))
1215                         return &line_info[type];
1217         return NULL;
1220 static void
1221 init_colors(void)
1223         int default_bg = line_info[LINE_DEFAULT].bg;
1224         int default_fg = line_info[LINE_DEFAULT].fg;
1225         enum line_type type;
1227         start_color();
1229         if (assume_default_colors(default_fg, default_bg) == ERR) {
1230                 default_bg = COLOR_BLACK;
1231                 default_fg = COLOR_WHITE;
1232         }
1234         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1235                 struct line_info *info = &line_info[type];
1236                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1237                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1239                 init_pair(type, fg, bg);
1240         }
1243 struct line {
1244         enum line_type type;
1246         /* State flags */
1247         unsigned int selected:1;
1248         unsigned int dirty:1;
1249         unsigned int cleareol:1;
1250         unsigned int other:16;
1252         void *data;             /* User data */
1253 };
1256 /*
1257  * Keys
1258  */
1260 struct keybinding {
1261         int alias;
1262         enum request request;
1263 };
1265 static const struct keybinding default_keybindings[] = {
1266         /* View switching */
1267         { 'm',          REQ_VIEW_MAIN },
1268         { 'd',          REQ_VIEW_DIFF },
1269         { 'l',          REQ_VIEW_LOG },
1270         { 't',          REQ_VIEW_TREE },
1271         { 'f',          REQ_VIEW_BLOB },
1272         { 'B',          REQ_VIEW_BLAME },
1273         { 'H',          REQ_VIEW_BRANCH },
1274         { 'p',          REQ_VIEW_PAGER },
1275         { 'h',          REQ_VIEW_HELP },
1276         { 'S',          REQ_VIEW_STATUS },
1277         { 'c',          REQ_VIEW_STAGE },
1279         /* View manipulation */
1280         { 'q',          REQ_VIEW_CLOSE },
1281         { KEY_TAB,      REQ_VIEW_NEXT },
1282         { KEY_RETURN,   REQ_ENTER },
1283         { KEY_UP,       REQ_PREVIOUS },
1284         { KEY_DOWN,     REQ_NEXT },
1285         { 'R',          REQ_REFRESH },
1286         { KEY_F(5),     REQ_REFRESH },
1287         { 'O',          REQ_MAXIMIZE },
1289         /* Cursor navigation */
1290         { 'k',          REQ_MOVE_UP },
1291         { 'j',          REQ_MOVE_DOWN },
1292         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1293         { KEY_END,      REQ_MOVE_LAST_LINE },
1294         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1295         { ' ',          REQ_MOVE_PAGE_DOWN },
1296         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1297         { 'b',          REQ_MOVE_PAGE_UP },
1298         { '-',          REQ_MOVE_PAGE_UP },
1300         /* Scrolling */
1301         { KEY_LEFT,     REQ_SCROLL_LEFT },
1302         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1303         { KEY_IC,       REQ_SCROLL_LINE_UP },
1304         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1305         { 'w',          REQ_SCROLL_PAGE_UP },
1306         { 's',          REQ_SCROLL_PAGE_DOWN },
1308         /* Searching */
1309         { '/',          REQ_SEARCH },
1310         { '?',          REQ_SEARCH_BACK },
1311         { 'n',          REQ_FIND_NEXT },
1312         { 'N',          REQ_FIND_PREV },
1314         /* Misc */
1315         { 'Q',          REQ_QUIT },
1316         { 'z',          REQ_STOP_LOADING },
1317         { 'v',          REQ_SHOW_VERSION },
1318         { 'r',          REQ_SCREEN_REDRAW },
1319         { 'o',          REQ_OPTIONS },
1320         { '.',          REQ_TOGGLE_LINENO },
1321         { 'D',          REQ_TOGGLE_DATE },
1322         { 'A',          REQ_TOGGLE_AUTHOR },
1323         { 'g',          REQ_TOGGLE_REV_GRAPH },
1324         { 'F',          REQ_TOGGLE_REFS },
1325         { 'I',          REQ_TOGGLE_SORT_ORDER },
1326         { 'i',          REQ_TOGGLE_SORT_FIELD },
1327         { ':',          REQ_PROMPT },
1328         { 'u',          REQ_STATUS_UPDATE },
1329         { '!',          REQ_STATUS_REVERT },
1330         { 'M',          REQ_STATUS_MERGE },
1331         { '@',          REQ_STAGE_NEXT },
1332         { ',',          REQ_PARENT },
1333         { 'e',          REQ_EDIT },
1334 };
1336 #define KEYMAP_INFO \
1337         KEYMAP_(GENERIC), \
1338         KEYMAP_(MAIN), \
1339         KEYMAP_(DIFF), \
1340         KEYMAP_(LOG), \
1341         KEYMAP_(TREE), \
1342         KEYMAP_(BLOB), \
1343         KEYMAP_(BLAME), \
1344         KEYMAP_(BRANCH), \
1345         KEYMAP_(PAGER), \
1346         KEYMAP_(HELP), \
1347         KEYMAP_(STATUS), \
1348         KEYMAP_(STAGE)
1350 enum keymap {
1351 #define KEYMAP_(name) KEYMAP_##name
1352         KEYMAP_INFO
1353 #undef  KEYMAP_
1354 };
1356 static const struct enum_map keymap_table[] = {
1357 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1358         KEYMAP_INFO
1359 #undef  KEYMAP_
1360 };
1362 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1364 struct keybinding_table {
1365         struct keybinding *data;
1366         size_t size;
1367 };
1369 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1371 static void
1372 add_keybinding(enum keymap keymap, enum request request, int key)
1374         struct keybinding_table *table = &keybindings[keymap];
1376         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1377         if (!table->data)
1378                 die("Failed to allocate keybinding");
1379         table->data[table->size].alias = key;
1380         table->data[table->size++].request = request;
1383 /* Looks for a key binding first in the given map, then in the generic map, and
1384  * lastly in the default keybindings. */
1385 static enum request
1386 get_keybinding(enum keymap keymap, int key)
1388         size_t i;
1390         for (i = 0; i < keybindings[keymap].size; i++)
1391                 if (keybindings[keymap].data[i].alias == key)
1392                         return keybindings[keymap].data[i].request;
1394         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1395                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1396                         return keybindings[KEYMAP_GENERIC].data[i].request;
1398         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1399                 if (default_keybindings[i].alias == key)
1400                         return default_keybindings[i].request;
1402         return (enum request) key;
1406 struct key {
1407         const char *name;
1408         int value;
1409 };
1411 static const struct key key_table[] = {
1412         { "Enter",      KEY_RETURN },
1413         { "Space",      ' ' },
1414         { "Backspace",  KEY_BACKSPACE },
1415         { "Tab",        KEY_TAB },
1416         { "Escape",     KEY_ESC },
1417         { "Left",       KEY_LEFT },
1418         { "Right",      KEY_RIGHT },
1419         { "Up",         KEY_UP },
1420         { "Down",       KEY_DOWN },
1421         { "Insert",     KEY_IC },
1422         { "Delete",     KEY_DC },
1423         { "Hash",       '#' },
1424         { "Home",       KEY_HOME },
1425         { "End",        KEY_END },
1426         { "PageUp",     KEY_PPAGE },
1427         { "PageDown",   KEY_NPAGE },
1428         { "F1",         KEY_F(1) },
1429         { "F2",         KEY_F(2) },
1430         { "F3",         KEY_F(3) },
1431         { "F4",         KEY_F(4) },
1432         { "F5",         KEY_F(5) },
1433         { "F6",         KEY_F(6) },
1434         { "F7",         KEY_F(7) },
1435         { "F8",         KEY_F(8) },
1436         { "F9",         KEY_F(9) },
1437         { "F10",        KEY_F(10) },
1438         { "F11",        KEY_F(11) },
1439         { "F12",        KEY_F(12) },
1440 };
1442 static int
1443 get_key_value(const char *name)
1445         int i;
1447         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1448                 if (!strcasecmp(key_table[i].name, name))
1449                         return key_table[i].value;
1451         if (strlen(name) == 1 && isprint(*name))
1452                 return (int) *name;
1454         return ERR;
1457 static const char *
1458 get_key_name(int key_value)
1460         static char key_char[] = "'X'";
1461         const char *seq = NULL;
1462         int key;
1464         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1465                 if (key_table[key].value == key_value)
1466                         seq = key_table[key].name;
1468         if (seq == NULL &&
1469             key_value < 127 &&
1470             isprint(key_value)) {
1471                 key_char[1] = (char) key_value;
1472                 seq = key_char;
1473         }
1475         return seq ? seq : "(no key)";
1478 static bool
1479 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1481         const char *sep = *pos > 0 ? ", " : "";
1482         const char *keyname = get_key_name(keybinding->alias);
1484         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1487 static bool
1488 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1489                            enum keymap keymap, bool all)
1491         int i;
1493         for (i = 0; i < keybindings[keymap].size; i++) {
1494                 if (keybindings[keymap].data[i].request == request) {
1495                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1496                                 return FALSE;
1497                         if (!all)
1498                                 break;
1499                 }
1500         }
1502         return TRUE;
1505 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1507 static const char *
1508 get_keys(enum keymap keymap, enum request request, bool all)
1510         static char buf[BUFSIZ];
1511         size_t pos = 0;
1512         int i;
1514         buf[pos] = 0;
1516         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1517                 return "Too many keybindings!";
1518         if (pos > 0 && !all)
1519                 return buf;
1521         if (keymap != KEYMAP_GENERIC) {
1522                 /* Only the generic keymap includes the default keybindings when
1523                  * listing all keys. */
1524                 if (all)
1525                         return buf;
1527                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1528                         return "Too many keybindings!";
1529                 if (pos)
1530                         return buf;
1531         }
1533         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1534                 if (default_keybindings[i].request == request) {
1535                         if (!append_key(buf, &pos, &default_keybindings[i]))
1536                                 return "Too many keybindings!";
1537                         if (!all)
1538                                 return buf;
1539                 }
1540         }
1542         return buf;
1545 struct run_request {
1546         enum keymap keymap;
1547         int key;
1548         const char *argv[SIZEOF_ARG];
1549 };
1551 static struct run_request *run_request;
1552 static size_t run_requests;
1554 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1556 static enum request
1557 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1559         struct run_request *req;
1561         if (argc >= ARRAY_SIZE(req->argv) - 1)
1562                 return REQ_NONE;
1564         if (!realloc_run_requests(&run_request, run_requests, 1))
1565                 return REQ_NONE;
1567         req = &run_request[run_requests];
1568         req->keymap = keymap;
1569         req->key = key;
1570         req->argv[0] = NULL;
1572         if (!format_argv(req->argv, argv, FORMAT_NONE))
1573                 return REQ_NONE;
1575         return REQ_NONE + ++run_requests;
1578 static struct run_request *
1579 get_run_request(enum request request)
1581         if (request <= REQ_NONE)
1582                 return NULL;
1583         return &run_request[request - REQ_NONE - 1];
1586 static void
1587 add_builtin_run_requests(void)
1589         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1590         const char *commit[] = { "git", "commit", NULL };
1591         const char *gc[] = { "git", "gc", NULL };
1592         struct {
1593                 enum keymap keymap;
1594                 int key;
1595                 int argc;
1596                 const char **argv;
1597         } reqs[] = {
1598                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1599                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1600                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1601         };
1602         int i;
1604         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1605                 enum request req;
1607                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1608                 if (req != REQ_NONE)
1609                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1610         }
1613 /*
1614  * User config file handling.
1615  */
1617 static int   config_lineno;
1618 static bool  config_errors;
1619 static const char *config_msg;
1621 static const struct enum_map color_map[] = {
1622 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1623         COLOR_MAP(DEFAULT),
1624         COLOR_MAP(BLACK),
1625         COLOR_MAP(BLUE),
1626         COLOR_MAP(CYAN),
1627         COLOR_MAP(GREEN),
1628         COLOR_MAP(MAGENTA),
1629         COLOR_MAP(RED),
1630         COLOR_MAP(WHITE),
1631         COLOR_MAP(YELLOW),
1632 };
1634 static const struct enum_map attr_map[] = {
1635 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1636         ATTR_MAP(NORMAL),
1637         ATTR_MAP(BLINK),
1638         ATTR_MAP(BOLD),
1639         ATTR_MAP(DIM),
1640         ATTR_MAP(REVERSE),
1641         ATTR_MAP(STANDOUT),
1642         ATTR_MAP(UNDERLINE),
1643 };
1645 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1647 static int parse_step(double *opt, const char *arg)
1649         *opt = atoi(arg);
1650         if (!strchr(arg, '%'))
1651                 return OK;
1653         /* "Shift down" so 100% and 1 does not conflict. */
1654         *opt = (*opt - 1) / 100;
1655         if (*opt >= 1.0) {
1656                 *opt = 0.99;
1657                 config_msg = "Step value larger than 100%";
1658                 return ERR;
1659         }
1660         if (*opt < 0.0) {
1661                 *opt = 1;
1662                 config_msg = "Invalid step value";
1663                 return ERR;
1664         }
1665         return OK;
1668 static int
1669 parse_int(int *opt, const char *arg, int min, int max)
1671         int value = atoi(arg);
1673         if (min <= value && value <= max) {
1674                 *opt = value;
1675                 return OK;
1676         }
1678         config_msg = "Integer value out of bound";
1679         return ERR;
1682 static bool
1683 set_color(int *color, const char *name)
1685         if (map_enum(color, color_map, name))
1686                 return TRUE;
1687         if (!prefixcmp(name, "color"))
1688                 return parse_int(color, name + 5, 0, 255) == OK;
1689         return FALSE;
1692 /* Wants: object fgcolor bgcolor [attribute] */
1693 static int
1694 option_color_command(int argc, const char *argv[])
1696         struct line_info *info;
1698         if (argc < 3) {
1699                 config_msg = "Wrong number of arguments given to color command";
1700                 return ERR;
1701         }
1703         info = get_line_info(argv[0]);
1704         if (!info) {
1705                 static const struct enum_map obsolete[] = {
1706                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1707                         ENUM_MAP("main-date",   LINE_DATE),
1708                         ENUM_MAP("main-author", LINE_AUTHOR),
1709                 };
1710                 int index;
1712                 if (!map_enum(&index, obsolete, argv[0])) {
1713                         config_msg = "Unknown color name";
1714                         return ERR;
1715                 }
1716                 info = &line_info[index];
1717         }
1719         if (!set_color(&info->fg, argv[1]) ||
1720             !set_color(&info->bg, argv[2])) {
1721                 config_msg = "Unknown color";
1722                 return ERR;
1723         }
1725         info->attr = 0;
1726         while (argc-- > 3) {
1727                 int attr;
1729                 if (!set_attribute(&attr, argv[argc])) {
1730                         config_msg = "Unknown attribute";
1731                         return ERR;
1732                 }
1733                 info->attr |= attr;
1734         }
1736         return OK;
1739 static int parse_bool(bool *opt, const char *arg)
1741         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1742                 ? TRUE : FALSE;
1743         return OK;
1746 static int parse_enum_do(unsigned int *opt, const char *arg,
1747                          const struct enum_map *map, size_t map_size)
1749         bool is_true;
1751         assert(map_size > 1);
1753         if (map_enum_do(map, map_size, (int *) opt, arg))
1754                 return OK;
1756         if (parse_bool(&is_true, arg) != OK)
1757                 return ERR;
1759         *opt = is_true ? map[1].value : map[0].value;
1760         return OK;
1763 #define parse_enum(opt, arg, map) \
1764         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1766 static int
1767 parse_string(char *opt, const char *arg, size_t optsize)
1769         int arglen = strlen(arg);
1771         switch (arg[0]) {
1772         case '\"':
1773         case '\'':
1774                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1775                         config_msg = "Unmatched quotation";
1776                         return ERR;
1777                 }
1778                 arg += 1; arglen -= 2;
1779         default:
1780                 string_ncopy_do(opt, optsize, arg, arglen);
1781                 return OK;
1782         }
1785 /* Wants: name = value */
1786 static int
1787 option_set_command(int argc, const char *argv[])
1789         if (argc != 3) {
1790                 config_msg = "Wrong number of arguments given to set command";
1791                 return ERR;
1792         }
1794         if (strcmp(argv[1], "=")) {
1795                 config_msg = "No value assigned";
1796                 return ERR;
1797         }
1799         if (!strcmp(argv[0], "show-author"))
1800                 return parse_enum(&opt_author, argv[2], author_map);
1802         if (!strcmp(argv[0], "show-date"))
1803                 return parse_enum(&opt_date, argv[2], date_map);
1805         if (!strcmp(argv[0], "show-rev-graph"))
1806                 return parse_bool(&opt_rev_graph, argv[2]);
1808         if (!strcmp(argv[0], "show-refs"))
1809                 return parse_bool(&opt_show_refs, argv[2]);
1811         if (!strcmp(argv[0], "show-line-numbers"))
1812                 return parse_bool(&opt_line_number, argv[2]);
1814         if (!strcmp(argv[0], "line-graphics"))
1815                 return parse_bool(&opt_line_graphics, argv[2]);
1817         if (!strcmp(argv[0], "line-number-interval"))
1818                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1820         if (!strcmp(argv[0], "author-width"))
1821                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1823         if (!strcmp(argv[0], "horizontal-scroll"))
1824                 return parse_step(&opt_hscroll, argv[2]);
1826         if (!strcmp(argv[0], "split-view-height"))
1827                 return parse_step(&opt_scale_split_view, argv[2]);
1829         if (!strcmp(argv[0], "tab-size"))
1830                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1832         if (!strcmp(argv[0], "commit-encoding"))
1833                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1835         config_msg = "Unknown variable name";
1836         return ERR;
1839 /* Wants: mode request key */
1840 static int
1841 option_bind_command(int argc, const char *argv[])
1843         enum request request;
1844         int keymap = -1;
1845         int key;
1847         if (argc < 3) {
1848                 config_msg = "Wrong number of arguments given to bind command";
1849                 return ERR;
1850         }
1852         if (set_keymap(&keymap, argv[0]) == ERR) {
1853                 config_msg = "Unknown key map";
1854                 return ERR;
1855         }
1857         key = get_key_value(argv[1]);
1858         if (key == ERR) {
1859                 config_msg = "Unknown key";
1860                 return ERR;
1861         }
1863         request = get_request(argv[2]);
1864         if (request == REQ_NONE) {
1865                 static const struct enum_map obsolete[] = {
1866                         ENUM_MAP("cherry-pick",         REQ_NONE),
1867                         ENUM_MAP("screen-resize",       REQ_NONE),
1868                         ENUM_MAP("tree-parent",         REQ_PARENT),
1869                 };
1870                 int alias;
1872                 if (map_enum(&alias, obsolete, argv[2])) {
1873                         if (alias != REQ_NONE)
1874                                 add_keybinding(keymap, alias, key);
1875                         config_msg = "Obsolete request name";
1876                         return ERR;
1877                 }
1878         }
1879         if (request == REQ_NONE && *argv[2]++ == '!')
1880                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1881         if (request == REQ_NONE) {
1882                 config_msg = "Unknown request name";
1883                 return ERR;
1884         }
1886         add_keybinding(keymap, request, key);
1888         return OK;
1891 static int
1892 set_option(const char *opt, char *value)
1894         const char *argv[SIZEOF_ARG];
1895         int argc = 0;
1897         if (!argv_from_string(argv, &argc, value)) {
1898                 config_msg = "Too many option arguments";
1899                 return ERR;
1900         }
1902         if (!strcmp(opt, "color"))
1903                 return option_color_command(argc, argv);
1905         if (!strcmp(opt, "set"))
1906                 return option_set_command(argc, argv);
1908         if (!strcmp(opt, "bind"))
1909                 return option_bind_command(argc, argv);
1911         config_msg = "Unknown option command";
1912         return ERR;
1915 static int
1916 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1918         int status = OK;
1920         config_lineno++;
1921         config_msg = "Internal error";
1923         /* Check for comment markers, since read_properties() will
1924          * only ensure opt and value are split at first " \t". */
1925         optlen = strcspn(opt, "#");
1926         if (optlen == 0)
1927                 return OK;
1929         if (opt[optlen] != 0) {
1930                 config_msg = "No option value";
1931                 status = ERR;
1933         }  else {
1934                 /* Look for comment endings in the value. */
1935                 size_t len = strcspn(value, "#");
1937                 if (len < valuelen) {
1938                         valuelen = len;
1939                         value[valuelen] = 0;
1940                 }
1942                 status = set_option(opt, value);
1943         }
1945         if (status == ERR) {
1946                 warn("Error on line %d, near '%.*s': %s",
1947                      config_lineno, (int) optlen, opt, config_msg);
1948                 config_errors = TRUE;
1949         }
1951         /* Always keep going if errors are encountered. */
1952         return OK;
1955 static void
1956 load_option_file(const char *path)
1958         struct io io = {};
1960         /* It's OK that the file doesn't exist. */
1961         if (!io_open(&io, "%s", path))
1962                 return;
1964         config_lineno = 0;
1965         config_errors = FALSE;
1967         if (io_load(&io, " \t", read_option) == ERR ||
1968             config_errors == TRUE)
1969                 warn("Errors while loading %s.", path);
1972 static int
1973 load_options(void)
1975         const char *home = getenv("HOME");
1976         const char *tigrc_user = getenv("TIGRC_USER");
1977         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1978         char buf[SIZEOF_STR];
1980         add_builtin_run_requests();
1982         if (!tigrc_system)
1983                 tigrc_system = SYSCONFDIR "/tigrc";
1984         load_option_file(tigrc_system);
1986         if (!tigrc_user) {
1987                 if (!home || !string_format(buf, "%s/.tigrc", home))
1988                         return ERR;
1989                 tigrc_user = buf;
1990         }
1991         load_option_file(tigrc_user);
1993         return OK;
1997 /*
1998  * The viewer
1999  */
2001 struct view;
2002 struct view_ops;
2004 /* The display array of active views and the index of the current view. */
2005 static struct view *display[2];
2006 static unsigned int current_view;
2008 #define foreach_displayed_view(view, i) \
2009         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2011 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2013 /* Current head and commit ID */
2014 static char ref_blob[SIZEOF_REF]        = "";
2015 static char ref_commit[SIZEOF_REF]      = "HEAD";
2016 static char ref_head[SIZEOF_REF]        = "HEAD";
2018 struct view {
2019         const char *name;       /* View name */
2020         const char *cmd_env;    /* Command line set via environment */
2021         const char *id;         /* Points to either of ref_{head,commit,blob} */
2023         struct view_ops *ops;   /* View operations */
2025         enum keymap keymap;     /* What keymap does this view have */
2026         bool git_dir;           /* Whether the view requires a git directory. */
2028         char ref[SIZEOF_REF];   /* Hovered commit reference */
2029         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2031         int height, width;      /* The width and height of the main window */
2032         WINDOW *win;            /* The main window */
2033         WINDOW *title;          /* The title window living below the main window */
2035         /* Navigation */
2036         unsigned long offset;   /* Offset of the window top */
2037         unsigned long yoffset;  /* Offset from the window side. */
2038         unsigned long lineno;   /* Current line number */
2039         unsigned long p_offset; /* Previous offset of the window top */
2040         unsigned long p_yoffset;/* Previous offset from the window side */
2041         unsigned long p_lineno; /* Previous current line number */
2042         bool p_restore;         /* Should the previous position be restored. */
2044         /* Searching */
2045         char grep[SIZEOF_STR];  /* Search string */
2046         regex_t *regex;         /* Pre-compiled regexp */
2048         /* If non-NULL, points to the view that opened this view. If this view
2049          * is closed tig will switch back to the parent view. */
2050         struct view *parent;
2052         /* Buffering */
2053         size_t lines;           /* Total number of lines */
2054         struct line *line;      /* Line index */
2055         unsigned int digits;    /* Number of digits in the lines member. */
2057         /* Drawing */
2058         struct line *curline;   /* Line currently being drawn. */
2059         enum line_type curtype; /* Attribute currently used for drawing. */
2060         unsigned long col;      /* Column when drawing. */
2061         bool has_scrolled;      /* View was scrolled. */
2063         /* Loading */
2064         struct io io;
2065         struct io *pipe;
2066         time_t start_time;
2067         time_t update_secs;
2068 };
2070 struct view_ops {
2071         /* What type of content being displayed. Used in the title bar. */
2072         const char *type;
2073         /* Default command arguments. */
2074         const char **argv;
2075         /* Open and reads in all view content. */
2076         bool (*open)(struct view *view);
2077         /* Read one line; updates view->line. */
2078         bool (*read)(struct view *view, char *data);
2079         /* Draw one line; @lineno must be < view->height. */
2080         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2081         /* Depending on view handle a special requests. */
2082         enum request (*request)(struct view *view, enum request request, struct line *line);
2083         /* Search for regexp in a line. */
2084         bool (*grep)(struct view *view, struct line *line);
2085         /* Select line */
2086         void (*select)(struct view *view, struct line *line);
2087         /* Prepare view for loading */
2088         bool (*prepare)(struct view *view);
2089 };
2091 static struct view_ops blame_ops;
2092 static struct view_ops blob_ops;
2093 static struct view_ops diff_ops;
2094 static struct view_ops help_ops;
2095 static struct view_ops log_ops;
2096 static struct view_ops main_ops;
2097 static struct view_ops pager_ops;
2098 static struct view_ops stage_ops;
2099 static struct view_ops status_ops;
2100 static struct view_ops tree_ops;
2101 static struct view_ops branch_ops;
2103 #define VIEW_STR(name, env, ref, ops, map, git) \
2104         { name, #env, ref, ops, map, git }
2106 #define VIEW_(id, name, ops, git, ref) \
2107         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2110 static struct view views[] = {
2111         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2112         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2113         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2114         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2115         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2116         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2117         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2118         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2119         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2120         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2121         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2122 };
2124 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2125 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2127 #define foreach_view(view, i) \
2128         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2130 #define view_is_displayed(view) \
2131         (view == display[0] || view == display[1])
2134 enum line_graphic {
2135         LINE_GRAPHIC_VLINE
2136 };
2138 static chtype line_graphics[] = {
2139         /* LINE_GRAPHIC_VLINE: */ '|'
2140 };
2142 static inline void
2143 set_view_attr(struct view *view, enum line_type type)
2145         if (!view->curline->selected && view->curtype != type) {
2146                 wattrset(view->win, get_line_attr(type));
2147                 wchgat(view->win, -1, 0, type, NULL);
2148                 view->curtype = type;
2149         }
2152 static int
2153 draw_chars(struct view *view, enum line_type type, const char *string,
2154            int max_len, bool use_tilde)
2156         static char out_buffer[BUFSIZ * 2];
2157         int len = 0;
2158         int col = 0;
2159         int trimmed = FALSE;
2160         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2162         if (max_len <= 0)
2163                 return 0;
2165         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2167         set_view_attr(view, type);
2168         if (len > 0) {
2169                 if (opt_iconv_out != ICONV_NONE) {
2170                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2171                         size_t inlen = len + 1;
2173                         char *outbuf = out_buffer;
2174                         size_t outlen = sizeof(out_buffer);
2176                         size_t ret;
2178                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2179                         if (ret != (size_t) -1) {
2180                                 string = out_buffer;
2181                                 len = sizeof(out_buffer) - outlen;
2182                         }
2183                 }
2185                 waddnstr(view->win, string, len);
2186         }
2187         if (trimmed && use_tilde) {
2188                 set_view_attr(view, LINE_DELIMITER);
2189                 waddch(view->win, '~');
2190                 col++;
2191         }
2193         return col;
2196 static int
2197 draw_space(struct view *view, enum line_type type, int max, int spaces)
2199         static char space[] = "                    ";
2200         int col = 0;
2202         spaces = MIN(max, spaces);
2204         while (spaces > 0) {
2205                 int len = MIN(spaces, sizeof(space) - 1);
2207                 col += draw_chars(view, type, space, len, FALSE);
2208                 spaces -= len;
2209         }
2211         return col;
2214 static bool
2215 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2217         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2218         return view->width + view->yoffset <= view->col;
2221 static bool
2222 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2224         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2225         int max = view->width + view->yoffset - view->col;
2226         int i;
2228         if (max < size)
2229                 size = max;
2231         set_view_attr(view, type);
2232         /* Using waddch() instead of waddnstr() ensures that
2233          * they'll be rendered correctly for the cursor line. */
2234         for (i = skip; i < size; i++)
2235                 waddch(view->win, graphic[i]);
2237         view->col += size;
2238         if (size < max && skip <= size)
2239                 waddch(view->win, ' ');
2240         view->col++;
2242         return view->width + view->yoffset <= view->col;
2245 static bool
2246 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2248         int max = MIN(view->width + view->yoffset - view->col, len);
2249         int col;
2251         if (text)
2252                 col = draw_chars(view, type, text, max - 1, trim);
2253         else
2254                 col = draw_space(view, type, max - 1, max - 1);
2256         view->col += col;
2257         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2258         return view->width + view->yoffset <= view->col;
2261 static bool
2262 draw_date(struct view *view, time_t *time)
2264         const char *date = time ? mkdate(time) : "";
2265         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2267         return draw_field(view, LINE_DATE, date, cols, FALSE);
2270 static bool
2271 draw_author(struct view *view, const char *author)
2273         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2274         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2276         if (abbreviate && author)
2277                 author = get_author_initials(author);
2279         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2282 static bool
2283 draw_mode(struct view *view, mode_t mode)
2285         const char *str;
2287         if (S_ISDIR(mode))
2288                 str = "drwxr-xr-x";
2289         else if (S_ISLNK(mode))
2290                 str = "lrwxrwxrwx";
2291         else if (S_ISGITLINK(mode))
2292                 str = "m---------";
2293         else if (S_ISREG(mode) && mode & S_IXUSR)
2294                 str = "-rwxr-xr-x";
2295         else if (S_ISREG(mode))
2296                 str = "-rw-r--r--";
2297         else
2298                 str = "----------";
2300         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2303 static bool
2304 draw_lineno(struct view *view, unsigned int lineno)
2306         char number[10];
2307         int digits3 = view->digits < 3 ? 3 : view->digits;
2308         int max = MIN(view->width + view->yoffset - view->col, digits3);
2309         char *text = NULL;
2311         lineno += view->offset + 1;
2312         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2313                 static char fmt[] = "%1ld";
2315                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2316                 if (string_format(number, fmt, lineno))
2317                         text = number;
2318         }
2319         if (text)
2320                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2321         else
2322                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2323         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2326 static bool
2327 draw_view_line(struct view *view, unsigned int lineno)
2329         struct line *line;
2330         bool selected = (view->offset + lineno == view->lineno);
2332         assert(view_is_displayed(view));
2334         if (view->offset + lineno >= view->lines)
2335                 return FALSE;
2337         line = &view->line[view->offset + lineno];
2339         wmove(view->win, lineno, 0);
2340         if (line->cleareol)
2341                 wclrtoeol(view->win);
2342         view->col = 0;
2343         view->curline = line;
2344         view->curtype = LINE_NONE;
2345         line->selected = FALSE;
2346         line->dirty = line->cleareol = 0;
2348         if (selected) {
2349                 set_view_attr(view, LINE_CURSOR);
2350                 line->selected = TRUE;
2351                 view->ops->select(view, line);
2352         }
2354         return view->ops->draw(view, line, lineno);
2357 static void
2358 redraw_view_dirty(struct view *view)
2360         bool dirty = FALSE;
2361         int lineno;
2363         for (lineno = 0; lineno < view->height; lineno++) {
2364                 if (view->offset + lineno >= view->lines)
2365                         break;
2366                 if (!view->line[view->offset + lineno].dirty)
2367                         continue;
2368                 dirty = TRUE;
2369                 if (!draw_view_line(view, lineno))
2370                         break;
2371         }
2373         if (!dirty)
2374                 return;
2375         wnoutrefresh(view->win);
2378 static void
2379 redraw_view_from(struct view *view, int lineno)
2381         assert(0 <= lineno && lineno < view->height);
2383         for (; lineno < view->height; lineno++) {
2384                 if (!draw_view_line(view, lineno))
2385                         break;
2386         }
2388         wnoutrefresh(view->win);
2391 static void
2392 redraw_view(struct view *view)
2394         werase(view->win);
2395         redraw_view_from(view, 0);
2399 static void
2400 update_view_title(struct view *view)
2402         char buf[SIZEOF_STR];
2403         char state[SIZEOF_STR];
2404         size_t bufpos = 0, statelen = 0;
2406         assert(view_is_displayed(view));
2408         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2409                 unsigned int view_lines = view->offset + view->height;
2410                 unsigned int lines = view->lines
2411                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2412                                    : 0;
2414                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2415                                    view->ops->type,
2416                                    view->lineno + 1,
2417                                    view->lines,
2418                                    lines);
2420         }
2422         if (view->pipe) {
2423                 time_t secs = time(NULL) - view->start_time;
2425                 /* Three git seconds are a long time ... */
2426                 if (secs > 2)
2427                         string_format_from(state, &statelen, " loading %lds", secs);
2428         }
2430         string_format_from(buf, &bufpos, "[%s]", view->name);
2431         if (*view->ref && bufpos < view->width) {
2432                 size_t refsize = strlen(view->ref);
2433                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2435                 if (minsize < view->width)
2436                         refsize = view->width - minsize + 7;
2437                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2438         }
2440         if (statelen && bufpos < view->width) {
2441                 string_format_from(buf, &bufpos, "%s", state);
2442         }
2444         if (view == display[current_view])
2445                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2446         else
2447                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2449         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2450         wclrtoeol(view->title);
2451         wnoutrefresh(view->title);
2454 static int
2455 apply_step(double step, int value)
2457         if (step >= 1)
2458                 return (int) step;
2459         value *= step + 0.01;
2460         return value ? value : 1;
2463 static void
2464 resize_display(void)
2466         int offset, i;
2467         struct view *base = display[0];
2468         struct view *view = display[1] ? display[1] : display[0];
2470         /* Setup window dimensions */
2472         getmaxyx(stdscr, base->height, base->width);
2474         /* Make room for the status window. */
2475         base->height -= 1;
2477         if (view != base) {
2478                 /* Horizontal split. */
2479                 view->width   = base->width;
2480                 view->height  = apply_step(opt_scale_split_view, base->height);
2481                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2482                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2483                 base->height -= view->height;
2485                 /* Make room for the title bar. */
2486                 view->height -= 1;
2487         }
2489         /* Make room for the title bar. */
2490         base->height -= 1;
2492         offset = 0;
2494         foreach_displayed_view (view, i) {
2495                 if (!view->win) {
2496                         view->win = newwin(view->height, 0, offset, 0);
2497                         if (!view->win)
2498                                 die("Failed to create %s view", view->name);
2500                         scrollok(view->win, FALSE);
2502                         view->title = newwin(1, 0, offset + view->height, 0);
2503                         if (!view->title)
2504                                 die("Failed to create title window");
2506                 } else {
2507                         wresize(view->win, view->height, view->width);
2508                         mvwin(view->win,   offset, 0);
2509                         mvwin(view->title, offset + view->height, 0);
2510                 }
2512                 offset += view->height + 1;
2513         }
2516 static void
2517 redraw_display(bool clear)
2519         struct view *view;
2520         int i;
2522         foreach_displayed_view (view, i) {
2523                 if (clear)
2524                         wclear(view->win);
2525                 redraw_view(view);
2526                 update_view_title(view);
2527         }
2530 static void
2531 toggle_enum_option_do(unsigned int *opt, const char *help,
2532                       const struct enum_map *map, size_t size)
2534         *opt = (*opt + 1) % size;
2535         redraw_display(FALSE);
2536         report("Displaying %s %s", enum_name(map[*opt]), help);
2539 #define toggle_enum_option(opt, help, map) \
2540         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2542 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2543 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2545 static void
2546 toggle_view_option(bool *option, const char *help)
2548         *option = !*option;
2549         redraw_display(FALSE);
2550         report("%sabling %s", *option ? "En" : "Dis", help);
2553 static void
2554 open_option_menu(void)
2556         const struct menu_item menu[] = {
2557                 { '.', "line numbers", &opt_line_number },
2558                 { 'D', "date display", &opt_date },
2559                 { 'A', "author display", &opt_author },
2560                 { 'g', "revision graph display", &opt_rev_graph },
2561                 { 'F', "reference display", &opt_show_refs },
2562                 { 0 }
2563         };
2564         int selected = 0;
2566         if (prompt_menu("Toggle option", menu, &selected)) {
2567                 if (menu[selected].data == &opt_date)
2568                         toggle_date();
2569                 else if (menu[selected].data == &opt_author)
2570                         toggle_author();
2571                 else
2572                         toggle_view_option(menu[selected].data, menu[selected].text);
2573         }
2576 static void
2577 maximize_view(struct view *view)
2579         memset(display, 0, sizeof(display));
2580         current_view = 0;
2581         display[current_view] = view;
2582         resize_display();
2583         redraw_display(FALSE);
2584         report("");
2588 /*
2589  * Navigation
2590  */
2592 static bool
2593 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2595         if (lineno >= view->lines)
2596                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2598         if (offset > lineno || offset + view->height <= lineno) {
2599                 unsigned long half = view->height / 2;
2601                 if (lineno > half)
2602                         offset = lineno - half;
2603                 else
2604                         offset = 0;
2605         }
2607         if (offset != view->offset || lineno != view->lineno) {
2608                 view->offset = offset;
2609                 view->lineno = lineno;
2610                 return TRUE;
2611         }
2613         return FALSE;
2616 /* Scrolling backend */
2617 static void
2618 do_scroll_view(struct view *view, int lines)
2620         bool redraw_current_line = FALSE;
2622         /* The rendering expects the new offset. */
2623         view->offset += lines;
2625         assert(0 <= view->offset && view->offset < view->lines);
2626         assert(lines);
2628         /* Move current line into the view. */
2629         if (view->lineno < view->offset) {
2630                 view->lineno = view->offset;
2631                 redraw_current_line = TRUE;
2632         } else if (view->lineno >= view->offset + view->height) {
2633                 view->lineno = view->offset + view->height - 1;
2634                 redraw_current_line = TRUE;
2635         }
2637         assert(view->offset <= view->lineno && view->lineno < view->lines);
2639         /* Redraw the whole screen if scrolling is pointless. */
2640         if (view->height < ABS(lines)) {
2641                 redraw_view(view);
2643         } else {
2644                 int line = lines > 0 ? view->height - lines : 0;
2645                 int end = line + ABS(lines);
2647                 scrollok(view->win, TRUE);
2648                 wscrl(view->win, lines);
2649                 scrollok(view->win, FALSE);
2651                 while (line < end && draw_view_line(view, line))
2652                         line++;
2654                 if (redraw_current_line)
2655                         draw_view_line(view, view->lineno - view->offset);
2656                 wnoutrefresh(view->win);
2657         }
2659         view->has_scrolled = TRUE;
2660         report("");
2663 /* Scroll frontend */
2664 static void
2665 scroll_view(struct view *view, enum request request)
2667         int lines = 1;
2669         assert(view_is_displayed(view));
2671         switch (request) {
2672         case REQ_SCROLL_LEFT:
2673                 if (view->yoffset == 0) {
2674                         report("Cannot scroll beyond the first column");
2675                         return;
2676                 }
2677                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2678                         view->yoffset = 0;
2679                 else
2680                         view->yoffset -= apply_step(opt_hscroll, view->width);
2681                 redraw_view_from(view, 0);
2682                 report("");
2683                 return;
2684         case REQ_SCROLL_RIGHT:
2685                 view->yoffset += apply_step(opt_hscroll, view->width);
2686                 redraw_view(view);
2687                 report("");
2688                 return;
2689         case REQ_SCROLL_PAGE_DOWN:
2690                 lines = view->height;
2691         case REQ_SCROLL_LINE_DOWN:
2692                 if (view->offset + lines > view->lines)
2693                         lines = view->lines - view->offset;
2695                 if (lines == 0 || view->offset + view->height >= view->lines) {
2696                         report("Cannot scroll beyond the last line");
2697                         return;
2698                 }
2699                 break;
2701         case REQ_SCROLL_PAGE_UP:
2702                 lines = view->height;
2703         case REQ_SCROLL_LINE_UP:
2704                 if (lines > view->offset)
2705                         lines = view->offset;
2707                 if (lines == 0) {
2708                         report("Cannot scroll beyond the first line");
2709                         return;
2710                 }
2712                 lines = -lines;
2713                 break;
2715         default:
2716                 die("request %d not handled in switch", request);
2717         }
2719         do_scroll_view(view, lines);
2722 /* Cursor moving */
2723 static void
2724 move_view(struct view *view, enum request request)
2726         int scroll_steps = 0;
2727         int steps;
2729         switch (request) {
2730         case REQ_MOVE_FIRST_LINE:
2731                 steps = -view->lineno;
2732                 break;
2734         case REQ_MOVE_LAST_LINE:
2735                 steps = view->lines - view->lineno - 1;
2736                 break;
2738         case REQ_MOVE_PAGE_UP:
2739                 steps = view->height > view->lineno
2740                       ? -view->lineno : -view->height;
2741                 break;
2743         case REQ_MOVE_PAGE_DOWN:
2744                 steps = view->lineno + view->height >= view->lines
2745                       ? view->lines - view->lineno - 1 : view->height;
2746                 break;
2748         case REQ_MOVE_UP:
2749                 steps = -1;
2750                 break;
2752         case REQ_MOVE_DOWN:
2753                 steps = 1;
2754                 break;
2756         default:
2757                 die("request %d not handled in switch", request);
2758         }
2760         if (steps <= 0 && view->lineno == 0) {
2761                 report("Cannot move beyond the first line");
2762                 return;
2764         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2765                 report("Cannot move beyond the last line");
2766                 return;
2767         }
2769         /* Move the current line */
2770         view->lineno += steps;
2771         assert(0 <= view->lineno && view->lineno < view->lines);
2773         /* Check whether the view needs to be scrolled */
2774         if (view->lineno < view->offset ||
2775             view->lineno >= view->offset + view->height) {
2776                 scroll_steps = steps;
2777                 if (steps < 0 && -steps > view->offset) {
2778                         scroll_steps = -view->offset;
2780                 } else if (steps > 0) {
2781                         if (view->lineno == view->lines - 1 &&
2782                             view->lines > view->height) {
2783                                 scroll_steps = view->lines - view->offset - 1;
2784                                 if (scroll_steps >= view->height)
2785                                         scroll_steps -= view->height - 1;
2786                         }
2787                 }
2788         }
2790         if (!view_is_displayed(view)) {
2791                 view->offset += scroll_steps;
2792                 assert(0 <= view->offset && view->offset < view->lines);
2793                 view->ops->select(view, &view->line[view->lineno]);
2794                 return;
2795         }
2797         /* Repaint the old "current" line if we be scrolling */
2798         if (ABS(steps) < view->height)
2799                 draw_view_line(view, view->lineno - steps - view->offset);
2801         if (scroll_steps) {
2802                 do_scroll_view(view, scroll_steps);
2803                 return;
2804         }
2806         /* Draw the current line */
2807         draw_view_line(view, view->lineno - view->offset);
2809         wnoutrefresh(view->win);
2810         report("");
2814 /*
2815  * Searching
2816  */
2818 static void search_view(struct view *view, enum request request);
2820 static bool
2821 grep_text(struct view *view, const char *text[])
2823         regmatch_t pmatch;
2824         size_t i;
2826         for (i = 0; text[i]; i++)
2827                 if (*text[i] &&
2828                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2829                         return TRUE;
2830         return FALSE;
2833 static void
2834 select_view_line(struct view *view, unsigned long lineno)
2836         unsigned long old_lineno = view->lineno;
2837         unsigned long old_offset = view->offset;
2839         if (goto_view_line(view, view->offset, lineno)) {
2840                 if (view_is_displayed(view)) {
2841                         if (old_offset != view->offset) {
2842                                 redraw_view(view);
2843                         } else {
2844                                 draw_view_line(view, old_lineno - view->offset);
2845                                 draw_view_line(view, view->lineno - view->offset);
2846                                 wnoutrefresh(view->win);
2847                         }
2848                 } else {
2849                         view->ops->select(view, &view->line[view->lineno]);
2850                 }
2851         }
2854 static void
2855 find_next(struct view *view, enum request request)
2857         unsigned long lineno = view->lineno;
2858         int direction;
2860         if (!*view->grep) {
2861                 if (!*opt_search)
2862                         report("No previous search");
2863                 else
2864                         search_view(view, request);
2865                 return;
2866         }
2868         switch (request) {
2869         case REQ_SEARCH:
2870         case REQ_FIND_NEXT:
2871                 direction = 1;
2872                 break;
2874         case REQ_SEARCH_BACK:
2875         case REQ_FIND_PREV:
2876                 direction = -1;
2877                 break;
2879         default:
2880                 return;
2881         }
2883         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2884                 lineno += direction;
2886         /* Note, lineno is unsigned long so will wrap around in which case it
2887          * will become bigger than view->lines. */
2888         for (; lineno < view->lines; lineno += direction) {
2889                 if (view->ops->grep(view, &view->line[lineno])) {
2890                         select_view_line(view, lineno);
2891                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2892                         return;
2893                 }
2894         }
2896         report("No match found for '%s'", view->grep);
2899 static void
2900 search_view(struct view *view, enum request request)
2902         int regex_err;
2904         if (view->regex) {
2905                 regfree(view->regex);
2906                 *view->grep = 0;
2907         } else {
2908                 view->regex = calloc(1, sizeof(*view->regex));
2909                 if (!view->regex)
2910                         return;
2911         }
2913         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2914         if (regex_err != 0) {
2915                 char buf[SIZEOF_STR] = "unknown error";
2917                 regerror(regex_err, view->regex, buf, sizeof(buf));
2918                 report("Search failed: %s", buf);
2919                 return;
2920         }
2922         string_copy(view->grep, opt_search);
2924         find_next(view, request);
2927 /*
2928  * Incremental updating
2929  */
2931 static void
2932 reset_view(struct view *view)
2934         int i;
2936         for (i = 0; i < view->lines; i++)
2937                 free(view->line[i].data);
2938         free(view->line);
2940         view->p_offset = view->offset;
2941         view->p_yoffset = view->yoffset;
2942         view->p_lineno = view->lineno;
2944         view->line = NULL;
2945         view->offset = 0;
2946         view->yoffset = 0;
2947         view->lines  = 0;
2948         view->lineno = 0;
2949         view->vid[0] = 0;
2950         view->update_secs = 0;
2953 static void
2954 free_argv(const char *argv[])
2956         int argc;
2958         for (argc = 0; argv[argc]; argc++)
2959                 free((void *) argv[argc]);
2962 static const char *
2963 format_arg(const char *name)
2965         static struct {
2966                 const char *name;
2967                 size_t namelen;
2968                 const char *value;
2969                 const char *value_if_empty;
2970         } vars[] = {
2971 #define FORMAT_VAR(name, value, value_if_empty) \
2972         { name, STRING_SIZE(name), value, value_if_empty }
2973                 FORMAT_VAR("%(directory)",      opt_path,       ""),
2974                 FORMAT_VAR("%(file)",           opt_file,       ""),
2975                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
2976                 FORMAT_VAR("%(head)",           ref_head,       ""),
2977                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
2978                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
2979         };
2980         int i;
2982         for (i = 0; i < ARRAY_SIZE(vars); i++)
2983                 if (!strncmp(name, vars[i].name, vars[i].namelen))
2984                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2986         return NULL;
2988 static bool
2989 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2991         char buf[SIZEOF_STR];
2992         int argc;
2993         bool noreplace = flags == FORMAT_NONE;
2995         free_argv(dst_argv);
2997         for (argc = 0; src_argv[argc]; argc++) {
2998                 const char *arg = src_argv[argc];
2999                 size_t bufpos = 0;
3001                 while (arg) {
3002                         char *next = strstr(arg, "%(");
3003                         int len = next - arg;
3004                         const char *value;
3006                         if (!next || noreplace) {
3007                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
3008                                         noreplace = TRUE;
3009                                 len = strlen(arg);
3010                                 value = "";
3012                         } else {
3013                                 value = format_arg(next);
3015                                 if (!value) {
3016                                         report("Unknown replacement: `%s`", next);
3017                                         return FALSE;
3018                                 }
3019                         }
3021                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3022                                 return FALSE;
3024                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3025                 }
3027                 dst_argv[argc] = strdup(buf);
3028                 if (!dst_argv[argc])
3029                         break;
3030         }
3032         dst_argv[argc] = NULL;
3034         return src_argv[argc] == NULL;
3037 static bool
3038 restore_view_position(struct view *view)
3040         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3041                 return FALSE;
3043         /* Changing the view position cancels the restoring. */
3044         /* FIXME: Changing back to the first line is not detected. */
3045         if (view->offset != 0 || view->lineno != 0) {
3046                 view->p_restore = FALSE;
3047                 return FALSE;
3048         }
3050         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3051             view_is_displayed(view))
3052                 werase(view->win);
3054         view->yoffset = view->p_yoffset;
3055         view->p_restore = FALSE;
3057         return TRUE;
3060 static void
3061 end_update(struct view *view, bool force)
3063         if (!view->pipe)
3064                 return;
3065         while (!view->ops->read(view, NULL))
3066                 if (!force)
3067                         return;
3068         set_nonblocking_input(FALSE);
3069         if (force)
3070                 kill_io(view->pipe);
3071         done_io(view->pipe);
3072         view->pipe = NULL;
3075 static void
3076 setup_update(struct view *view, const char *vid)
3078         set_nonblocking_input(TRUE);
3079         reset_view(view);
3080         string_copy_rev(view->vid, vid);
3081         view->pipe = &view->io;
3082         view->start_time = time(NULL);
3085 static bool
3086 prepare_update(struct view *view, const char *argv[], const char *dir,
3087                enum format_flags flags)
3089         if (view->pipe)
3090                 end_update(view, TRUE);
3091         return init_io_rd(&view->io, argv, dir, flags);
3094 static bool
3095 prepare_update_file(struct view *view, const char *name)
3097         if (view->pipe)
3098                 end_update(view, TRUE);
3099         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3102 static bool
3103 begin_update(struct view *view, bool refresh)
3105         if (view->pipe)
3106                 end_update(view, TRUE);
3108         if (!refresh) {
3109                 if (view->ops->prepare) {
3110                         if (!view->ops->prepare(view))
3111                                 return FALSE;
3112                 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3113                         return FALSE;
3114                 }
3116                 /* Put the current ref_* value to the view title ref
3117                  * member. This is needed by the blob view. Most other
3118                  * views sets it automatically after loading because the
3119                  * first line is a commit line. */
3120                 string_copy_rev(view->ref, view->id);
3121         }
3123         if (!start_io(&view->io))
3124                 return FALSE;
3126         setup_update(view, view->id);
3128         return TRUE;
3131 static bool
3132 update_view(struct view *view)
3134         char out_buffer[BUFSIZ * 2];
3135         char *line;
3136         /* Clear the view and redraw everything since the tree sorting
3137          * might have rearranged things. */
3138         bool redraw = view->lines == 0;
3139         bool can_read = TRUE;
3141         if (!view->pipe)
3142                 return TRUE;
3144         if (!io_can_read(view->pipe)) {
3145                 if (view->lines == 0 && view_is_displayed(view)) {
3146                         time_t secs = time(NULL) - view->start_time;
3148                         if (secs > 1 && secs > view->update_secs) {
3149                                 if (view->update_secs == 0)
3150                                         redraw_view(view);
3151                                 update_view_title(view);
3152                                 view->update_secs = secs;
3153                         }
3154                 }
3155                 return TRUE;
3156         }
3158         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3159                 if (opt_iconv_in != ICONV_NONE) {
3160                         ICONV_CONST char *inbuf = line;
3161                         size_t inlen = strlen(line) + 1;
3163                         char *outbuf = out_buffer;
3164                         size_t outlen = sizeof(out_buffer);
3166                         size_t ret;
3168                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3169                         if (ret != (size_t) -1)
3170                                 line = out_buffer;
3171                 }
3173                 if (!view->ops->read(view, line)) {
3174                         report("Allocation failure");
3175                         end_update(view, TRUE);
3176                         return FALSE;
3177                 }
3178         }
3180         {
3181                 unsigned long lines = view->lines;
3182                 int digits;
3184                 for (digits = 0; lines; digits++)
3185                         lines /= 10;
3187                 /* Keep the displayed view in sync with line number scaling. */
3188                 if (digits != view->digits) {
3189                         view->digits = digits;
3190                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3191                                 redraw = TRUE;
3192                 }
3193         }
3195         if (io_error(view->pipe)) {
3196                 report("Failed to read: %s", io_strerror(view->pipe));
3197                 end_update(view, TRUE);
3199         } else if (io_eof(view->pipe)) {
3200                 report("");
3201                 end_update(view, FALSE);
3202         }
3204         if (restore_view_position(view))
3205                 redraw = TRUE;
3207         if (!view_is_displayed(view))
3208                 return TRUE;
3210         if (redraw)
3211                 redraw_view_from(view, 0);
3212         else
3213                 redraw_view_dirty(view);
3215         /* Update the title _after_ the redraw so that if the redraw picks up a
3216          * commit reference in view->ref it'll be available here. */
3217         update_view_title(view);
3218         return TRUE;
3221 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3223 static struct line *
3224 add_line_data(struct view *view, void *data, enum line_type type)
3226         struct line *line;
3228         if (!realloc_lines(&view->line, view->lines, 1))
3229                 return NULL;
3231         line = &view->line[view->lines++];
3232         memset(line, 0, sizeof(*line));
3233         line->type = type;
3234         line->data = data;
3235         line->dirty = 1;
3237         return line;
3240 static struct line *
3241 add_line_text(struct view *view, const char *text, enum line_type type)
3243         char *data = text ? strdup(text) : NULL;
3245         return data ? add_line_data(view, data, type) : NULL;
3248 static struct line *
3249 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3251         char buf[SIZEOF_STR];
3252         va_list args;
3254         va_start(args, fmt);
3255         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3256                 buf[0] = 0;
3257         va_end(args);
3259         return buf[0] ? add_line_text(view, buf, type) : NULL;
3262 /*
3263  * View opening
3264  */
3266 enum open_flags {
3267         OPEN_DEFAULT = 0,       /* Use default view switching. */
3268         OPEN_SPLIT = 1,         /* Split current view. */
3269         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3270         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3271         OPEN_PREPARED = 32,     /* Open already prepared command. */
3272 };
3274 static void
3275 open_view(struct view *prev, enum request request, enum open_flags flags)
3277         bool split = !!(flags & OPEN_SPLIT);
3278         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3279         bool nomaximize = !!(flags & OPEN_REFRESH);
3280         struct view *view = VIEW(request);
3281         int nviews = displayed_views();
3282         struct view *base_view = display[0];
3284         if (view == prev && nviews == 1 && !reload) {
3285                 report("Already in %s view", view->name);
3286                 return;
3287         }
3289         if (view->git_dir && !opt_git_dir[0]) {
3290                 report("The %s view is disabled in pager view", view->name);
3291                 return;
3292         }
3294         if (split) {
3295                 display[1] = view;
3296                 current_view = 1;
3297         } else if (!nomaximize) {
3298                 /* Maximize the current view. */
3299                 memset(display, 0, sizeof(display));
3300                 current_view = 0;
3301                 display[current_view] = view;
3302         }
3304         /* No parent signals that this is the first loaded view. */
3305         if (prev && view != prev) {
3306                 view->parent = prev;
3307         }
3309         /* Resize the view when switching between split- and full-screen,
3310          * or when switching between two different full-screen views. */
3311         if (nviews != displayed_views() ||
3312             (nviews == 1 && base_view != display[0]))
3313                 resize_display();
3315         if (view->ops->open) {
3316                 if (view->pipe)
3317                         end_update(view, TRUE);
3318                 if (!view->ops->open(view)) {
3319                         report("Failed to load %s view", view->name);
3320                         return;
3321                 }
3322                 restore_view_position(view);
3324         } else if ((reload || strcmp(view->vid, view->id)) &&
3325                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3326                 report("Failed to load %s view", view->name);
3327                 return;
3328         }
3330         if (split && prev->lineno - prev->offset >= prev->height) {
3331                 /* Take the title line into account. */
3332                 int lines = prev->lineno - prev->offset - prev->height + 1;
3334                 /* Scroll the view that was split if the current line is
3335                  * outside the new limited view. */
3336                 do_scroll_view(prev, lines);
3337         }
3339         if (prev && view != prev && split && view_is_displayed(prev)) {
3340                 /* "Blur" the previous view. */
3341                 update_view_title(prev);
3342         }
3344         if (view->pipe && view->lines == 0) {
3345                 /* Clear the old view and let the incremental updating refill
3346                  * the screen. */
3347                 werase(view->win);
3348                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3349                 report("");
3350         } else if (view_is_displayed(view)) {
3351                 redraw_view(view);
3352                 report("");
3353         }
3356 static void
3357 open_external_viewer(const char *argv[], const char *dir)
3359         def_prog_mode();           /* save current tty modes */
3360         endwin();                  /* restore original tty modes */
3361         run_io_fg(argv, dir);
3362         fprintf(stderr, "Press Enter to continue");
3363         getc(opt_tty);
3364         reset_prog_mode();
3365         redraw_display(TRUE);
3368 static void
3369 open_mergetool(const char *file)
3371         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3373         open_external_viewer(mergetool_argv, opt_cdup);
3376 static void
3377 open_editor(const char *file)
3379         const char *editor_argv[] = { "vi", file, NULL };
3380         const char *editor;
3382         editor = getenv("GIT_EDITOR");
3383         if (!editor && *opt_editor)
3384                 editor = opt_editor;
3385         if (!editor)
3386                 editor = getenv("VISUAL");
3387         if (!editor)
3388                 editor = getenv("EDITOR");
3389         if (!editor)
3390                 editor = "vi";
3392         editor_argv[0] = editor;
3393         open_external_viewer(editor_argv, opt_cdup);
3396 static void
3397 open_run_request(enum request request)
3399         struct run_request *req = get_run_request(request);
3400         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3402         if (!req) {
3403                 report("Unknown run request");
3404                 return;
3405         }
3407         if (format_argv(argv, req->argv, FORMAT_ALL))
3408                 open_external_viewer(argv, NULL);
3409         free_argv(argv);
3412 /*
3413  * User request switch noodle
3414  */
3416 static int
3417 view_driver(struct view *view, enum request request)
3419         int i;
3421         if (request == REQ_NONE)
3422                 return TRUE;
3424         if (request > REQ_NONE) {
3425                 open_run_request(request);
3426                 /* FIXME: When all views can refresh always do this. */
3427                 if (view == VIEW(REQ_VIEW_STATUS) ||
3428                     view == VIEW(REQ_VIEW_MAIN) ||
3429                     view == VIEW(REQ_VIEW_LOG) ||
3430                     view == VIEW(REQ_VIEW_BRANCH) ||
3431                     view == VIEW(REQ_VIEW_STAGE))
3432                         request = REQ_REFRESH;
3433                 else
3434                         return TRUE;
3435         }
3437         if (view && view->lines) {
3438                 request = view->ops->request(view, request, &view->line[view->lineno]);
3439                 if (request == REQ_NONE)
3440                         return TRUE;
3441         }
3443         switch (request) {
3444         case REQ_MOVE_UP:
3445         case REQ_MOVE_DOWN:
3446         case REQ_MOVE_PAGE_UP:
3447         case REQ_MOVE_PAGE_DOWN:
3448         case REQ_MOVE_FIRST_LINE:
3449         case REQ_MOVE_LAST_LINE:
3450                 move_view(view, request);
3451                 break;
3453         case REQ_SCROLL_LEFT:
3454         case REQ_SCROLL_RIGHT:
3455         case REQ_SCROLL_LINE_DOWN:
3456         case REQ_SCROLL_LINE_UP:
3457         case REQ_SCROLL_PAGE_DOWN:
3458         case REQ_SCROLL_PAGE_UP:
3459                 scroll_view(view, request);
3460                 break;
3462         case REQ_VIEW_BLAME:
3463                 if (!opt_file[0]) {
3464                         report("No file chosen, press %s to open tree view",
3465                                get_key(view->keymap, REQ_VIEW_TREE));
3466                         break;
3467                 }
3468                 open_view(view, request, OPEN_DEFAULT);
3469                 break;
3471         case REQ_VIEW_BLOB:
3472                 if (!ref_blob[0]) {
3473                         report("No file chosen, press %s to open tree view",
3474                                get_key(view->keymap, REQ_VIEW_TREE));
3475                         break;
3476                 }
3477                 open_view(view, request, OPEN_DEFAULT);
3478                 break;
3480         case REQ_VIEW_PAGER:
3481                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3482                         report("No pager content, press %s to run command from prompt",
3483                                get_key(view->keymap, REQ_PROMPT));
3484                         break;
3485                 }
3486                 open_view(view, request, OPEN_DEFAULT);
3487                 break;
3489         case REQ_VIEW_STAGE:
3490                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3491                         report("No stage content, press %s to open the status view and choose file",
3492                                get_key(view->keymap, REQ_VIEW_STATUS));
3493                         break;
3494                 }
3495                 open_view(view, request, OPEN_DEFAULT);
3496                 break;
3498         case REQ_VIEW_STATUS:
3499                 if (opt_is_inside_work_tree == FALSE) {
3500                         report("The status view requires a working tree");
3501                         break;
3502                 }
3503                 open_view(view, request, OPEN_DEFAULT);
3504                 break;
3506         case REQ_VIEW_MAIN:
3507         case REQ_VIEW_DIFF:
3508         case REQ_VIEW_LOG:
3509         case REQ_VIEW_TREE:
3510         case REQ_VIEW_HELP:
3511         case REQ_VIEW_BRANCH:
3512                 open_view(view, request, OPEN_DEFAULT);
3513                 break;
3515         case REQ_NEXT:
3516         case REQ_PREVIOUS:
3517                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3519                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3520                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3521                    (view == VIEW(REQ_VIEW_DIFF) &&
3522                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3523                    (view == VIEW(REQ_VIEW_STAGE) &&
3524                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3525                    (view == VIEW(REQ_VIEW_BLOB) &&
3526                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3527                    (view == VIEW(REQ_VIEW_MAIN) &&
3528                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3529                         int line;
3531                         view = view->parent;
3532                         line = view->lineno;
3533                         move_view(view, request);
3534                         if (view_is_displayed(view))
3535                                 update_view_title(view);
3536                         if (line != view->lineno)
3537                                 view->ops->request(view, REQ_ENTER,
3538                                                    &view->line[view->lineno]);
3540                 } else {
3541                         move_view(view, request);
3542                 }
3543                 break;
3545         case REQ_VIEW_NEXT:
3546         {
3547                 int nviews = displayed_views();
3548                 int next_view = (current_view + 1) % nviews;
3550                 if (next_view == current_view) {
3551                         report("Only one view is displayed");
3552                         break;
3553                 }
3555                 current_view = next_view;
3556                 /* Blur out the title of the previous view. */
3557                 update_view_title(view);
3558                 report("");
3559                 break;
3560         }
3561         case REQ_REFRESH:
3562                 report("Refreshing is not yet supported for the %s view", view->name);
3563                 break;
3565         case REQ_MAXIMIZE:
3566                 if (displayed_views() == 2)
3567                         maximize_view(view);
3568                 break;
3570         case REQ_OPTIONS:
3571                 open_option_menu();
3572                 break;
3574         case REQ_TOGGLE_LINENO:
3575                 toggle_view_option(&opt_line_number, "line numbers");
3576                 break;
3578         case REQ_TOGGLE_DATE:
3579                 toggle_date();
3580                 break;
3582         case REQ_TOGGLE_AUTHOR:
3583                 toggle_author();
3584                 break;
3586         case REQ_TOGGLE_REV_GRAPH:
3587                 toggle_view_option(&opt_rev_graph, "revision graph display");
3588                 break;
3590         case REQ_TOGGLE_REFS:
3591                 toggle_view_option(&opt_show_refs, "reference display");
3592                 break;
3594         case REQ_TOGGLE_SORT_FIELD:
3595         case REQ_TOGGLE_SORT_ORDER:
3596                 report("Sorting is not yet supported for the %s view", view->name);
3597                 break;
3599         case REQ_SEARCH:
3600         case REQ_SEARCH_BACK:
3601                 search_view(view, request);
3602                 break;
3604         case REQ_FIND_NEXT:
3605         case REQ_FIND_PREV:
3606                 find_next(view, request);
3607                 break;
3609         case REQ_STOP_LOADING:
3610                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3611                         view = &views[i];
3612                         if (view->pipe)
3613                                 report("Stopped loading the %s view", view->name),
3614                         end_update(view, TRUE);
3615                 }
3616                 break;
3618         case REQ_SHOW_VERSION:
3619                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3620                 return TRUE;
3622         case REQ_SCREEN_REDRAW:
3623                 redraw_display(TRUE);
3624                 break;
3626         case REQ_EDIT:
3627                 report("Nothing to edit");
3628                 break;
3630         case REQ_ENTER:
3631                 report("Nothing to enter");
3632                 break;
3634         case REQ_VIEW_CLOSE:
3635                 /* XXX: Mark closed views by letting view->parent point to the
3636                  * view itself. Parents to closed view should never be
3637                  * followed. */
3638                 if (view->parent &&
3639                     view->parent->parent != view->parent) {
3640                         maximize_view(view->parent);
3641                         view->parent = view;
3642                         break;
3643                 }
3644                 /* Fall-through */
3645         case REQ_QUIT:
3646                 return FALSE;
3648         default:
3649                 report("Unknown key, press %s for help",
3650                        get_key(view->keymap, REQ_VIEW_HELP));
3651                 return TRUE;
3652         }
3654         return TRUE;
3658 /*
3659  * View backend utilities
3660  */
3662 enum sort_field {
3663         ORDERBY_NAME,
3664         ORDERBY_DATE,
3665         ORDERBY_AUTHOR,
3666 };
3668 struct sort_state {
3669         const enum sort_field *fields;
3670         size_t size, current;
3671         bool reverse;
3672 };
3674 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3675 #define get_sort_field(state) ((state).fields[(state).current])
3676 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3678 static void
3679 sort_view(struct view *view, enum request request, struct sort_state *state,
3680           int (*compare)(const void *, const void *))
3682         switch (request) {
3683         case REQ_TOGGLE_SORT_FIELD:
3684                 state->current = (state->current + 1) % state->size;
3685                 break;
3687         case REQ_TOGGLE_SORT_ORDER:
3688                 state->reverse = !state->reverse;
3689                 break;
3690         default:
3691                 die("Not a sort request");
3692         }
3694         qsort(view->line, view->lines, sizeof(*view->line), compare);
3695         redraw_view(view);
3698 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3700 /* Small author cache to reduce memory consumption. It uses binary
3701  * search to lookup or find place to position new entries. No entries
3702  * are ever freed. */
3703 static const char *
3704 get_author(const char *name)
3706         static const char **authors;
3707         static size_t authors_size;
3708         int from = 0, to = authors_size - 1;
3710         while (from <= to) {
3711                 size_t pos = (to + from) / 2;
3712                 int cmp = strcmp(name, authors[pos]);
3714                 if (!cmp)
3715                         return authors[pos];
3717                 if (cmp < 0)
3718                         to = pos - 1;
3719                 else
3720                         from = pos + 1;
3721         }
3723         if (!realloc_authors(&authors, authors_size, 1))
3724                 return NULL;
3725         name = strdup(name);
3726         if (!name)
3727                 return NULL;
3729         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3730         authors[from] = name;
3731         authors_size++;
3733         return name;
3736 static void
3737 parse_timezone(time_t *time, const char *zone)
3739         long tz;
3741         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3742         tz += ('0' - zone[2]) * 60 * 60;
3743         tz += ('0' - zone[3]) * 60;
3744         tz += ('0' - zone[4]);
3746         if (zone[0] == '-')
3747                 tz = -tz;
3749         *time -= tz;
3752 /* Parse author lines where the name may be empty:
3753  *      author  <email@address.tld> 1138474660 +0100
3754  */
3755 static void
3756 parse_author_line(char *ident, const char **author, time_t *time)
3758         char *nameend = strchr(ident, '<');
3759         char *emailend = strchr(ident, '>');
3761         if (nameend && emailend)
3762                 *nameend = *emailend = 0;
3763         ident = chomp_string(ident);
3764         if (!*ident) {
3765                 if (nameend)
3766                         ident = chomp_string(nameend + 1);
3767                 if (!*ident)
3768                         ident = "Unknown";
3769         }
3771         *author = get_author(ident);
3773         /* Parse epoch and timezone */
3774         if (emailend && emailend[1] == ' ') {
3775                 char *secs = emailend + 2;
3776                 char *zone = strchr(secs, ' ');
3778                 *time = (time_t) atol(secs);
3780                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3781                         parse_timezone(time, zone + 1);
3782         }
3785 static bool
3786 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3788         char rev[SIZEOF_REV];
3789         const char *revlist_argv[] = {
3790                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3791         };
3792         struct menu_item *items;
3793         char text[SIZEOF_STR];
3794         bool ok = TRUE;
3795         int i;
3797         items = calloc(*parents + 1, sizeof(*items));
3798         if (!items)
3799                 return FALSE;
3801         for (i = 0; i < *parents; i++) {
3802                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3803                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3804                     !(items[i].text = strdup(text))) {
3805                         ok = FALSE;
3806                         break;
3807                 }
3808         }
3810         if (ok) {
3811                 *parents = 0;
3812                 ok = prompt_menu("Select parent", items, parents);
3813         }
3814         for (i = 0; items[i].text; i++)
3815                 free((char *) items[i].text);
3816         free(items);
3817         return ok;
3820 static bool
3821 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3823         char buf[SIZEOF_STR * 4];
3824         const char *revlist_argv[] = {
3825                 "git", "log", "--no-color", "-1",
3826                         "--pretty=format:%P", id, "--", path, NULL
3827         };
3828         int parents;
3830         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3831             (parents = strlen(buf) / 40) < 0) {
3832                 report("Failed to get parent information");
3833                 return FALSE;
3835         } else if (parents == 0) {
3836                 if (path)
3837                         report("Path '%s' does not exist in the parent", path);
3838                 else
3839                         report("The selected commit has no parents");
3840                 return FALSE;
3841         }
3843         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3844                 return FALSE;
3846         string_copy_rev(rev, &buf[41 * parents]);
3847         return TRUE;
3850 /*
3851  * Pager backend
3852  */
3854 static bool
3855 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3857         char text[SIZEOF_STR];
3859         if (opt_line_number && draw_lineno(view, lineno))
3860                 return TRUE;
3862         string_expand(text, sizeof(text), line->data, opt_tab_size);
3863         draw_text(view, line->type, text, TRUE);
3864         return TRUE;
3867 static bool
3868 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3870         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3871         char ref[SIZEOF_STR];
3873         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3874                 return TRUE;
3876         /* This is the only fatal call, since it can "corrupt" the buffer. */
3877         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3878                 return FALSE;
3880         return TRUE;
3883 static void
3884 add_pager_refs(struct view *view, struct line *line)
3886         char buf[SIZEOF_STR];
3887         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3888         struct ref_list *list;
3889         size_t bufpos = 0, i;
3890         const char *sep = "Refs: ";
3891         bool is_tag = FALSE;
3893         assert(line->type == LINE_COMMIT);
3895         list = get_ref_list(commit_id);
3896         if (!list) {
3897                 if (view == VIEW(REQ_VIEW_DIFF))
3898                         goto try_add_describe_ref;
3899                 return;
3900         }
3902         for (i = 0; i < list->size; i++) {
3903                 struct ref *ref = list->refs[i];
3904                 const char *fmt = ref->tag    ? "%s[%s]" :
3905                                   ref->remote ? "%s<%s>" : "%s%s";
3907                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3908                         return;
3909                 sep = ", ";
3910                 if (ref->tag)
3911                         is_tag = TRUE;
3912         }
3914         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3915 try_add_describe_ref:
3916                 /* Add <tag>-g<commit_id> "fake" reference. */
3917                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3918                         return;
3919         }
3921         if (bufpos == 0)
3922                 return;
3924         add_line_text(view, buf, LINE_PP_REFS);
3927 static bool
3928 pager_read(struct view *view, char *data)
3930         struct line *line;
3932         if (!data)
3933                 return TRUE;
3935         line = add_line_text(view, data, get_line_type(data));
3936         if (!line)
3937                 return FALSE;
3939         if (line->type == LINE_COMMIT &&
3940             (view == VIEW(REQ_VIEW_DIFF) ||
3941              view == VIEW(REQ_VIEW_LOG)))
3942                 add_pager_refs(view, line);
3944         return TRUE;
3947 static enum request
3948 pager_request(struct view *view, enum request request, struct line *line)
3950         int split = 0;
3952         if (request != REQ_ENTER)
3953                 return request;
3955         if (line->type == LINE_COMMIT &&
3956            (view == VIEW(REQ_VIEW_LOG) ||
3957             view == VIEW(REQ_VIEW_PAGER))) {
3958                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3959                 split = 1;
3960         }
3962         /* Always scroll the view even if it was split. That way
3963          * you can use Enter to scroll through the log view and
3964          * split open each commit diff. */
3965         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3967         /* FIXME: A minor workaround. Scrolling the view will call report("")
3968          * but if we are scrolling a non-current view this won't properly
3969          * update the view title. */
3970         if (split)
3971                 update_view_title(view);
3973         return REQ_NONE;
3976 static bool
3977 pager_grep(struct view *view, struct line *line)
3979         const char *text[] = { line->data, NULL };
3981         return grep_text(view, text);
3984 static void
3985 pager_select(struct view *view, struct line *line)
3987         if (line->type == LINE_COMMIT) {
3988                 char *text = (char *)line->data + STRING_SIZE("commit ");
3990                 if (view != VIEW(REQ_VIEW_PAGER))
3991                         string_copy_rev(view->ref, text);
3992                 string_copy_rev(ref_commit, text);
3993         }
3996 static struct view_ops pager_ops = {
3997         "line",
3998         NULL,
3999         NULL,
4000         pager_read,
4001         pager_draw,
4002         pager_request,
4003         pager_grep,
4004         pager_select,
4005 };
4007 static const char *log_argv[SIZEOF_ARG] = {
4008         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4009 };
4011 static enum request
4012 log_request(struct view *view, enum request request, struct line *line)
4014         switch (request) {
4015         case REQ_REFRESH:
4016                 load_refs();
4017                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4018                 return REQ_NONE;
4019         default:
4020                 return pager_request(view, request, line);
4021         }
4024 static struct view_ops log_ops = {
4025         "line",
4026         log_argv,
4027         NULL,
4028         pager_read,
4029         pager_draw,
4030         log_request,
4031         pager_grep,
4032         pager_select,
4033 };
4035 static const char *diff_argv[SIZEOF_ARG] = {
4036         "git", "show", "--pretty=fuller", "--no-color", "--root",
4037                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4038 };
4040 static struct view_ops diff_ops = {
4041         "line",
4042         diff_argv,
4043         NULL,
4044         pager_read,
4045         pager_draw,
4046         pager_request,
4047         pager_grep,
4048         pager_select,
4049 };
4051 /*
4052  * Help backend
4053  */
4055 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4057 static bool
4058 help_open_keymap_title(struct view *view, enum keymap keymap)
4060         struct line *line;
4062         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4063                                help_keymap_hidden[keymap] ? '+' : '-',
4064                                enum_name(keymap_table[keymap]));
4065         if (line)
4066                 line->other = keymap;
4068         return help_keymap_hidden[keymap];
4071 static void
4072 help_open_keymap(struct view *view, enum keymap keymap)
4074         const char *group = NULL;
4075         char buf[SIZEOF_STR];
4076         size_t bufpos;
4077         bool add_title = TRUE;
4078         int i;
4080         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4081                 const char *key = NULL;
4083                 if (req_info[i].request == REQ_NONE)
4084                         continue;
4086                 if (!req_info[i].request) {
4087                         group = req_info[i].help;
4088                         continue;
4089                 }
4091                 key = get_keys(keymap, req_info[i].request, TRUE);
4092                 if (!key || !*key)
4093                         continue;
4095                 if (add_title && help_open_keymap_title(view, keymap))
4096                         return;
4097                 add_title = FALSE;
4099                 if (group) {
4100                         add_line_text(view, group, LINE_HELP_GROUP);
4101                         group = NULL;
4102                 }
4104                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4105                                 enum_name(req_info[i]), req_info[i].help);
4106         }
4108         group = "External commands:";
4110         for (i = 0; i < run_requests; i++) {
4111                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4112                 const char *key;
4113                 int argc;
4115                 if (!req || req->keymap != keymap)
4116                         continue;
4118                 key = get_key_name(req->key);
4119                 if (!*key)
4120                         key = "(no key defined)";
4122                 if (add_title && help_open_keymap_title(view, keymap))
4123                         return;
4124                 if (group) {
4125                         add_line_text(view, group, LINE_HELP_GROUP);
4126                         group = NULL;
4127                 }
4129                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4130                         if (!string_format_from(buf, &bufpos, "%s%s",
4131                                                 argc ? " " : "", req->argv[argc]))
4132                                 return;
4134                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4135         }
4138 static bool
4139 help_open(struct view *view)
4141         enum keymap keymap;
4143         reset_view(view);
4144         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4145         add_line_text(view, "", LINE_DEFAULT);
4147         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4148                 help_open_keymap(view, keymap);
4150         return TRUE;
4153 static enum request
4154 help_request(struct view *view, enum request request, struct line *line)
4156         switch (request) {
4157         case REQ_ENTER:
4158                 if (line->type == LINE_HELP_KEYMAP) {
4159                         help_keymap_hidden[line->other] =
4160                                 !help_keymap_hidden[line->other];
4161                         view->p_restore = TRUE;
4162                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4163                 }
4165                 return REQ_NONE;
4166         default:
4167                 return pager_request(view, request, line);
4168         }
4171 static struct view_ops help_ops = {
4172         "line",
4173         NULL,
4174         help_open,
4175         NULL,
4176         pager_draw,
4177         help_request,
4178         pager_grep,
4179         pager_select,
4180 };
4183 /*
4184  * Tree backend
4185  */
4187 struct tree_stack_entry {
4188         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4189         unsigned long lineno;           /* Line number to restore */
4190         char *name;                     /* Position of name in opt_path */
4191 };
4193 /* The top of the path stack. */
4194 static struct tree_stack_entry *tree_stack = NULL;
4195 unsigned long tree_lineno = 0;
4197 static void
4198 pop_tree_stack_entry(void)
4200         struct tree_stack_entry *entry = tree_stack;
4202         tree_lineno = entry->lineno;
4203         entry->name[0] = 0;
4204         tree_stack = entry->prev;
4205         free(entry);
4208 static void
4209 push_tree_stack_entry(const char *name, unsigned long lineno)
4211         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4212         size_t pathlen = strlen(opt_path);
4214         if (!entry)
4215                 return;
4217         entry->prev = tree_stack;
4218         entry->name = opt_path + pathlen;
4219         tree_stack = entry;
4221         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4222                 pop_tree_stack_entry();
4223                 return;
4224         }
4226         /* Move the current line to the first tree entry. */
4227         tree_lineno = 1;
4228         entry->lineno = lineno;
4231 /* Parse output from git-ls-tree(1):
4232  *
4233  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4234  */
4236 #define SIZEOF_TREE_ATTR \
4237         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4239 #define SIZEOF_TREE_MODE \
4240         STRING_SIZE("100644 ")
4242 #define TREE_ID_OFFSET \
4243         STRING_SIZE("100644 blob ")
4245 struct tree_entry {
4246         char id[SIZEOF_REV];
4247         mode_t mode;
4248         time_t time;                    /* Date from the author ident. */
4249         const char *author;             /* Author of the commit. */
4250         char name[1];
4251 };
4253 static const char *
4254 tree_path(const struct line *line)
4256         return ((struct tree_entry *) line->data)->name;
4259 static int
4260 tree_compare_entry(const struct line *line1, const struct line *line2)
4262         if (line1->type != line2->type)
4263                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4264         return strcmp(tree_path(line1), tree_path(line2));
4267 static const enum sort_field tree_sort_fields[] = {
4268         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4269 };
4270 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4272 static int
4273 tree_compare(const void *l1, const void *l2)
4275         const struct line *line1 = (const struct line *) l1;
4276         const struct line *line2 = (const struct line *) l2;
4277         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4278         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4280         if (line1->type == LINE_TREE_HEAD)
4281                 return -1;
4282         if (line2->type == LINE_TREE_HEAD)
4283                 return 1;
4285         switch (get_sort_field(tree_sort_state)) {
4286         case ORDERBY_DATE:
4287                 return sort_order(tree_sort_state, entry1->time - entry2->time);
4289         case ORDERBY_AUTHOR:
4290                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4292         case ORDERBY_NAME:
4293         default:
4294                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4295         }
4299 static struct line *
4300 tree_entry(struct view *view, enum line_type type, const char *path,
4301            const char *mode, const char *id)
4303         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4304         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4306         if (!entry || !line) {
4307                 free(entry);
4308                 return NULL;
4309         }
4311         strncpy(entry->name, path, strlen(path));
4312         if (mode)
4313                 entry->mode = strtoul(mode, NULL, 8);
4314         if (id)
4315                 string_copy_rev(entry->id, id);
4317         return line;
4320 static bool
4321 tree_read_date(struct view *view, char *text, bool *read_date)
4323         static const char *author_name;
4324         static time_t author_time;
4326         if (!text && *read_date) {
4327                 *read_date = FALSE;
4328                 return TRUE;
4330         } else if (!text) {
4331                 char *path = *opt_path ? opt_path : ".";
4332                 /* Find next entry to process */
4333                 const char *log_file[] = {
4334                         "git", "log", "--no-color", "--pretty=raw",
4335                                 "--cc", "--raw", view->id, "--", path, NULL
4336                 };
4337                 struct io io = {};
4339                 if (!view->lines) {
4340                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4341                         report("Tree is empty");
4342                         return TRUE;
4343                 }
4345                 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4346                         report("Failed to load tree data");
4347                         return TRUE;
4348                 }
4350                 done_io(view->pipe);
4351                 view->io = io;
4352                 *read_date = TRUE;
4353                 return FALSE;
4355         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4356                 parse_author_line(text + STRING_SIZE("author "),
4357                                   &author_name, &author_time);
4359         } else if (*text == ':') {
4360                 char *pos;
4361                 size_t annotated = 1;
4362                 size_t i;
4364                 pos = strchr(text, '\t');
4365                 if (!pos)
4366                         return TRUE;
4367                 text = pos + 1;
4368                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4369                         text += strlen(opt_path);
4370                 pos = strchr(text, '/');
4371                 if (pos)
4372                         *pos = 0;
4374                 for (i = 1; i < view->lines; i++) {
4375                         struct line *line = &view->line[i];
4376                         struct tree_entry *entry = line->data;
4378                         annotated += !!entry->author;
4379                         if (entry->author || strcmp(entry->name, text))
4380                                 continue;
4382                         entry->author = author_name;
4383                         entry->time = author_time;
4384                         line->dirty = 1;
4385                         break;
4386                 }
4388                 if (annotated == view->lines)
4389                         kill_io(view->pipe);
4390         }
4391         return TRUE;
4394 static bool
4395 tree_read(struct view *view, char *text)
4397         static bool read_date = FALSE;
4398         struct tree_entry *data;
4399         struct line *entry, *line;
4400         enum line_type type;
4401         size_t textlen = text ? strlen(text) : 0;
4402         char *path = text + SIZEOF_TREE_ATTR;
4404         if (read_date || !text)
4405                 return tree_read_date(view, text, &read_date);
4407         if (textlen <= SIZEOF_TREE_ATTR)
4408                 return FALSE;
4409         if (view->lines == 0 &&
4410             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4411                 return FALSE;
4413         /* Strip the path part ... */
4414         if (*opt_path) {
4415                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4416                 size_t striplen = strlen(opt_path);
4418                 if (pathlen > striplen)
4419                         memmove(path, path + striplen,
4420                                 pathlen - striplen + 1);
4422                 /* Insert "link" to parent directory. */
4423                 if (view->lines == 1 &&
4424                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4425                         return FALSE;
4426         }
4428         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4429         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4430         if (!entry)
4431                 return FALSE;
4432         data = entry->data;
4434         /* Skip "Directory ..." and ".." line. */
4435         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4436                 if (tree_compare_entry(line, entry) <= 0)
4437                         continue;
4439                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4441                 line->data = data;
4442                 line->type = type;
4443                 for (; line <= entry; line++)
4444                         line->dirty = line->cleareol = 1;
4445                 return TRUE;
4446         }
4448         if (tree_lineno > view->lineno) {
4449                 view->lineno = tree_lineno;
4450                 tree_lineno = 0;
4451         }
4453         return TRUE;
4456 static bool
4457 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4459         struct tree_entry *entry = line->data;
4461         if (line->type == LINE_TREE_HEAD) {
4462                 if (draw_text(view, line->type, "Directory path /", TRUE))
4463                         return TRUE;
4464         } else {
4465                 if (draw_mode(view, entry->mode))
4466                         return TRUE;
4468                 if (opt_author && draw_author(view, entry->author))
4469                         return TRUE;
4471                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4472                         return TRUE;
4473         }
4474         if (draw_text(view, line->type, entry->name, TRUE))
4475                 return TRUE;
4476         return TRUE;
4479 static void
4480 open_blob_editor()
4482         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4483         int fd = mkstemp(file);
4485         if (fd == -1)
4486                 report("Failed to create temporary file");
4487         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4488                 report("Failed to save blob data to file");
4489         else
4490                 open_editor(file);
4491         if (fd != -1)
4492                 unlink(file);
4495 static enum request
4496 tree_request(struct view *view, enum request request, struct line *line)
4498         enum open_flags flags;
4500         switch (request) {
4501         case REQ_VIEW_BLAME:
4502                 if (line->type != LINE_TREE_FILE) {
4503                         report("Blame only supported for files");
4504                         return REQ_NONE;
4505                 }
4507                 string_copy(opt_ref, view->vid);
4508                 return request;
4510         case REQ_EDIT:
4511                 if (line->type != LINE_TREE_FILE) {
4512                         report("Edit only supported for files");
4513                 } else if (!is_head_commit(view->vid)) {
4514                         open_blob_editor();
4515                 } else {
4516                         open_editor(opt_file);
4517                 }
4518                 return REQ_NONE;
4520         case REQ_TOGGLE_SORT_FIELD:
4521         case REQ_TOGGLE_SORT_ORDER:
4522                 sort_view(view, request, &tree_sort_state, tree_compare);
4523                 return REQ_NONE;
4525         case REQ_PARENT:
4526                 if (!*opt_path) {
4527                         /* quit view if at top of tree */
4528                         return REQ_VIEW_CLOSE;
4529                 }
4530                 /* fake 'cd  ..' */
4531                 line = &view->line[1];
4532                 break;
4534         case REQ_ENTER:
4535                 break;
4537         default:
4538                 return request;
4539         }
4541         /* Cleanup the stack if the tree view is at a different tree. */
4542         while (!*opt_path && tree_stack)
4543                 pop_tree_stack_entry();
4545         switch (line->type) {
4546         case LINE_TREE_DIR:
4547                 /* Depending on whether it is a subdirectory or parent link
4548                  * mangle the path buffer. */
4549                 if (line == &view->line[1] && *opt_path) {
4550                         pop_tree_stack_entry();
4552                 } else {
4553                         const char *basename = tree_path(line);
4555                         push_tree_stack_entry(basename, view->lineno);
4556                 }
4558                 /* Trees and subtrees share the same ID, so they are not not
4559                  * unique like blobs. */
4560                 flags = OPEN_RELOAD;
4561                 request = REQ_VIEW_TREE;
4562                 break;
4564         case LINE_TREE_FILE:
4565                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4566                 request = REQ_VIEW_BLOB;
4567                 break;
4569         default:
4570                 return REQ_NONE;
4571         }
4573         open_view(view, request, flags);
4574         if (request == REQ_VIEW_TREE)
4575                 view->lineno = tree_lineno;
4577         return REQ_NONE;
4580 static bool
4581 tree_grep(struct view *view, struct line *line)
4583         struct tree_entry *entry = line->data;
4584         const char *text[] = {
4585                 entry->name,
4586                 opt_author ? entry->author : "",
4587                 opt_date ? mkdate(&entry->time) : "",
4588                 NULL
4589         };
4591         return grep_text(view, text);
4594 static void
4595 tree_select(struct view *view, struct line *line)
4597         struct tree_entry *entry = line->data;
4599         if (line->type == LINE_TREE_FILE) {
4600                 string_copy_rev(ref_blob, entry->id);
4601                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4603         } else if (line->type != LINE_TREE_DIR) {
4604                 return;
4605         }
4607         string_copy_rev(view->ref, entry->id);
4610 static bool
4611 tree_prepare(struct view *view)
4613         if (view->lines == 0 && opt_prefix[0]) {
4614                 char *pos = opt_prefix;
4616                 while (pos && *pos) {
4617                         char *end = strchr(pos, '/');
4619                         if (end)
4620                                 *end = 0;
4621                         push_tree_stack_entry(pos, 0);
4622                         pos = end;
4623                         if (end) {
4624                                 *end = '/';
4625                                 pos++;
4626                         }
4627                 }
4629         } else if (strcmp(view->vid, view->id)) {
4630                 opt_path[0] = 0;
4631         }
4633         return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4636 static const char *tree_argv[SIZEOF_ARG] = {
4637         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4638 };
4640 static struct view_ops tree_ops = {
4641         "file",
4642         tree_argv,
4643         NULL,
4644         tree_read,
4645         tree_draw,
4646         tree_request,
4647         tree_grep,
4648         tree_select,
4649         tree_prepare,
4650 };
4652 static bool
4653 blob_read(struct view *view, char *line)
4655         if (!line)
4656                 return TRUE;
4657         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4660 static enum request
4661 blob_request(struct view *view, enum request request, struct line *line)
4663         switch (request) {
4664         case REQ_EDIT:
4665                 open_blob_editor();
4666                 return REQ_NONE;
4667         default:
4668                 return pager_request(view, request, line);
4669         }
4672 static const char *blob_argv[SIZEOF_ARG] = {
4673         "git", "cat-file", "blob", "%(blob)", NULL
4674 };
4676 static struct view_ops blob_ops = {
4677         "line",
4678         blob_argv,
4679         NULL,
4680         blob_read,
4681         pager_draw,
4682         blob_request,
4683         pager_grep,
4684         pager_select,
4685 };
4687 /*
4688  * Blame backend
4689  *
4690  * Loading the blame view is a two phase job:
4691  *
4692  *  1. File content is read either using opt_file from the
4693  *     filesystem or using git-cat-file.
4694  *  2. Then blame information is incrementally added by
4695  *     reading output from git-blame.
4696  */
4698 static const char *blame_head_argv[] = {
4699         "git", "blame", "--incremental", "--", "%(file)", NULL
4700 };
4702 static const char *blame_ref_argv[] = {
4703         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4704 };
4706 static const char *blame_cat_file_argv[] = {
4707         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4708 };
4710 struct blame_commit {
4711         char id[SIZEOF_REV];            /* SHA1 ID. */
4712         char title[128];                /* First line of the commit message. */
4713         const char *author;             /* Author of the commit. */
4714         time_t time;                    /* Date from the author ident. */
4715         char filename[128];             /* Name of file. */
4716         bool has_previous;              /* Was a "previous" line detected. */
4717 };
4719 struct blame {
4720         struct blame_commit *commit;
4721         unsigned long lineno;
4722         char text[1];
4723 };
4725 static bool
4726 blame_open(struct view *view)
4728         char path[SIZEOF_STR];
4730         if (!view->parent && *opt_prefix) {
4731                 string_copy(path, opt_file);
4732                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4733                         return FALSE;
4734         }
4736         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4737                 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4738                         return FALSE;
4739         }
4741         setup_update(view, opt_file);
4742         string_format(view->ref, "%s ...", opt_file);
4744         return TRUE;
4747 static struct blame_commit *
4748 get_blame_commit(struct view *view, const char *id)
4750         size_t i;
4752         for (i = 0; i < view->lines; i++) {
4753                 struct blame *blame = view->line[i].data;
4755                 if (!blame->commit)
4756                         continue;
4758                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4759                         return blame->commit;
4760         }
4762         {
4763                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4765                 if (commit)
4766                         string_ncopy(commit->id, id, SIZEOF_REV);
4767                 return commit;
4768         }
4771 static bool
4772 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4774         const char *pos = *posref;
4776         *posref = NULL;
4777         pos = strchr(pos + 1, ' ');
4778         if (!pos || !isdigit(pos[1]))
4779                 return FALSE;
4780         *number = atoi(pos + 1);
4781         if (*number < min || *number > max)
4782                 return FALSE;
4784         *posref = pos;
4785         return TRUE;
4788 static struct blame_commit *
4789 parse_blame_commit(struct view *view, const char *text, int *blamed)
4791         struct blame_commit *commit;
4792         struct blame *blame;
4793         const char *pos = text + SIZEOF_REV - 2;
4794         size_t orig_lineno = 0;
4795         size_t lineno;
4796         size_t group;
4798         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4799                 return NULL;
4801         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4802             !parse_number(&pos, &lineno, 1, view->lines) ||
4803             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4804                 return NULL;
4806         commit = get_blame_commit(view, text);
4807         if (!commit)
4808                 return NULL;
4810         *blamed += group;
4811         while (group--) {
4812                 struct line *line = &view->line[lineno + group - 1];
4814                 blame = line->data;
4815                 blame->commit = commit;
4816                 blame->lineno = orig_lineno + group - 1;
4817                 line->dirty = 1;
4818         }
4820         return commit;
4823 static bool
4824 blame_read_file(struct view *view, const char *line, bool *read_file)
4826         if (!line) {
4827                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4828                 struct io io = {};
4830                 if (view->lines == 0 && !view->parent)
4831                         die("No blame exist for %s", view->vid);
4833                 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4834                         report("Failed to load blame data");
4835                         return TRUE;
4836                 }
4838                 done_io(view->pipe);
4839                 view->io = io;
4840                 *read_file = FALSE;
4841                 return FALSE;
4843         } else {
4844                 size_t linelen = strlen(line);
4845                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4847                 if (!blame)
4848                         return FALSE;
4850                 blame->commit = NULL;
4851                 strncpy(blame->text, line, linelen);
4852                 blame->text[linelen] = 0;
4853                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4854         }
4857 static bool
4858 match_blame_header(const char *name, char **line)
4860         size_t namelen = strlen(name);
4861         bool matched = !strncmp(name, *line, namelen);
4863         if (matched)
4864                 *line += namelen;
4866         return matched;
4869 static bool
4870 blame_read(struct view *view, char *line)
4872         static struct blame_commit *commit = NULL;
4873         static int blamed = 0;
4874         static bool read_file = TRUE;
4876         if (read_file)
4877                 return blame_read_file(view, line, &read_file);
4879         if (!line) {
4880                 /* Reset all! */
4881                 commit = NULL;
4882                 blamed = 0;
4883                 read_file = TRUE;
4884                 string_format(view->ref, "%s", view->vid);
4885                 if (view_is_displayed(view)) {
4886                         update_view_title(view);
4887                         redraw_view_from(view, 0);
4888                 }
4889                 return TRUE;
4890         }
4892         if (!commit) {
4893                 commit = parse_blame_commit(view, line, &blamed);
4894                 string_format(view->ref, "%s %2d%%", view->vid,
4895                               view->lines ? blamed * 100 / view->lines : 0);
4897         } else if (match_blame_header("author ", &line)) {
4898                 commit->author = get_author(line);
4900         } else if (match_blame_header("author-time ", &line)) {
4901                 commit->time = (time_t) atol(line);
4903         } else if (match_blame_header("author-tz ", &line)) {
4904                 parse_timezone(&commit->time, line);
4906         } else if (match_blame_header("summary ", &line)) {
4907                 string_ncopy(commit->title, line, strlen(line));
4909         } else if (match_blame_header("previous ", &line)) {
4910                 commit->has_previous = TRUE;
4912         } else if (match_blame_header("filename ", &line)) {
4913                 string_ncopy(commit->filename, line, strlen(line));
4914                 commit = NULL;
4915         }
4917         return TRUE;
4920 static bool
4921 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4923         struct blame *blame = line->data;
4924         time_t *time = NULL;
4925         const char *id = NULL, *author = NULL;
4926         char text[SIZEOF_STR];
4928         if (blame->commit && *blame->commit->filename) {
4929                 id = blame->commit->id;
4930                 author = blame->commit->author;
4931                 time = &blame->commit->time;
4932         }
4934         if (opt_date && draw_date(view, time))
4935                 return TRUE;
4937         if (opt_author && draw_author(view, author))
4938                 return TRUE;
4940         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4941                 return TRUE;
4943         if (draw_lineno(view, lineno))
4944                 return TRUE;
4946         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4947         draw_text(view, LINE_DEFAULT, text, TRUE);
4948         return TRUE;
4951 static bool
4952 check_blame_commit(struct blame *blame, bool check_null_id)
4954         if (!blame->commit)
4955                 report("Commit data not loaded yet");
4956         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4957                 report("No commit exist for the selected line");
4958         else
4959                 return TRUE;
4960         return FALSE;
4963 static void
4964 setup_blame_parent_line(struct view *view, struct blame *blame)
4966         const char *diff_tree_argv[] = {
4967                 "git", "diff-tree", "-U0", blame->commit->id,
4968                         "--", blame->commit->filename, NULL
4969         };
4970         struct io io = {};
4971         int parent_lineno = -1;
4972         int blamed_lineno = -1;
4973         char *line;
4975         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4976                 return;
4978         while ((line = io_get(&io, '\n', TRUE))) {
4979                 if (*line == '@') {
4980                         char *pos = strchr(line, '+');
4982                         parent_lineno = atoi(line + 4);
4983                         if (pos)
4984                                 blamed_lineno = atoi(pos + 1);
4986                 } else if (*line == '+' && parent_lineno != -1) {
4987                         if (blame->lineno == blamed_lineno - 1 &&
4988                             !strcmp(blame->text, line + 1)) {
4989                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4990                                 break;
4991                         }
4992                         blamed_lineno++;
4993                 }
4994         }
4996         done_io(&io);
4999 static enum request
5000 blame_request(struct view *view, enum request request, struct line *line)
5002         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5003         struct blame *blame = line->data;
5005         switch (request) {
5006         case REQ_VIEW_BLAME:
5007                 if (check_blame_commit(blame, TRUE)) {
5008                         string_copy(opt_ref, blame->commit->id);
5009                         string_copy(opt_file, blame->commit->filename);
5010                         if (blame->lineno)
5011                                 view->lineno = blame->lineno;
5012                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5013                 }
5014                 break;
5016         case REQ_PARENT:
5017                 if (check_blame_commit(blame, TRUE) &&
5018                     select_commit_parent(blame->commit->id, opt_ref,
5019                                          blame->commit->filename)) {
5020                         string_copy(opt_file, blame->commit->filename);
5021                         setup_blame_parent_line(view, blame);
5022                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5023                 }
5024                 break;
5026         case REQ_ENTER:
5027                 if (!check_blame_commit(blame, FALSE))
5028                         break;
5030                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5031                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5032                         break;
5034                 if (!strcmp(blame->commit->id, NULL_ID)) {
5035                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5036                         const char *diff_index_argv[] = {
5037                                 "git", "diff-index", "--root", "--patch-with-stat",
5038                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5039                         };
5041                         if (!blame->commit->has_previous) {
5042                                 diff_index_argv[1] = "diff";
5043                                 diff_index_argv[2] = "--no-color";
5044                                 diff_index_argv[6] = "--";
5045                                 diff_index_argv[7] = "/dev/null";
5046                         }
5048                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5049                                 report("Failed to allocate diff command");
5050                                 break;
5051                         }
5052                         flags |= OPEN_PREPARED;
5053                 }
5055                 open_view(view, REQ_VIEW_DIFF, flags);
5056                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5057                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5058                 break;
5060         default:
5061                 return request;
5062         }
5064         return REQ_NONE;
5067 static bool
5068 blame_grep(struct view *view, struct line *line)
5070         struct blame *blame = line->data;
5071         struct blame_commit *commit = blame->commit;
5072         const char *text[] = {
5073                 blame->text,
5074                 commit ? commit->title : "",
5075                 commit ? commit->id : "",
5076                 commit && opt_author ? commit->author : "",
5077                 commit && opt_date ? mkdate(&commit->time) : "",
5078                 NULL
5079         };
5081         return grep_text(view, text);
5084 static void
5085 blame_select(struct view *view, struct line *line)
5087         struct blame *blame = line->data;
5088         struct blame_commit *commit = blame->commit;
5090         if (!commit)
5091                 return;
5093         if (!strcmp(commit->id, NULL_ID))
5094                 string_ncopy(ref_commit, "HEAD", 4);
5095         else
5096                 string_copy_rev(ref_commit, commit->id);
5099 static struct view_ops blame_ops = {
5100         "line",
5101         NULL,
5102         blame_open,
5103         blame_read,
5104         blame_draw,
5105         blame_request,
5106         blame_grep,
5107         blame_select,
5108 };
5110 /*
5111  * Branch backend
5112  */
5114 struct branch {
5115         const char *author;             /* Author of the last commit. */
5116         time_t time;                    /* Date of the last activity. */
5117         const struct ref *ref;          /* Name and commit ID information. */
5118 };
5120 static const struct ref branch_all;
5122 static const enum sort_field branch_sort_fields[] = {
5123         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5124 };
5125 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5127 static int
5128 branch_compare(const void *l1, const void *l2)
5130         const struct branch *branch1 = ((const struct line *) l1)->data;
5131         const struct branch *branch2 = ((const struct line *) l2)->data;
5133         switch (get_sort_field(branch_sort_state)) {
5134         case ORDERBY_DATE:
5135                 return sort_order(branch_sort_state, branch1->time - branch2->time);
5137         case ORDERBY_AUTHOR:
5138                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5140         case ORDERBY_NAME:
5141         default:
5142                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5143         }
5146 static bool
5147 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5149         struct branch *branch = line->data;
5150         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5152         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5153                 return TRUE;
5155         if (opt_author && draw_author(view, branch->author))
5156                 return TRUE;
5158         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5159         return TRUE;
5162 static enum request
5163 branch_request(struct view *view, enum request request, struct line *line)
5165         struct branch *branch = line->data;
5167         switch (request) {
5168         case REQ_REFRESH:
5169                 load_refs();
5170                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5171                 return REQ_NONE;
5173         case REQ_TOGGLE_SORT_FIELD:
5174         case REQ_TOGGLE_SORT_ORDER:
5175                 sort_view(view, request, &branch_sort_state, branch_compare);
5176                 return REQ_NONE;
5178         case REQ_ENTER:
5179                 if (branch->ref == &branch_all) {
5180                         const char *all_branches_argv[] = {
5181                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5182                                       "--topo-order", "--all", NULL
5183                         };
5184                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5186                         if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5187                                 report("Failed to load view of all branches");
5188                                 return REQ_NONE;
5189                         }
5190                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5191                 } else {
5192                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5193                 }
5194                 return REQ_NONE;
5196         default:
5197                 return request;
5198         }
5201 static bool
5202 branch_read(struct view *view, char *line)
5204         static char id[SIZEOF_REV];
5205         struct branch *reference;
5206         size_t i;
5208         if (!line)
5209                 return TRUE;
5211         switch (get_line_type(line)) {
5212         case LINE_COMMIT:
5213                 string_copy_rev(id, line + STRING_SIZE("commit "));
5214                 return TRUE;
5216         case LINE_AUTHOR:
5217                 for (i = 0, reference = NULL; i < view->lines; i++) {
5218                         struct branch *branch = view->line[i].data;
5220                         if (strcmp(branch->ref->id, id))
5221                                 continue;
5223                         view->line[i].dirty = TRUE;
5224                         if (reference) {
5225                                 branch->author = reference->author;
5226                                 branch->time = reference->time;
5227                                 continue;
5228                         }
5230                         parse_author_line(line + STRING_SIZE("author "),
5231                                           &branch->author, &branch->time);
5232                         reference = branch;
5233                 }
5234                 return TRUE;
5236         default:
5237                 return TRUE;
5238         }
5242 static bool
5243 branch_open_visitor(void *data, const struct ref *ref)
5245         struct view *view = data;
5246         struct branch *branch;
5248         if (ref->tag || ref->ltag || ref->remote)
5249                 return TRUE;
5251         branch = calloc(1, sizeof(*branch));
5252         if (!branch)
5253                 return FALSE;
5255         branch->ref = ref;
5256         return !!add_line_data(view, branch, LINE_DEFAULT);
5259 static bool
5260 branch_open(struct view *view)
5262         const char *branch_log[] = {
5263                 "git", "log", "--no-color", "--pretty=raw",
5264                         "--simplify-by-decoration", "--all", NULL
5265         };
5267         if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5268                 report("Failed to load branch data");
5269                 return TRUE;
5270         }
5272         setup_update(view, view->id);
5273         branch_open_visitor(view, &branch_all);
5274         foreach_ref(branch_open_visitor, view);
5275         view->p_restore = TRUE;
5277         return TRUE;
5280 static bool
5281 branch_grep(struct view *view, struct line *line)
5283         struct branch *branch = line->data;
5284         const char *text[] = {
5285                 branch->ref->name,
5286                 branch->author,
5287                 NULL
5288         };
5290         return grep_text(view, text);
5293 static void
5294 branch_select(struct view *view, struct line *line)
5296         struct branch *branch = line->data;
5298         string_copy_rev(view->ref, branch->ref->id);
5299         string_copy_rev(ref_commit, branch->ref->id);
5300         string_copy_rev(ref_head, branch->ref->id);
5303 static struct view_ops branch_ops = {
5304         "branch",
5305         NULL,
5306         branch_open,
5307         branch_read,
5308         branch_draw,
5309         branch_request,
5310         branch_grep,
5311         branch_select,
5312 };
5314 /*
5315  * Status backend
5316  */
5318 struct status {
5319         char status;
5320         struct {
5321                 mode_t mode;
5322                 char rev[SIZEOF_REV];
5323                 char name[SIZEOF_STR];
5324         } old;
5325         struct {
5326                 mode_t mode;
5327                 char rev[SIZEOF_REV];
5328                 char name[SIZEOF_STR];
5329         } new;
5330 };
5332 static char status_onbranch[SIZEOF_STR];
5333 static struct status stage_status;
5334 static enum line_type stage_line_type;
5335 static size_t stage_chunks;
5336 static int *stage_chunk;
5338 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5340 /* This should work even for the "On branch" line. */
5341 static inline bool
5342 status_has_none(struct view *view, struct line *line)
5344         return line < view->line + view->lines && !line[1].data;
5347 /* Get fields from the diff line:
5348  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5349  */
5350 static inline bool
5351 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5353         const char *old_mode = buf +  1;
5354         const char *new_mode = buf +  8;
5355         const char *old_rev  = buf + 15;
5356         const char *new_rev  = buf + 56;
5357         const char *status   = buf + 97;
5359         if (bufsize < 98 ||
5360             old_mode[-1] != ':' ||
5361             new_mode[-1] != ' ' ||
5362             old_rev[-1]  != ' ' ||
5363             new_rev[-1]  != ' ' ||
5364             status[-1]   != ' ')
5365                 return FALSE;
5367         file->status = *status;
5369         string_copy_rev(file->old.rev, old_rev);
5370         string_copy_rev(file->new.rev, new_rev);
5372         file->old.mode = strtoul(old_mode, NULL, 8);
5373         file->new.mode = strtoul(new_mode, NULL, 8);
5375         file->old.name[0] = file->new.name[0] = 0;
5377         return TRUE;
5380 static bool
5381 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5383         struct status *unmerged = NULL;
5384         char *buf;
5385         struct io io = {};
5387         if (!run_io(&io, argv, opt_cdup, IO_RD))
5388                 return FALSE;
5390         add_line_data(view, NULL, type);
5392         while ((buf = io_get(&io, 0, TRUE))) {
5393                 struct status *file = unmerged;
5395                 if (!file) {
5396                         file = calloc(1, sizeof(*file));
5397                         if (!file || !add_line_data(view, file, type))
5398                                 goto error_out;
5399                 }
5401                 /* Parse diff info part. */
5402                 if (status) {
5403                         file->status = status;
5404                         if (status == 'A')
5405                                 string_copy(file->old.rev, NULL_ID);
5407                 } else if (!file->status || file == unmerged) {
5408                         if (!status_get_diff(file, buf, strlen(buf)))
5409                                 goto error_out;
5411                         buf = io_get(&io, 0, TRUE);
5412                         if (!buf)
5413                                 break;
5415                         /* Collapse all modified entries that follow an
5416                          * associated unmerged entry. */
5417                         if (unmerged == file) {
5418                                 unmerged->status = 'U';
5419                                 unmerged = NULL;
5420                         } else if (file->status == 'U') {
5421                                 unmerged = file;
5422                         }
5423                 }
5425                 /* Grab the old name for rename/copy. */
5426                 if (!*file->old.name &&
5427                     (file->status == 'R' || file->status == 'C')) {
5428                         string_ncopy(file->old.name, buf, strlen(buf));
5430                         buf = io_get(&io, 0, TRUE);
5431                         if (!buf)
5432                                 break;
5433                 }
5435                 /* git-ls-files just delivers a NUL separated list of
5436                  * file names similar to the second half of the
5437                  * git-diff-* output. */
5438                 string_ncopy(file->new.name, buf, strlen(buf));
5439                 if (!*file->old.name)
5440                         string_copy(file->old.name, file->new.name);
5441                 file = NULL;
5442         }
5444         if (io_error(&io)) {
5445 error_out:
5446                 done_io(&io);
5447                 return FALSE;
5448         }
5450         if (!view->line[view->lines - 1].data)
5451                 add_line_data(view, NULL, LINE_STAT_NONE);
5453         done_io(&io);
5454         return TRUE;
5457 /* Don't show unmerged entries in the staged section. */
5458 static const char *status_diff_index_argv[] = {
5459         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5460                              "--cached", "-M", "HEAD", NULL
5461 };
5463 static const char *status_diff_files_argv[] = {
5464         "git", "diff-files", "-z", NULL
5465 };
5467 static const char *status_list_other_argv[] = {
5468         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5469 };
5471 static const char *status_list_no_head_argv[] = {
5472         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5473 };
5475 static const char *update_index_argv[] = {
5476         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5477 };
5479 /* Restore the previous line number to stay in the context or select a
5480  * line with something that can be updated. */
5481 static void
5482 status_restore(struct view *view)
5484         if (view->p_lineno >= view->lines)
5485                 view->p_lineno = view->lines - 1;
5486         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5487                 view->p_lineno++;
5488         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5489                 view->p_lineno--;
5491         /* If the above fails, always skip the "On branch" line. */
5492         if (view->p_lineno < view->lines)
5493                 view->lineno = view->p_lineno;
5494         else
5495                 view->lineno = 1;
5497         if (view->lineno < view->offset)
5498                 view->offset = view->lineno;
5499         else if (view->offset + view->height <= view->lineno)
5500                 view->offset = view->lineno - view->height + 1;
5502         view->p_restore = FALSE;
5505 static void
5506 status_update_onbranch(void)
5508         static const char *paths[][2] = {
5509                 { "rebase-apply/rebasing",      "Rebasing" },
5510                 { "rebase-apply/applying",      "Applying mailbox" },
5511                 { "rebase-apply/",              "Rebasing mailbox" },
5512                 { "rebase-merge/interactive",   "Interactive rebase" },
5513                 { "rebase-merge/",              "Rebase merge" },
5514                 { "MERGE_HEAD",                 "Merging" },
5515                 { "BISECT_LOG",                 "Bisecting" },
5516                 { "HEAD",                       "On branch" },
5517         };
5518         char buf[SIZEOF_STR];
5519         struct stat stat;
5520         int i;
5522         if (is_initial_commit()) {
5523                 string_copy(status_onbranch, "Initial commit");
5524                 return;
5525         }
5527         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5528                 char *head = opt_head;
5530                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5531                     lstat(buf, &stat) < 0)
5532                         continue;
5534                 if (!*opt_head) {
5535                         struct io io = {};
5537                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5538                             io_read_buf(&io, buf, sizeof(buf))) {
5539                                 head = buf;
5540                                 if (!prefixcmp(head, "refs/heads/"))
5541                                         head += STRING_SIZE("refs/heads/");
5542                         }
5543                 }
5545                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5546                         string_copy(status_onbranch, opt_head);
5547                 return;
5548         }
5550         string_copy(status_onbranch, "Not currently on any branch");
5553 /* First parse staged info using git-diff-index(1), then parse unstaged
5554  * info using git-diff-files(1), and finally untracked files using
5555  * git-ls-files(1). */
5556 static bool
5557 status_open(struct view *view)
5559         reset_view(view);
5561         add_line_data(view, NULL, LINE_STAT_HEAD);
5562         status_update_onbranch();
5564         run_io_bg(update_index_argv);
5566         if (is_initial_commit()) {
5567                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5568                         return FALSE;
5569         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5570                 return FALSE;
5571         }
5573         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5574             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5575                 return FALSE;
5577         /* Restore the exact position or use the specialized restore
5578          * mode? */
5579         if (!view->p_restore)
5580                 status_restore(view);
5581         return TRUE;
5584 static bool
5585 status_draw(struct view *view, struct line *line, unsigned int lineno)
5587         struct status *status = line->data;
5588         enum line_type type;
5589         const char *text;
5591         if (!status) {
5592                 switch (line->type) {
5593                 case LINE_STAT_STAGED:
5594                         type = LINE_STAT_SECTION;
5595                         text = "Changes to be committed:";
5596                         break;
5598                 case LINE_STAT_UNSTAGED:
5599                         type = LINE_STAT_SECTION;
5600                         text = "Changed but not updated:";
5601                         break;
5603                 case LINE_STAT_UNTRACKED:
5604                         type = LINE_STAT_SECTION;
5605                         text = "Untracked files:";
5606                         break;
5608                 case LINE_STAT_NONE:
5609                         type = LINE_DEFAULT;
5610                         text = "  (no files)";
5611                         break;
5613                 case LINE_STAT_HEAD:
5614                         type = LINE_STAT_HEAD;
5615                         text = status_onbranch;
5616                         break;
5618                 default:
5619                         return FALSE;
5620                 }
5621         } else {
5622                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5624                 buf[0] = status->status;
5625                 if (draw_text(view, line->type, buf, TRUE))
5626                         return TRUE;
5627                 type = LINE_DEFAULT;
5628                 text = status->new.name;
5629         }
5631         draw_text(view, type, text, TRUE);
5632         return TRUE;
5635 static enum request
5636 status_load_error(struct view *view, struct view *stage, const char *path)
5638         if (displayed_views() == 2 || display[current_view] != view)
5639                 maximize_view(view);
5640         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5641         return REQ_NONE;
5644 static enum request
5645 status_enter(struct view *view, struct line *line)
5647         struct status *status = line->data;
5648         const char *oldpath = status ? status->old.name : NULL;
5649         /* Diffs for unmerged entries are empty when passing the new
5650          * path, so leave it empty. */
5651         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5652         const char *info;
5653         enum open_flags split;
5654         struct view *stage = VIEW(REQ_VIEW_STAGE);
5656         if (line->type == LINE_STAT_NONE ||
5657             (!status && line[1].type == LINE_STAT_NONE)) {
5658                 report("No file to diff");
5659                 return REQ_NONE;
5660         }
5662         switch (line->type) {
5663         case LINE_STAT_STAGED:
5664                 if (is_initial_commit()) {
5665                         const char *no_head_diff_argv[] = {
5666                                 "git", "diff", "--no-color", "--patch-with-stat",
5667                                         "--", "/dev/null", newpath, NULL
5668                         };
5670                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5671                                 return status_load_error(view, stage, newpath);
5672                 } else {
5673                         const char *index_show_argv[] = {
5674                                 "git", "diff-index", "--root", "--patch-with-stat",
5675                                         "-C", "-M", "--cached", "HEAD", "--",
5676                                         oldpath, newpath, NULL
5677                         };
5679                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5680                                 return status_load_error(view, stage, newpath);
5681                 }
5683                 if (status)
5684                         info = "Staged changes to %s";
5685                 else
5686                         info = "Staged changes";
5687                 break;
5689         case LINE_STAT_UNSTAGED:
5690         {
5691                 const char *files_show_argv[] = {
5692                         "git", "diff-files", "--root", "--patch-with-stat",
5693                                 "-C", "-M", "--", oldpath, newpath, NULL
5694                 };
5696                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5697                         return status_load_error(view, stage, newpath);
5698                 if (status)
5699                         info = "Unstaged changes to %s";
5700                 else
5701                         info = "Unstaged changes";
5702                 break;
5703         }
5704         case LINE_STAT_UNTRACKED:
5705                 if (!newpath) {
5706                         report("No file to show");
5707                         return REQ_NONE;
5708                 }
5710                 if (!suffixcmp(status->new.name, -1, "/")) {
5711                         report("Cannot display a directory");
5712                         return REQ_NONE;
5713                 }
5715                 if (!prepare_update_file(stage, newpath))
5716                         return status_load_error(view, stage, newpath);
5717                 info = "Untracked file %s";
5718                 break;
5720         case LINE_STAT_HEAD:
5721                 return REQ_NONE;
5723         default:
5724                 die("line type %d not handled in switch", line->type);
5725         }
5727         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5728         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5729         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5730                 if (status) {
5731                         stage_status = *status;
5732                 } else {
5733                         memset(&stage_status, 0, sizeof(stage_status));
5734                 }
5736                 stage_line_type = line->type;
5737                 stage_chunks = 0;
5738                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5739         }
5741         return REQ_NONE;
5744 static bool
5745 status_exists(struct status *status, enum line_type type)
5747         struct view *view = VIEW(REQ_VIEW_STATUS);
5748         unsigned long lineno;
5750         for (lineno = 0; lineno < view->lines; lineno++) {
5751                 struct line *line = &view->line[lineno];
5752                 struct status *pos = line->data;
5754                 if (line->type != type)
5755                         continue;
5756                 if (!pos && (!status || !status->status) && line[1].data) {
5757                         select_view_line(view, lineno);
5758                         return TRUE;
5759                 }
5760                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5761                         select_view_line(view, lineno);
5762                         return TRUE;
5763                 }
5764         }
5766         return FALSE;
5770 static bool
5771 status_update_prepare(struct io *io, enum line_type type)
5773         const char *staged_argv[] = {
5774                 "git", "update-index", "-z", "--index-info", NULL
5775         };
5776         const char *others_argv[] = {
5777                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5778         };
5780         switch (type) {
5781         case LINE_STAT_STAGED:
5782                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5784         case LINE_STAT_UNSTAGED:
5785         case LINE_STAT_UNTRACKED:
5786                 return run_io(io, others_argv, opt_cdup, IO_WR);
5788         default:
5789                 die("line type %d not handled in switch", type);
5790                 return FALSE;
5791         }
5794 static bool
5795 status_update_write(struct io *io, struct status *status, enum line_type type)
5797         char buf[SIZEOF_STR];
5798         size_t bufsize = 0;
5800         switch (type) {
5801         case LINE_STAT_STAGED:
5802                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5803                                         status->old.mode,
5804                                         status->old.rev,
5805                                         status->old.name, 0))
5806                         return FALSE;
5807                 break;
5809         case LINE_STAT_UNSTAGED:
5810         case LINE_STAT_UNTRACKED:
5811                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5812                         return FALSE;
5813                 break;
5815         default:
5816                 die("line type %d not handled in switch", type);
5817         }
5819         return io_write(io, buf, bufsize);
5822 static bool
5823 status_update_file(struct status *status, enum line_type type)
5825         struct io io = {};
5826         bool result;
5828         if (!status_update_prepare(&io, type))
5829                 return FALSE;
5831         result = status_update_write(&io, status, type);
5832         return done_io(&io) && result;
5835 static bool
5836 status_update_files(struct view *view, struct line *line)
5838         char buf[sizeof(view->ref)];
5839         struct io io = {};
5840         bool result = TRUE;
5841         struct line *pos = view->line + view->lines;
5842         int files = 0;
5843         int file, done;
5844         int cursor_y = -1, cursor_x = -1;
5846         if (!status_update_prepare(&io, line->type))
5847                 return FALSE;
5849         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5850                 files++;
5852         string_copy(buf, view->ref);
5853         getsyx(cursor_y, cursor_x);
5854         for (file = 0, done = 5; result && file < files; line++, file++) {
5855                 int almost_done = file * 100 / files;
5857                 if (almost_done > done) {
5858                         done = almost_done;
5859                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5860                                       file, files, done);
5861                         update_view_title(view);
5862                         setsyx(cursor_y, cursor_x);
5863                         doupdate();
5864                 }
5865                 result = status_update_write(&io, line->data, line->type);
5866         }
5867         string_copy(view->ref, buf);
5869         return done_io(&io) && result;
5872 static bool
5873 status_update(struct view *view)
5875         struct line *line = &view->line[view->lineno];
5877         assert(view->lines);
5879         if (!line->data) {
5880                 /* This should work even for the "On branch" line. */
5881                 if (line < view->line + view->lines && !line[1].data) {
5882                         report("Nothing to update");
5883                         return FALSE;
5884                 }
5886                 if (!status_update_files(view, line + 1)) {
5887                         report("Failed to update file status");
5888                         return FALSE;
5889                 }
5891         } else if (!status_update_file(line->data, line->type)) {
5892                 report("Failed to update file status");
5893                 return FALSE;
5894         }
5896         return TRUE;
5899 static bool
5900 status_revert(struct status *status, enum line_type type, bool has_none)
5902         if (!status || type != LINE_STAT_UNSTAGED) {
5903                 if (type == LINE_STAT_STAGED) {
5904                         report("Cannot revert changes to staged files");
5905                 } else if (type == LINE_STAT_UNTRACKED) {
5906                         report("Cannot revert changes to untracked files");
5907                 } else if (has_none) {
5908                         report("Nothing to revert");
5909                 } else {
5910                         report("Cannot revert changes to multiple files");
5911                 }
5913         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5914                 char mode[10] = "100644";
5915                 const char *reset_argv[] = {
5916                         "git", "update-index", "--cacheinfo", mode,
5917                                 status->old.rev, status->old.name, NULL
5918                 };
5919                 const char *checkout_argv[] = {
5920                         "git", "checkout", "--", status->old.name, NULL
5921                 };
5923                 if (status->status == 'U') {
5924                         string_format(mode, "%5o", status->old.mode);
5926                         if (status->old.mode == 0 && status->new.mode == 0) {
5927                                 reset_argv[2] = "--force-remove";
5928                                 reset_argv[3] = status->old.name;
5929                                 reset_argv[4] = NULL;
5930                         }
5932                         if (!run_io_fg(reset_argv, opt_cdup))
5933                                 return FALSE;
5934                         if (status->old.mode == 0 && status->new.mode == 0)
5935                                 return TRUE;
5936                 }
5938                 return run_io_fg(checkout_argv, opt_cdup);
5939         }
5941         return FALSE;
5944 static enum request
5945 status_request(struct view *view, enum request request, struct line *line)
5947         struct status *status = line->data;
5949         switch (request) {
5950         case REQ_STATUS_UPDATE:
5951                 if (!status_update(view))
5952                         return REQ_NONE;
5953                 break;
5955         case REQ_STATUS_REVERT:
5956                 if (!status_revert(status, line->type, status_has_none(view, line)))
5957                         return REQ_NONE;
5958                 break;
5960         case REQ_STATUS_MERGE:
5961                 if (!status || status->status != 'U') {
5962                         report("Merging only possible for files with unmerged status ('U').");
5963                         return REQ_NONE;
5964                 }
5965                 open_mergetool(status->new.name);
5966                 break;
5968         case REQ_EDIT:
5969                 if (!status)
5970                         return request;
5971                 if (status->status == 'D') {
5972                         report("File has been deleted.");
5973                         return REQ_NONE;
5974                 }
5976                 open_editor(status->new.name);
5977                 break;
5979         case REQ_VIEW_BLAME:
5980                 if (status)
5981                         opt_ref[0] = 0;
5982                 return request;
5984         case REQ_ENTER:
5985                 /* After returning the status view has been split to
5986                  * show the stage view. No further reloading is
5987                  * necessary. */
5988                 return status_enter(view, line);
5990         case REQ_REFRESH:
5991                 /* Simply reload the view. */
5992                 break;
5994         default:
5995                 return request;
5996         }
5998         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6000         return REQ_NONE;
6003 static void
6004 status_select(struct view *view, struct line *line)
6006         struct status *status = line->data;
6007         char file[SIZEOF_STR] = "all files";
6008         const char *text;
6009         const char *key;
6011         if (status && !string_format(file, "'%s'", status->new.name))
6012                 return;
6014         if (!status && line[1].type == LINE_STAT_NONE)
6015                 line++;
6017         switch (line->type) {
6018         case LINE_STAT_STAGED:
6019                 text = "Press %s to unstage %s for commit";
6020                 break;
6022         case LINE_STAT_UNSTAGED:
6023                 text = "Press %s to stage %s for commit";
6024                 break;
6026         case LINE_STAT_UNTRACKED:
6027                 text = "Press %s to stage %s for addition";
6028                 break;
6030         case LINE_STAT_HEAD:
6031         case LINE_STAT_NONE:
6032                 text = "Nothing to update";
6033                 break;
6035         default:
6036                 die("line type %d not handled in switch", line->type);
6037         }
6039         if (status && status->status == 'U') {
6040                 text = "Press %s to resolve conflict in %s";
6041                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6043         } else {
6044                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6045         }
6047         string_format(view->ref, text, key, file);
6048         if (status)
6049                 string_copy(opt_file, status->new.name);
6052 static bool
6053 status_grep(struct view *view, struct line *line)
6055         struct status *status = line->data;
6057         if (status) {
6058                 const char buf[2] = { status->status, 0 };
6059                 const char *text[] = { status->new.name, buf, NULL };
6061                 return grep_text(view, text);
6062         }
6064         return FALSE;
6067 static struct view_ops status_ops = {
6068         "file",
6069         NULL,
6070         status_open,
6071         NULL,
6072         status_draw,
6073         status_request,
6074         status_grep,
6075         status_select,
6076 };
6079 static bool
6080 stage_diff_write(struct io *io, struct line *line, struct line *end)
6082         while (line < end) {
6083                 if (!io_write(io, line->data, strlen(line->data)) ||
6084                     !io_write(io, "\n", 1))
6085                         return FALSE;
6086                 line++;
6087                 if (line->type == LINE_DIFF_CHUNK ||
6088                     line->type == LINE_DIFF_HEADER)
6089                         break;
6090         }
6092         return TRUE;
6095 static struct line *
6096 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6098         for (; view->line < line; line--)
6099                 if (line->type == type)
6100                         return line;
6102         return NULL;
6105 static bool
6106 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6108         const char *apply_argv[SIZEOF_ARG] = {
6109                 "git", "apply", "--whitespace=nowarn", NULL
6110         };
6111         struct line *diff_hdr;
6112         struct io io = {};
6113         int argc = 3;
6115         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6116         if (!diff_hdr)
6117                 return FALSE;
6119         if (!revert)
6120                 apply_argv[argc++] = "--cached";
6121         if (revert || stage_line_type == LINE_STAT_STAGED)
6122                 apply_argv[argc++] = "-R";
6123         apply_argv[argc++] = "-";
6124         apply_argv[argc++] = NULL;
6125         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6126                 return FALSE;
6128         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6129             !stage_diff_write(&io, chunk, view->line + view->lines))
6130                 chunk = NULL;
6132         done_io(&io);
6133         run_io_bg(update_index_argv);
6135         return chunk ? TRUE : FALSE;
6138 static bool
6139 stage_update(struct view *view, struct line *line)
6141         struct line *chunk = NULL;
6143         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6144                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6146         if (chunk) {
6147                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6148                         report("Failed to apply chunk");
6149                         return FALSE;
6150                 }
6152         } else if (!stage_status.status) {
6153                 view = VIEW(REQ_VIEW_STATUS);
6155                 for (line = view->line; line < view->line + view->lines; line++)
6156                         if (line->type == stage_line_type)
6157                                 break;
6159                 if (!status_update_files(view, line + 1)) {
6160                         report("Failed to update files");
6161                         return FALSE;
6162                 }
6164         } else if (!status_update_file(&stage_status, stage_line_type)) {
6165                 report("Failed to update file");
6166                 return FALSE;
6167         }
6169         return TRUE;
6172 static bool
6173 stage_revert(struct view *view, struct line *line)
6175         struct line *chunk = NULL;
6177         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6178                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6180         if (chunk) {
6181                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6182                         return FALSE;
6184                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6185                         report("Failed to revert chunk");
6186                         return FALSE;
6187                 }
6188                 return TRUE;
6190         } else {
6191                 return status_revert(stage_status.status ? &stage_status : NULL,
6192                                      stage_line_type, FALSE);
6193         }
6197 static void
6198 stage_next(struct view *view, struct line *line)
6200         int i;
6202         if (!stage_chunks) {
6203                 for (line = view->line; line < view->line + view->lines; line++) {
6204                         if (line->type != LINE_DIFF_CHUNK)
6205                                 continue;
6207                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6208                                 report("Allocation failure");
6209                                 return;
6210                         }
6212                         stage_chunk[stage_chunks++] = line - view->line;
6213                 }
6214         }
6216         for (i = 0; i < stage_chunks; i++) {
6217                 if (stage_chunk[i] > view->lineno) {
6218                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6219                         report("Chunk %d of %d", i + 1, stage_chunks);
6220                         return;
6221                 }
6222         }
6224         report("No next chunk found");
6227 static enum request
6228 stage_request(struct view *view, enum request request, struct line *line)
6230         switch (request) {
6231         case REQ_STATUS_UPDATE:
6232                 if (!stage_update(view, line))
6233                         return REQ_NONE;
6234                 break;
6236         case REQ_STATUS_REVERT:
6237                 if (!stage_revert(view, line))
6238                         return REQ_NONE;
6239                 break;
6241         case REQ_STAGE_NEXT:
6242                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6243                         report("File is untracked; press %s to add",
6244                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6245                         return REQ_NONE;
6246                 }
6247                 stage_next(view, line);
6248                 return REQ_NONE;
6250         case REQ_EDIT:
6251                 if (!stage_status.new.name[0])
6252                         return request;
6253                 if (stage_status.status == 'D') {
6254                         report("File has been deleted.");
6255                         return REQ_NONE;
6256                 }
6258                 open_editor(stage_status.new.name);
6259                 break;
6261         case REQ_REFRESH:
6262                 /* Reload everything ... */
6263                 break;
6265         case REQ_VIEW_BLAME:
6266                 if (stage_status.new.name[0]) {
6267                         string_copy(opt_file, stage_status.new.name);
6268                         opt_ref[0] = 0;
6269                 }
6270                 return request;
6272         case REQ_ENTER:
6273                 return pager_request(view, request, line);
6275         default:
6276                 return request;
6277         }
6279         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6280         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6282         /* Check whether the staged entry still exists, and close the
6283          * stage view if it doesn't. */
6284         if (!status_exists(&stage_status, stage_line_type)) {
6285                 status_restore(VIEW(REQ_VIEW_STATUS));
6286                 return REQ_VIEW_CLOSE;
6287         }
6289         if (stage_line_type == LINE_STAT_UNTRACKED) {
6290                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6291                         report("Cannot display a directory");
6292                         return REQ_NONE;
6293                 }
6295                 if (!prepare_update_file(view, stage_status.new.name)) {
6296                         report("Failed to open file: %s", strerror(errno));
6297                         return REQ_NONE;
6298                 }
6299         }
6300         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6302         return REQ_NONE;
6305 static struct view_ops stage_ops = {
6306         "line",
6307         NULL,
6308         NULL,
6309         pager_read,
6310         pager_draw,
6311         stage_request,
6312         pager_grep,
6313         pager_select,
6314 };
6317 /*
6318  * Revision graph
6319  */
6321 struct commit {
6322         char id[SIZEOF_REV];            /* SHA1 ID. */
6323         char title[128];                /* First line of the commit message. */
6324         const char *author;             /* Author of the commit. */
6325         time_t time;                    /* Date from the author ident. */
6326         struct ref_list *refs;          /* Repository references. */
6327         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6328         size_t graph_size;              /* The width of the graph array. */
6329         bool has_parents;               /* Rewritten --parents seen. */
6330 };
6332 /* Size of rev graph with no  "padding" columns */
6333 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6335 struct rev_graph {
6336         struct rev_graph *prev, *next, *parents;
6337         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6338         size_t size;
6339         struct commit *commit;
6340         size_t pos;
6341         unsigned int boundary:1;
6342 };
6344 /* Parents of the commit being visualized. */
6345 static struct rev_graph graph_parents[4];
6347 /* The current stack of revisions on the graph. */
6348 static struct rev_graph graph_stacks[4] = {
6349         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6350         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6351         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6352         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6353 };
6355 static inline bool
6356 graph_parent_is_merge(struct rev_graph *graph)
6358         return graph->parents->size > 1;
6361 static inline void
6362 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6364         struct commit *commit = graph->commit;
6366         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6367                 commit->graph[commit->graph_size++] = symbol;
6370 static void
6371 clear_rev_graph(struct rev_graph *graph)
6373         graph->boundary = 0;
6374         graph->size = graph->pos = 0;
6375         graph->commit = NULL;
6376         memset(graph->parents, 0, sizeof(*graph->parents));
6379 static void
6380 done_rev_graph(struct rev_graph *graph)
6382         if (graph_parent_is_merge(graph) &&
6383             graph->pos < graph->size - 1 &&
6384             graph->next->size == graph->size + graph->parents->size - 1) {
6385                 size_t i = graph->pos + graph->parents->size - 1;
6387                 graph->commit->graph_size = i * 2;
6388                 while (i < graph->next->size - 1) {
6389                         append_to_rev_graph(graph, ' ');
6390                         append_to_rev_graph(graph, '\\');
6391                         i++;
6392                 }
6393         }
6395         clear_rev_graph(graph);
6398 static void
6399 push_rev_graph(struct rev_graph *graph, const char *parent)
6401         int i;
6403         /* "Collapse" duplicate parents lines.
6404          *
6405          * FIXME: This needs to also update update the drawn graph but
6406          * for now it just serves as a method for pruning graph lines. */
6407         for (i = 0; i < graph->size; i++)
6408                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6409                         return;
6411         if (graph->size < SIZEOF_REVITEMS) {
6412                 string_copy_rev(graph->rev[graph->size++], parent);
6413         }
6416 static chtype
6417 get_rev_graph_symbol(struct rev_graph *graph)
6419         chtype symbol;
6421         if (graph->boundary)
6422                 symbol = REVGRAPH_BOUND;
6423         else if (graph->parents->size == 0)
6424                 symbol = REVGRAPH_INIT;
6425         else if (graph_parent_is_merge(graph))
6426                 symbol = REVGRAPH_MERGE;
6427         else if (graph->pos >= graph->size)
6428                 symbol = REVGRAPH_BRANCH;
6429         else
6430                 symbol = REVGRAPH_COMMIT;
6432         return symbol;
6435 static void
6436 draw_rev_graph(struct rev_graph *graph)
6438         struct rev_filler {
6439                 chtype separator, line;
6440         };
6441         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6442         static struct rev_filler fillers[] = {
6443                 { ' ',  '|' },
6444                 { '`',  '.' },
6445                 { '\'', ' ' },
6446                 { '/',  ' ' },
6447         };
6448         chtype symbol = get_rev_graph_symbol(graph);
6449         struct rev_filler *filler;
6450         size_t i;
6452         if (opt_line_graphics)
6453                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6455         filler = &fillers[DEFAULT];
6457         for (i = 0; i < graph->pos; i++) {
6458                 append_to_rev_graph(graph, filler->line);
6459                 if (graph_parent_is_merge(graph->prev) &&
6460                     graph->prev->pos == i)
6461                         filler = &fillers[RSHARP];
6463                 append_to_rev_graph(graph, filler->separator);
6464         }
6466         /* Place the symbol for this revision. */
6467         append_to_rev_graph(graph, symbol);
6469         if (graph->prev->size > graph->size)
6470                 filler = &fillers[RDIAG];
6471         else
6472                 filler = &fillers[DEFAULT];
6474         i++;
6476         for (; i < graph->size; i++) {
6477                 append_to_rev_graph(graph, filler->separator);
6478                 append_to_rev_graph(graph, filler->line);
6479                 if (graph_parent_is_merge(graph->prev) &&
6480                     i < graph->prev->pos + graph->parents->size)
6481                         filler = &fillers[RSHARP];
6482                 if (graph->prev->size > graph->size)
6483                         filler = &fillers[LDIAG];
6484         }
6486         if (graph->prev->size > graph->size) {
6487                 append_to_rev_graph(graph, filler->separator);
6488                 if (filler->line != ' ')
6489                         append_to_rev_graph(graph, filler->line);
6490         }
6493 /* Prepare the next rev graph */
6494 static void
6495 prepare_rev_graph(struct rev_graph *graph)
6497         size_t i;
6499         /* First, traverse all lines of revisions up to the active one. */
6500         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6501                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6502                         break;
6504                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6505         }
6507         /* Interleave the new revision parent(s). */
6508         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6509                 push_rev_graph(graph->next, graph->parents->rev[i]);
6511         /* Lastly, put any remaining revisions. */
6512         for (i = graph->pos + 1; i < graph->size; i++)
6513                 push_rev_graph(graph->next, graph->rev[i]);
6516 static void
6517 update_rev_graph(struct view *view, struct rev_graph *graph)
6519         /* If this is the finalizing update ... */
6520         if (graph->commit)
6521                 prepare_rev_graph(graph);
6523         /* Graph visualization needs a one rev look-ahead,
6524          * so the first update doesn't visualize anything. */
6525         if (!graph->prev->commit)
6526                 return;
6528         if (view->lines > 2)
6529                 view->line[view->lines - 3].dirty = 1;
6530         if (view->lines > 1)
6531                 view->line[view->lines - 2].dirty = 1;
6532         draw_rev_graph(graph->prev);
6533         done_rev_graph(graph->prev->prev);
6537 /*
6538  * Main view backend
6539  */
6541 static const char *main_argv[SIZEOF_ARG] = {
6542         "git", "log", "--no-color", "--pretty=raw", "--parents",
6543                       "--topo-order", "%(head)", NULL
6544 };
6546 static bool
6547 main_draw(struct view *view, struct line *line, unsigned int lineno)
6549         struct commit *commit = line->data;
6551         if (!commit->author)
6552                 return FALSE;
6554         if (opt_date && draw_date(view, &commit->time))
6555                 return TRUE;
6557         if (opt_author && draw_author(view, commit->author))
6558                 return TRUE;
6560         if (opt_rev_graph && commit->graph_size &&
6561             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6562                 return TRUE;
6564         if (opt_show_refs && commit->refs) {
6565                 size_t i;
6567                 for (i = 0; i < commit->refs->size; i++) {
6568                         struct ref *ref = commit->refs->refs[i];
6569                         enum line_type type;
6571                         if (ref->head)
6572                                 type = LINE_MAIN_HEAD;
6573                         else if (ref->ltag)
6574                                 type = LINE_MAIN_LOCAL_TAG;
6575                         else if (ref->tag)
6576                                 type = LINE_MAIN_TAG;
6577                         else if (ref->tracked)
6578                                 type = LINE_MAIN_TRACKED;
6579                         else if (ref->remote)
6580                                 type = LINE_MAIN_REMOTE;
6581                         else
6582                                 type = LINE_MAIN_REF;
6584                         if (draw_text(view, type, "[", TRUE) ||
6585                             draw_text(view, type, ref->name, TRUE) ||
6586                             draw_text(view, type, "]", TRUE))
6587                                 return TRUE;
6589                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6590                                 return TRUE;
6591                 }
6592         }
6594         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6595         return TRUE;
6598 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6599 static bool
6600 main_read(struct view *view, char *line)
6602         static struct rev_graph *graph = graph_stacks;
6603         enum line_type type;
6604         struct commit *commit;
6606         if (!line) {
6607                 int i;
6609                 if (!view->lines && !view->parent)
6610                         die("No revisions match the given arguments.");
6611                 if (view->lines > 0) {
6612                         commit = view->line[view->lines - 1].data;
6613                         view->line[view->lines - 1].dirty = 1;
6614                         if (!commit->author) {
6615                                 view->lines--;
6616                                 free(commit);
6617                                 graph->commit = NULL;
6618                         }
6619                 }
6620                 update_rev_graph(view, graph);
6622                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6623                         clear_rev_graph(&graph_stacks[i]);
6624                 return TRUE;
6625         }
6627         type = get_line_type(line);
6628         if (type == LINE_COMMIT) {
6629                 commit = calloc(1, sizeof(struct commit));
6630                 if (!commit)
6631                         return FALSE;
6633                 line += STRING_SIZE("commit ");
6634                 if (*line == '-') {
6635                         graph->boundary = 1;
6636                         line++;
6637                 }
6639                 string_copy_rev(commit->id, line);
6640                 commit->refs = get_ref_list(commit->id);
6641                 graph->commit = commit;
6642                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6644                 while ((line = strchr(line, ' '))) {
6645                         line++;
6646                         push_rev_graph(graph->parents, line);
6647                         commit->has_parents = TRUE;
6648                 }
6649                 return TRUE;
6650         }
6652         if (!view->lines)
6653                 return TRUE;
6654         commit = view->line[view->lines - 1].data;
6656         switch (type) {
6657         case LINE_PARENT:
6658                 if (commit->has_parents)
6659                         break;
6660                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6661                 break;
6663         case LINE_AUTHOR:
6664                 parse_author_line(line + STRING_SIZE("author "),
6665                                   &commit->author, &commit->time);
6666                 update_rev_graph(view, graph);
6667                 graph = graph->next;
6668                 break;
6670         default:
6671                 /* Fill in the commit title if it has not already been set. */
6672                 if (commit->title[0])
6673                         break;
6675                 /* Require titles to start with a non-space character at the
6676                  * offset used by git log. */
6677                 if (strncmp(line, "    ", 4))
6678                         break;
6679                 line += 4;
6680                 /* Well, if the title starts with a whitespace character,
6681                  * try to be forgiving.  Otherwise we end up with no title. */
6682                 while (isspace(*line))
6683                         line++;
6684                 if (*line == '\0')
6685                         break;
6686                 /* FIXME: More graceful handling of titles; append "..." to
6687                  * shortened titles, etc. */
6689                 string_expand(commit->title, sizeof(commit->title), line, 1);
6690                 view->line[view->lines - 1].dirty = 1;
6691         }
6693         return TRUE;
6696 static enum request
6697 main_request(struct view *view, enum request request, struct line *line)
6699         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6701         switch (request) {
6702         case REQ_ENTER:
6703                 open_view(view, REQ_VIEW_DIFF, flags);
6704                 break;
6705         case REQ_REFRESH:
6706                 load_refs();
6707                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6708                 break;
6709         default:
6710                 return request;
6711         }
6713         return REQ_NONE;
6716 static bool
6717 grep_refs(struct ref_list *list, regex_t *regex)
6719         regmatch_t pmatch;
6720         size_t i;
6722         if (!opt_show_refs || !list)
6723                 return FALSE;
6725         for (i = 0; i < list->size; i++) {
6726                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6727                         return TRUE;
6728         }
6730         return FALSE;
6733 static bool
6734 main_grep(struct view *view, struct line *line)
6736         struct commit *commit = line->data;
6737         const char *text[] = {
6738                 commit->title,
6739                 opt_author ? commit->author : "",
6740                 opt_date ? mkdate(&commit->time) : "",
6741                 NULL
6742         };
6744         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6747 static void
6748 main_select(struct view *view, struct line *line)
6750         struct commit *commit = line->data;
6752         string_copy_rev(view->ref, commit->id);
6753         string_copy_rev(ref_commit, view->ref);
6756 static struct view_ops main_ops = {
6757         "commit",
6758         main_argv,
6759         NULL,
6760         main_read,
6761         main_draw,
6762         main_request,
6763         main_grep,
6764         main_select,
6765 };
6768 /*
6769  * Unicode / UTF-8 handling
6770  *
6771  * NOTE: Much of the following code for dealing with Unicode is derived from
6772  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6773  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6774  */
6776 static inline int
6777 unicode_width(unsigned long c)
6779         if (c >= 0x1100 &&
6780            (c <= 0x115f                         /* Hangul Jamo */
6781             || c == 0x2329
6782             || c == 0x232a
6783             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6784                                                 /* CJK ... Yi */
6785             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6786             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6787             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6788             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6789             || (c >= 0xffe0  && c <= 0xffe6)
6790             || (c >= 0x20000 && c <= 0x2fffd)
6791             || (c >= 0x30000 && c <= 0x3fffd)))
6792                 return 2;
6794         if (c == '\t')
6795                 return opt_tab_size;
6797         return 1;
6800 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6801  * Illegal bytes are set one. */
6802 static const unsigned char utf8_bytes[256] = {
6803         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,
6804         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,
6805         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,
6806         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,
6807         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,
6808         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,
6809         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,
6810         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,
6811 };
6813 static inline unsigned char
6814 utf8_char_length(const char *string, const char *end)
6816         int c = *(unsigned char *) string;
6818         return utf8_bytes[c];
6821 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6822 static inline unsigned long
6823 utf8_to_unicode(const char *string, size_t length)
6825         unsigned long unicode;
6827         switch (length) {
6828         case 1:
6829                 unicode  =   string[0];
6830                 break;
6831         case 2:
6832                 unicode  =  (string[0] & 0x1f) << 6;
6833                 unicode +=  (string[1] & 0x3f);
6834                 break;
6835         case 3:
6836                 unicode  =  (string[0] & 0x0f) << 12;
6837                 unicode += ((string[1] & 0x3f) << 6);
6838                 unicode +=  (string[2] & 0x3f);
6839                 break;
6840         case 4:
6841                 unicode  =  (string[0] & 0x0f) << 18;
6842                 unicode += ((string[1] & 0x3f) << 12);
6843                 unicode += ((string[2] & 0x3f) << 6);
6844                 unicode +=  (string[3] & 0x3f);
6845                 break;
6846         case 5:
6847                 unicode  =  (string[0] & 0x0f) << 24;
6848                 unicode += ((string[1] & 0x3f) << 18);
6849                 unicode += ((string[2] & 0x3f) << 12);
6850                 unicode += ((string[3] & 0x3f) << 6);
6851                 unicode +=  (string[4] & 0x3f);
6852                 break;
6853         case 6:
6854                 unicode  =  (string[0] & 0x01) << 30;
6855                 unicode += ((string[1] & 0x3f) << 24);
6856                 unicode += ((string[2] & 0x3f) << 18);
6857                 unicode += ((string[3] & 0x3f) << 12);
6858                 unicode += ((string[4] & 0x3f) << 6);
6859                 unicode +=  (string[5] & 0x3f);
6860                 break;
6861         default:
6862                 die("Invalid Unicode length");
6863         }
6865         /* Invalid characters could return the special 0xfffd value but NUL
6866          * should be just as good. */
6867         return unicode > 0xffff ? 0 : unicode;
6870 /* Calculates how much of string can be shown within the given maximum width
6871  * and sets trimmed parameter to non-zero value if all of string could not be
6872  * shown. If the reserve flag is TRUE, it will reserve at least one
6873  * trailing character, which can be useful when drawing a delimiter.
6874  *
6875  * Returns the number of bytes to output from string to satisfy max_width. */
6876 static size_t
6877 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6879         const char *string = *start;
6880         const char *end = strchr(string, '\0');
6881         unsigned char last_bytes = 0;
6882         size_t last_ucwidth = 0;
6884         *width = 0;
6885         *trimmed = 0;
6887         while (string < end) {
6888                 unsigned char bytes = utf8_char_length(string, end);
6889                 size_t ucwidth;
6890                 unsigned long unicode;
6892                 if (string + bytes > end)
6893                         break;
6895                 /* Change representation to figure out whether
6896                  * it is a single- or double-width character. */
6898                 unicode = utf8_to_unicode(string, bytes);
6899                 /* FIXME: Graceful handling of invalid Unicode character. */
6900                 if (!unicode)
6901                         break;
6903                 ucwidth = unicode_width(unicode);
6904                 if (skip > 0) {
6905                         skip -= ucwidth <= skip ? ucwidth : skip;
6906                         *start += bytes;
6907                 }
6908                 *width  += ucwidth;
6909                 if (*width > max_width) {
6910                         *trimmed = 1;
6911                         *width -= ucwidth;
6912                         if (reserve && *width == max_width) {
6913                                 string -= last_bytes;
6914                                 *width -= last_ucwidth;
6915                         }
6916                         break;
6917                 }
6919                 string  += bytes;
6920                 last_bytes = ucwidth ? bytes : 0;
6921                 last_ucwidth = ucwidth;
6922         }
6924         return string - *start;
6928 /*
6929  * Status management
6930  */
6932 /* Whether or not the curses interface has been initialized. */
6933 static bool cursed = FALSE;
6935 /* Terminal hacks and workarounds. */
6936 static bool use_scroll_redrawwin;
6937 static bool use_scroll_status_wclear;
6939 /* The status window is used for polling keystrokes. */
6940 static WINDOW *status_win;
6942 /* Reading from the prompt? */
6943 static bool input_mode = FALSE;
6945 static bool status_empty = FALSE;
6947 /* Update status and title window. */
6948 static void
6949 report(const char *msg, ...)
6951         struct view *view = display[current_view];
6953         if (input_mode)
6954                 return;
6956         if (!view) {
6957                 char buf[SIZEOF_STR];
6958                 va_list args;
6960                 va_start(args, msg);
6961                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6962                         buf[sizeof(buf) - 1] = 0;
6963                         buf[sizeof(buf) - 2] = '.';
6964                         buf[sizeof(buf) - 3] = '.';
6965                         buf[sizeof(buf) - 4] = '.';
6966                 }
6967                 va_end(args);
6968                 die("%s", buf);
6969         }
6971         if (!status_empty || *msg) {
6972                 va_list args;
6974                 va_start(args, msg);
6976                 wmove(status_win, 0, 0);
6977                 if (view->has_scrolled && use_scroll_status_wclear)
6978                         wclear(status_win);
6979                 if (*msg) {
6980                         vwprintw(status_win, msg, args);
6981                         status_empty = FALSE;
6982                 } else {
6983                         status_empty = TRUE;
6984                 }
6985                 wclrtoeol(status_win);
6986                 wnoutrefresh(status_win);
6988                 va_end(args);
6989         }
6991         update_view_title(view);
6994 /* Controls when nodelay should be in effect when polling user input. */
6995 static void
6996 set_nonblocking_input(bool loading)
6998         static unsigned int loading_views;
7000         if ((loading == FALSE && loading_views-- == 1) ||
7001             (loading == TRUE  && loading_views++ == 0))
7002                 nodelay(status_win, loading);
7005 static void
7006 init_display(void)
7008         const char *term;
7009         int x, y;
7011         /* Initialize the curses library */
7012         if (isatty(STDIN_FILENO)) {
7013                 cursed = !!initscr();
7014                 opt_tty = stdin;
7015         } else {
7016                 /* Leave stdin and stdout alone when acting as a pager. */
7017                 opt_tty = fopen("/dev/tty", "r+");
7018                 if (!opt_tty)
7019                         die("Failed to open /dev/tty");
7020                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7021         }
7023         if (!cursed)
7024                 die("Failed to initialize curses");
7026         nonl();         /* Disable conversion and detect newlines from input. */
7027         cbreak();       /* Take input chars one at a time, no wait for \n */
7028         noecho();       /* Don't echo input */
7029         leaveok(stdscr, FALSE);
7031         if (has_colors())
7032                 init_colors();
7034         getmaxyx(stdscr, y, x);
7035         status_win = newwin(1, 0, y - 1, 0);
7036         if (!status_win)
7037                 die("Failed to create status window");
7039         /* Enable keyboard mapping */
7040         keypad(status_win, TRUE);
7041         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7043         TABSIZE = opt_tab_size;
7044         if (opt_line_graphics) {
7045                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
7046         }
7048         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7049         if (term && !strcmp(term, "gnome-terminal")) {
7050                 /* In the gnome-terminal-emulator, the message from
7051                  * scrolling up one line when impossible followed by
7052                  * scrolling down one line causes corruption of the
7053                  * status line. This is fixed by calling wclear. */
7054                 use_scroll_status_wclear = TRUE;
7055                 use_scroll_redrawwin = FALSE;
7057         } else if (term && !strcmp(term, "xrvt-xpm")) {
7058                 /* No problems with full optimizations in xrvt-(unicode)
7059                  * and aterm. */
7060                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7062         } else {
7063                 /* When scrolling in (u)xterm the last line in the
7064                  * scrolling direction will update slowly. */
7065                 use_scroll_redrawwin = TRUE;
7066                 use_scroll_status_wclear = FALSE;
7067         }
7070 static int
7071 get_input(int prompt_position)
7073         struct view *view;
7074         int i, key, cursor_y, cursor_x;
7076         if (prompt_position)
7077                 input_mode = TRUE;
7079         while (TRUE) {
7080                 foreach_view (view, i) {
7081                         update_view(view);
7082                         if (view_is_displayed(view) && view->has_scrolled &&
7083                             use_scroll_redrawwin)
7084                                 redrawwin(view->win);
7085                         view->has_scrolled = FALSE;
7086                 }
7088                 /* Update the cursor position. */
7089                 if (prompt_position) {
7090                         getbegyx(status_win, cursor_y, cursor_x);
7091                         cursor_x = prompt_position;
7092                 } else {
7093                         view = display[current_view];
7094                         getbegyx(view->win, cursor_y, cursor_x);
7095                         cursor_x = view->width - 1;
7096                         cursor_y += view->lineno - view->offset;
7097                 }
7098                 setsyx(cursor_y, cursor_x);
7100                 /* Refresh, accept single keystroke of input */
7101                 doupdate();
7102                 key = wgetch(status_win);
7104                 /* wgetch() with nodelay() enabled returns ERR when
7105                  * there's no input. */
7106                 if (key == ERR) {
7108                 } else if (key == KEY_RESIZE) {
7109                         int height, width;
7111                         getmaxyx(stdscr, height, width);
7113                         wresize(status_win, 1, width);
7114                         mvwin(status_win, height - 1, 0);
7115                         wnoutrefresh(status_win);
7116                         resize_display();
7117                         redraw_display(TRUE);
7119                 } else {
7120                         input_mode = FALSE;
7121                         return key;
7122                 }
7123         }
7126 static char *
7127 prompt_input(const char *prompt, input_handler handler, void *data)
7129         enum input_status status = INPUT_OK;
7130         static char buf[SIZEOF_STR];
7131         size_t pos = 0;
7133         buf[pos] = 0;
7135         while (status == INPUT_OK || status == INPUT_SKIP) {
7136                 int key;
7138                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7139                 wclrtoeol(status_win);
7141                 key = get_input(pos + 1);
7142                 switch (key) {
7143                 case KEY_RETURN:
7144                 case KEY_ENTER:
7145                 case '\n':
7146                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7147                         break;
7149                 case KEY_BACKSPACE:
7150                         if (pos > 0)
7151                                 buf[--pos] = 0;
7152                         else
7153                                 status = INPUT_CANCEL;
7154                         break;
7156                 case KEY_ESC:
7157                         status = INPUT_CANCEL;
7158                         break;
7160                 default:
7161                         if (pos >= sizeof(buf)) {
7162                                 report("Input string too long");
7163                                 return NULL;
7164                         }
7166                         status = handler(data, buf, key);
7167                         if (status == INPUT_OK)
7168                                 buf[pos++] = (char) key;
7169                 }
7170         }
7172         /* Clear the status window */
7173         status_empty = FALSE;
7174         report("");
7176         if (status == INPUT_CANCEL)
7177                 return NULL;
7179         buf[pos++] = 0;
7181         return buf;
7184 static enum input_status
7185 prompt_yesno_handler(void *data, char *buf, int c)
7187         if (c == 'y' || c == 'Y')
7188                 return INPUT_STOP;
7189         if (c == 'n' || c == 'N')
7190                 return INPUT_CANCEL;
7191         return INPUT_SKIP;
7194 static bool
7195 prompt_yesno(const char *prompt)
7197         char prompt2[SIZEOF_STR];
7199         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7200                 return FALSE;
7202         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7205 static enum input_status
7206 read_prompt_handler(void *data, char *buf, int c)
7208         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7211 static char *
7212 read_prompt(const char *prompt)
7214         return prompt_input(prompt, read_prompt_handler, NULL);
7217 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7219         enum input_status status = INPUT_OK;
7220         int size = 0;
7222         while (items[size].text)
7223                 size++;
7225         while (status == INPUT_OK) {
7226                 const struct menu_item *item = &items[*selected];
7227                 int key;
7228                 int i;
7230                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7231                           prompt, *selected + 1, size);
7232                 if (item->hotkey)
7233                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7234                 wprintw(status_win, "%s", item->text);
7235                 wclrtoeol(status_win);
7237                 key = get_input(COLS - 1);
7238                 switch (key) {
7239                 case KEY_RETURN:
7240                 case KEY_ENTER:
7241                 case '\n':
7242                         status = INPUT_STOP;
7243                         break;
7245                 case KEY_LEFT:
7246                 case KEY_UP:
7247                         *selected = *selected - 1;
7248                         if (*selected < 0)
7249                                 *selected = size - 1;
7250                         break;
7252                 case KEY_RIGHT:
7253                 case KEY_DOWN:
7254                         *selected = (*selected + 1) % size;
7255                         break;
7257                 case KEY_ESC:
7258                         status = INPUT_CANCEL;
7259                         break;
7261                 default:
7262                         for (i = 0; items[i].text; i++)
7263                                 if (items[i].hotkey == key) {
7264                                         *selected = i;
7265                                         status = INPUT_STOP;
7266                                         break;
7267                                 }
7268                 }
7269         }
7271         /* Clear the status window */
7272         status_empty = FALSE;
7273         report("");
7275         return status != INPUT_CANCEL;
7278 /*
7279  * Repository properties
7280  */
7282 static struct ref **refs = NULL;
7283 static size_t refs_size = 0;
7285 static struct ref_list **ref_lists = NULL;
7286 static size_t ref_lists_size = 0;
7288 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7289 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7290 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7292 static int
7293 compare_refs(const void *ref1_, const void *ref2_)
7295         const struct ref *ref1 = *(const struct ref **)ref1_;
7296         const struct ref *ref2 = *(const struct ref **)ref2_;
7298         if (ref1->tag != ref2->tag)
7299                 return ref2->tag - ref1->tag;
7300         if (ref1->ltag != ref2->ltag)
7301                 return ref2->ltag - ref2->ltag;
7302         if (ref1->head != ref2->head)
7303                 return ref2->head - ref1->head;
7304         if (ref1->tracked != ref2->tracked)
7305                 return ref2->tracked - ref1->tracked;
7306         if (ref1->remote != ref2->remote)
7307                 return ref2->remote - ref1->remote;
7308         return strcmp(ref1->name, ref2->name);
7311 static void
7312 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7314         size_t i;
7316         for (i = 0; i < refs_size; i++)
7317                 if (!visitor(data, refs[i]))
7318                         break;
7321 static struct ref_list *
7322 get_ref_list(const char *id)
7324         struct ref_list *list;
7325         size_t i;
7327         for (i = 0; i < ref_lists_size; i++)
7328                 if (!strcmp(id, ref_lists[i]->id))
7329                         return ref_lists[i];
7331         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7332                 return NULL;
7333         list = calloc(1, sizeof(*list));
7334         if (!list)
7335                 return NULL;
7337         for (i = 0; i < refs_size; i++) {
7338                 if (!strcmp(id, refs[i]->id) &&
7339                     realloc_refs_list(&list->refs, list->size, 1))
7340                         list->refs[list->size++] = refs[i];
7341         }
7343         if (!list->refs) {
7344                 free(list);
7345                 return NULL;
7346         }
7348         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7349         ref_lists[ref_lists_size++] = list;
7350         return list;
7353 static int
7354 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7356         struct ref *ref = NULL;
7357         bool tag = FALSE;
7358         bool ltag = FALSE;
7359         bool remote = FALSE;
7360         bool tracked = FALSE;
7361         bool head = FALSE;
7362         int from = 0, to = refs_size - 1;
7364         if (!prefixcmp(name, "refs/tags/")) {
7365                 if (!suffixcmp(name, namelen, "^{}")) {
7366                         namelen -= 3;
7367                         name[namelen] = 0;
7368                 } else {
7369                         ltag = TRUE;
7370                 }
7372                 tag = TRUE;
7373                 namelen -= STRING_SIZE("refs/tags/");
7374                 name    += STRING_SIZE("refs/tags/");
7376         } else if (!prefixcmp(name, "refs/remotes/")) {
7377                 remote = TRUE;
7378                 namelen -= STRING_SIZE("refs/remotes/");
7379                 name    += STRING_SIZE("refs/remotes/");
7380                 tracked  = !strcmp(opt_remote, name);
7382         } else if (!prefixcmp(name, "refs/heads/")) {
7383                 namelen -= STRING_SIZE("refs/heads/");
7384                 name    += STRING_SIZE("refs/heads/");
7385                 head     = !strncmp(opt_head, name, namelen);
7387         } else if (!strcmp(name, "HEAD")) {
7388                 string_ncopy(opt_head_rev, id, idlen);
7389                 return OK;
7390         }
7392         /* If we are reloading or it's an annotated tag, replace the
7393          * previous SHA1 with the resolved commit id; relies on the fact
7394          * git-ls-remote lists the commit id of an annotated tag right
7395          * before the commit id it points to. */
7396         while (from <= to) {
7397                 size_t pos = (to + from) / 2;
7398                 int cmp = strcmp(name, refs[pos]->name);
7400                 if (!cmp) {
7401                         ref = refs[pos];
7402                         break;
7403                 }
7405                 if (cmp < 0)
7406                         to = pos - 1;
7407                 else
7408                         from = pos + 1;
7409         }
7411         if (!ref) {
7412                 if (!realloc_refs(&refs, refs_size, 1))
7413                         return ERR;
7414                 ref = calloc(1, sizeof(*ref) + namelen);
7415                 if (!ref)
7416                         return ERR;
7417                 memmove(refs + from + 1, refs + from,
7418                         (refs_size - from) * sizeof(*refs));
7419                 refs[from] = ref;
7420                 strncpy(ref->name, name, namelen);
7421                 refs_size++;
7422         }
7424         ref->head = head;
7425         ref->tag = tag;
7426         ref->ltag = ltag;
7427         ref->remote = remote;
7428         ref->tracked = tracked;
7429         string_copy_rev(ref->id, id);
7431         return OK;
7434 static int
7435 load_refs(void)
7437         const char *head_argv[] = {
7438                 "git", "symbolic-ref", "HEAD", NULL
7439         };
7440         static const char *ls_remote_argv[SIZEOF_ARG] = {
7441                 "git", "ls-remote", opt_git_dir, NULL
7442         };
7443         static bool init = FALSE;
7444         size_t i;
7446         if (!init) {
7447                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7448                 init = TRUE;
7449         }
7451         if (!*opt_git_dir)
7452                 return OK;
7454         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7455             !prefixcmp(opt_head, "refs/heads/")) {
7456                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7458                 memmove(opt_head, offset, strlen(offset) + 1);
7459         }
7461         for (i = 0; i < refs_size; i++)
7462                 refs[i]->id[0] = 0;
7464         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7465                 return ERR;
7467         /* Update the ref lists to reflect changes. */
7468         for (i = 0; i < ref_lists_size; i++) {
7469                 struct ref_list *list = ref_lists[i];
7470                 size_t old, new;
7472                 for (old = new = 0; old < list->size; old++)
7473                         if (!strcmp(list->id, list->refs[old]->id))
7474                                 list->refs[new++] = list->refs[old];
7475                 list->size = new;
7476         }
7478         return OK;
7481 static void
7482 set_remote_branch(const char *name, const char *value, size_t valuelen)
7484         if (!strcmp(name, ".remote")) {
7485                 string_ncopy(opt_remote, value, valuelen);
7487         } else if (*opt_remote && !strcmp(name, ".merge")) {
7488                 size_t from = strlen(opt_remote);
7490                 if (!prefixcmp(value, "refs/heads/"))
7491                         value += STRING_SIZE("refs/heads/");
7493                 if (!string_format_from(opt_remote, &from, "/%s", value))
7494                         opt_remote[0] = 0;
7495         }
7498 static void
7499 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7501         const char *argv[SIZEOF_ARG] = { name, "=" };
7502         int argc = 1 + (cmd == option_set_command);
7503         int error = ERR;
7505         if (!argv_from_string(argv, &argc, value))
7506                 config_msg = "Too many option arguments";
7507         else
7508                 error = cmd(argc, argv);
7510         if (error == ERR)
7511                 warn("Option 'tig.%s': %s", name, config_msg);
7514 static bool
7515 set_environment_variable(const char *name, const char *value)
7517         size_t len = strlen(name) + 1 + strlen(value) + 1;
7518         char *env = malloc(len);
7520         if (env &&
7521             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7522             putenv(env) == 0)
7523                 return TRUE;
7524         free(env);
7525         return FALSE;
7528 static void
7529 set_work_tree(const char *value)
7531         char cwd[SIZEOF_STR];
7533         if (!getcwd(cwd, sizeof(cwd)))
7534                 die("Failed to get cwd path: %s", strerror(errno));
7535         if (chdir(opt_git_dir) < 0)
7536                 die("Failed to chdir(%s): %s", strerror(errno));
7537         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7538                 die("Failed to get git path: %s", strerror(errno));
7539         if (chdir(cwd) < 0)
7540                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7541         if (chdir(value) < 0)
7542                 die("Failed to chdir(%s): %s", value, strerror(errno));
7543         if (!getcwd(cwd, sizeof(cwd)))
7544                 die("Failed to get cwd path: %s", strerror(errno));
7545         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7546                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7547         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7548                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7549         opt_is_inside_work_tree = TRUE;
7552 static int
7553 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7555         if (!strcmp(name, "i18n.commitencoding"))
7556                 string_ncopy(opt_encoding, value, valuelen);
7558         else if (!strcmp(name, "core.editor"))
7559                 string_ncopy(opt_editor, value, valuelen);
7561         else if (!strcmp(name, "core.worktree"))
7562                 set_work_tree(value);
7564         else if (!prefixcmp(name, "tig.color."))
7565                 set_repo_config_option(name + 10, value, option_color_command);
7567         else if (!prefixcmp(name, "tig.bind."))
7568                 set_repo_config_option(name + 9, value, option_bind_command);
7570         else if (!prefixcmp(name, "tig."))
7571                 set_repo_config_option(name + 4, value, option_set_command);
7573         else if (*opt_head && !prefixcmp(name, "branch.") &&
7574                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7575                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7577         return OK;
7580 static int
7581 load_git_config(void)
7583         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7585         return run_io_load(config_list_argv, "=", read_repo_config_option);
7588 static int
7589 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7591         if (!opt_git_dir[0]) {
7592                 string_ncopy(opt_git_dir, name, namelen);
7594         } else if (opt_is_inside_work_tree == -1) {
7595                 /* This can be 3 different values depending on the
7596                  * version of git being used. If git-rev-parse does not
7597                  * understand --is-inside-work-tree it will simply echo
7598                  * the option else either "true" or "false" is printed.
7599                  * Default to true for the unknown case. */
7600                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7602         } else if (*name == '.') {
7603                 string_ncopy(opt_cdup, name, namelen);
7605         } else {
7606                 string_ncopy(opt_prefix, name, namelen);
7607         }
7609         return OK;
7612 static int
7613 load_repo_info(void)
7615         const char *rev_parse_argv[] = {
7616                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7617                         "--show-cdup", "--show-prefix", NULL
7618         };
7620         return run_io_load(rev_parse_argv, "=", read_repo_info);
7624 /*
7625  * Main
7626  */
7628 static const char usage[] =
7629 "tig " TIG_VERSION " (" __DATE__ ")\n"
7630 "\n"
7631 "Usage: tig        [options] [revs] [--] [paths]\n"
7632 "   or: tig show   [options] [revs] [--] [paths]\n"
7633 "   or: tig blame  [rev] path\n"
7634 "   or: tig status\n"
7635 "   or: tig <      [git command output]\n"
7636 "\n"
7637 "Options:\n"
7638 "  -v, --version   Show version and exit\n"
7639 "  -h, --help      Show help message and exit";
7641 static void __NORETURN
7642 quit(int sig)
7644         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7645         if (cursed)
7646                 endwin();
7647         exit(0);
7650 static void __NORETURN
7651 die(const char *err, ...)
7653         va_list args;
7655         endwin();
7657         va_start(args, err);
7658         fputs("tig: ", stderr);
7659         vfprintf(stderr, err, args);
7660         fputs("\n", stderr);
7661         va_end(args);
7663         exit(1);
7666 static void
7667 warn(const char *msg, ...)
7669         va_list args;
7671         va_start(args, msg);
7672         fputs("tig warning: ", stderr);
7673         vfprintf(stderr, msg, args);
7674         fputs("\n", stderr);
7675         va_end(args);
7678 static enum request
7679 parse_options(int argc, const char *argv[])
7681         enum request request = REQ_VIEW_MAIN;
7682         const char *subcommand;
7683         bool seen_dashdash = FALSE;
7684         /* XXX: This is vulnerable to the user overriding options
7685          * required for the main view parser. */
7686         const char *custom_argv[SIZEOF_ARG] = {
7687                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7688                         "--topo-order", NULL
7689         };
7690         int i, j = 6;
7692         if (!isatty(STDIN_FILENO)) {
7693                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7694                 return REQ_VIEW_PAGER;
7695         }
7697         if (argc <= 1)
7698                 return REQ_NONE;
7700         subcommand = argv[1];
7701         if (!strcmp(subcommand, "status")) {
7702                 if (argc > 2)
7703                         warn("ignoring arguments after `%s'", subcommand);
7704                 return REQ_VIEW_STATUS;
7706         } else if (!strcmp(subcommand, "blame")) {
7707                 if (argc <= 2 || argc > 4)
7708                         die("invalid number of options to blame\n\n%s", usage);
7710                 i = 2;
7711                 if (argc == 4) {
7712                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7713                         i++;
7714                 }
7716                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7717                 return REQ_VIEW_BLAME;
7719         } else if (!strcmp(subcommand, "show")) {
7720                 request = REQ_VIEW_DIFF;
7722         } else {
7723                 subcommand = NULL;
7724         }
7726         if (subcommand) {
7727                 custom_argv[1] = subcommand;
7728                 j = 2;
7729         }
7731         for (i = 1 + !!subcommand; i < argc; i++) {
7732                 const char *opt = argv[i];
7734                 if (seen_dashdash || !strcmp(opt, "--")) {
7735                         seen_dashdash = TRUE;
7737                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7738                         printf("tig version %s\n", TIG_VERSION);
7739                         quit(0);
7741                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7742                         printf("%s\n", usage);
7743                         quit(0);
7744                 }
7746                 custom_argv[j++] = opt;
7747                 if (j >= ARRAY_SIZE(custom_argv))
7748                         die("command too long");
7749         }
7751         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7752                 die("Failed to format arguments");
7754         return request;
7757 int
7758 main(int argc, const char *argv[])
7760         enum request request = parse_options(argc, argv);
7761         struct view *view;
7762         size_t i;
7764         signal(SIGINT, quit);
7765         signal(SIGPIPE, SIG_IGN);
7767         if (setlocale(LC_ALL, "")) {
7768                 char *codeset = nl_langinfo(CODESET);
7770                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7771         }
7773         if (load_repo_info() == ERR)
7774                 die("Failed to load repo info.");
7776         if (load_options() == ERR)
7777                 die("Failed to load user config.");
7779         if (load_git_config() == ERR)
7780                 die("Failed to load repo config.");
7782         /* Require a git repository unless when running in pager mode. */
7783         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7784                 die("Not a git repository");
7786         if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7787                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7788                 if (opt_iconv_in == ICONV_NONE)
7789                         die("Failed to initialize character set conversion");
7790         }
7792         if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7793                 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7794                 if (opt_iconv_out == ICONV_NONE)
7795                         die("Failed to initialize character set conversion");
7796         }
7798         if (load_refs() == ERR)
7799                 die("Failed to load refs.");
7801         foreach_view (view, i)
7802                 argv_from_env(view->ops->argv, view->cmd_env);
7804         init_display();
7806         if (request != REQ_NONE)
7807                 open_view(NULL, request, OPEN_PREPARED);
7808         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7810         while (view_driver(display[current_view], request)) {
7811                 int key = get_input(0);
7813                 view = display[current_view];
7814                 request = get_keybinding(view->keymap, key);
7816                 /* Some low-level request handling. This keeps access to
7817                  * status_win restricted. */
7818                 switch (request) {
7819                 case REQ_PROMPT:
7820                 {
7821                         char *cmd = read_prompt(":");
7823                         if (cmd && isdigit(*cmd)) {
7824                                 int lineno = view->lineno + 1;
7826                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7827                                         select_view_line(view, lineno - 1);
7828                                         report("");
7829                                 } else {
7830                                         report("Unable to parse '%s' as a line number", cmd);
7831                                 }
7833                         } else if (cmd) {
7834                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7835                                 const char *argv[SIZEOF_ARG] = { "git" };
7836                                 int argc = 1;
7838                                 /* When running random commands, initially show the
7839                                  * command in the title. However, it maybe later be
7840                                  * overwritten if a commit line is selected. */
7841                                 string_ncopy(next->ref, cmd, strlen(cmd));
7843                                 if (!argv_from_string(argv, &argc, cmd)) {
7844                                         report("Too many arguments");
7845                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7846                                         report("Failed to format command");
7847                                 } else {
7848                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7849                                 }
7850                         }
7852                         request = REQ_NONE;
7853                         break;
7854                 }
7855                 case REQ_SEARCH:
7856                 case REQ_SEARCH_BACK:
7857                 {
7858                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7859                         char *search = read_prompt(prompt);
7861                         if (search)
7862                                 string_ncopy(opt_search, search, strlen(search));
7863                         else if (*opt_search)
7864                                 request = request == REQ_SEARCH ?
7865                                         REQ_FIND_NEXT :
7866                                         REQ_FIND_PREV;
7867                         else
7868                                 request = REQ_NONE;
7869                         break;
7870                 }
7871                 default:
7872                         break;
7873                 }
7874         }
7876         quit(0);
7878         return 0;