Code

cffa86d5fcafdd22544a19e7283f5e1f3241749d
[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 <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
73 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
74 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
75 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
77 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x)  (sizeof(x) - 1)
80 #define SIZEOF_STR      1024    /* Default string size. */
81 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG      32      /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT   'I'
88 #define REVGRAPH_MERGE  'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND  '^'
93 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT   (-1)
98 #define ICONV_NONE      ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST     /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
105 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define ID_COLS         8
109 #define MIN_VIEW_HEIGHT 4
111 #define NULL_ID         "0000000000000000000000000000000000000000"
113 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
115 #ifndef GIT_CONFIG
116 #define GIT_CONFIG "config"
117 #endif
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, 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 struct enum_map {
303         const char *name;
304         int namelen;
305         int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static bool
311 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
313         size_t namelen = strlen(name);
314         int i;
316         for (i = 0; i < map_size; i++)
317                 if (namelen == map[i].namelen &&
318                     !string_enum_compare(name, map[i].name, namelen)) {
319                         *value = map[i].value;
320                         return TRUE;
321                 }
323         return FALSE;
326 #define map_enum(attr, map, name) \
327         map_enum_do(map, ARRAY_SIZE(map), attr, name)
329 #define prefixcmp(str1, str2) \
330         strncmp(str1, str2, STRING_SIZE(str2))
332 static inline int
333 suffixcmp(const char *str, int slen, const char *suffix)
335         size_t len = slen >= 0 ? slen : strlen(str);
336         size_t suffixlen = strlen(suffix);
338         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
342 static const char *
343 mkdate(const time_t *time)
345         static char buf[DATE_COLS + 1];
346         struct tm tm;
348         gmtime_r(time, &tm);
349         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
353 static bool
354 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
356         int valuelen;
358         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
359                 bool advance = cmd[valuelen] != 0;
361                 cmd[valuelen] = 0;
362                 argv[(*argc)++] = chomp_string(cmd);
363                 cmd = chomp_string(cmd + valuelen + advance);
364         }
366         if (*argc < SIZEOF_ARG)
367                 argv[*argc] = NULL;
368         return *argc < SIZEOF_ARG;
371 static void
372 argv_from_env(const char **argv, const char *name)
374         char *env = argv ? getenv(name) : NULL;
375         int argc = 0;
377         if (env && *env)
378                 env = strdup(env);
379         if (env && !argv_from_string(argv, &argc, env))
380                 die("Too many arguments in the `%s` environment variable", name);
384 /*
385  * Executing external commands.
386  */
388 enum io_type {
389         IO_FD,                  /* File descriptor based IO. */
390         IO_BG,                  /* Execute command in the background. */
391         IO_FG,                  /* Execute command with same std{in,out,err}. */
392         IO_RD,                  /* Read only fork+exec IO. */
393         IO_WR,                  /* Write only fork+exec IO. */
394         IO_AP,                  /* Append fork+exec output to file. */
395 };
397 struct io {
398         enum io_type type;      /* The requested type of pipe. */
399         const char *dir;        /* Directory from which to execute. */
400         pid_t pid;              /* Pipe for reading or writing. */
401         int pipe;               /* Pipe end for reading or writing. */
402         int error;              /* Error status. */
403         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
404         char *buf;              /* Read buffer. */
405         size_t bufalloc;        /* Allocated buffer size. */
406         size_t bufsize;         /* Buffer content size. */
407         char *bufpos;           /* Current buffer position. */
408         unsigned int eof:1;     /* Has end of file been reached. */
409 };
411 static void
412 reset_io(struct io *io)
414         io->pipe = -1;
415         io->pid = 0;
416         io->buf = io->bufpos = NULL;
417         io->bufalloc = io->bufsize = 0;
418         io->error = 0;
419         io->eof = 0;
422 static void
423 init_io(struct io *io, const char *dir, enum io_type type)
425         reset_io(io);
426         io->type = type;
427         io->dir = dir;
430 static bool
431 init_io_rd(struct io *io, const char *argv[], const char *dir,
432                 enum format_flags flags)
434         init_io(io, dir, IO_RD);
435         return format_argv(io->argv, argv, flags);
438 static bool
439 io_open(struct io *io, const char *name)
441         init_io(io, NULL, IO_FD);
442         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
443         if (io->pipe == -1)
444                 io->error = errno;
445         return io->pipe != -1;
448 static bool
449 kill_io(struct io *io)
451         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
454 static bool
455 done_io(struct io *io)
457         pid_t pid = io->pid;
459         if (io->pipe != -1)
460                 close(io->pipe);
461         free(io->buf);
462         reset_io(io);
464         while (pid > 0) {
465                 int status;
466                 pid_t waiting = waitpid(pid, &status, 0);
468                 if (waiting < 0) {
469                         if (errno == EINTR)
470                                 continue;
471                         report("waitpid failed (%s)", strerror(errno));
472                         return FALSE;
473                 }
475                 return waiting == pid &&
476                        !WIFSIGNALED(status) &&
477                        WIFEXITED(status) &&
478                        !WEXITSTATUS(status);
479         }
481         return TRUE;
484 static bool
485 start_io(struct io *io)
487         int pipefds[2] = { -1, -1 };
489         if (io->type == IO_FD)
490                 return TRUE;
492         if ((io->type == IO_RD || io->type == IO_WR) &&
493             pipe(pipefds) < 0)
494                 return FALSE;
495         else if (io->type == IO_AP)
496                 pipefds[1] = io->pipe;
498         if ((io->pid = fork())) {
499                 if (pipefds[!(io->type == IO_WR)] != -1)
500                         close(pipefds[!(io->type == IO_WR)]);
501                 if (io->pid != -1) {
502                         io->pipe = pipefds[!!(io->type == IO_WR)];
503                         return TRUE;
504                 }
506         } else {
507                 if (io->type != IO_FG) {
508                         int devnull = open("/dev/null", O_RDWR);
509                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
510                         int writefd = (io->type == IO_RD || io->type == IO_AP)
511                                                         ? pipefds[1] : devnull;
513                         dup2(readfd,  STDIN_FILENO);
514                         dup2(writefd, STDOUT_FILENO);
515                         dup2(devnull, STDERR_FILENO);
517                         close(devnull);
518                         if (pipefds[0] != -1)
519                                 close(pipefds[0]);
520                         if (pipefds[1] != -1)
521                                 close(pipefds[1]);
522                 }
524                 if (io->dir && *io->dir && chdir(io->dir) == -1)
525                         die("Failed to change directory: %s", strerror(errno));
527                 execvp(io->argv[0], (char *const*) io->argv);
528                 die("Failed to execute program: %s", strerror(errno));
529         }
531         if (pipefds[!!(io->type == IO_WR)] != -1)
532                 close(pipefds[!!(io->type == IO_WR)]);
533         return FALSE;
536 static bool
537 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
539         init_io(io, dir, type);
540         if (!format_argv(io->argv, argv, FORMAT_NONE))
541                 return FALSE;
542         return start_io(io);
545 static int
546 run_io_do(struct io *io)
548         return start_io(io) && done_io(io);
551 static int
552 run_io_bg(const char **argv)
554         struct io io = {};
556         init_io(&io, NULL, IO_BG);
557         if (!format_argv(io.argv, argv, FORMAT_NONE))
558                 return FALSE;
559         return run_io_do(&io);
562 static bool
563 run_io_fg(const char **argv, const char *dir)
565         struct io io = {};
567         init_io(&io, dir, IO_FG);
568         if (!format_argv(io.argv, argv, FORMAT_NONE))
569                 return FALSE;
570         return run_io_do(&io);
573 static bool
574 run_io_append(const char **argv, enum format_flags flags, int fd)
576         struct io io = {};
578         init_io(&io, NULL, IO_AP);
579         io.pipe = fd;
580         if (format_argv(io.argv, argv, flags))
581                 return run_io_do(&io);
582         close(fd);
583         return FALSE;
586 static bool
587 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
589         return init_io_rd(io, argv, NULL, flags) && start_io(io);
592 static bool
593 io_eof(struct io *io)
595         return io->eof;
598 static int
599 io_error(struct io *io)
601         return io->error;
604 static char *
605 io_strerror(struct io *io)
607         return strerror(io->error);
610 static bool
611 io_can_read(struct io *io)
613         struct timeval tv = { 0, 500 };
614         fd_set fds;
616         FD_ZERO(&fds);
617         FD_SET(io->pipe, &fds);
619         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
622 static ssize_t
623 io_read(struct io *io, void *buf, size_t bufsize)
625         do {
626                 ssize_t readsize = read(io->pipe, buf, bufsize);
628                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
629                         continue;
630                 else if (readsize == -1)
631                         io->error = errno;
632                 else if (readsize == 0)
633                         io->eof = 1;
634                 return readsize;
635         } while (1);
638 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
640 static char *
641 io_get(struct io *io, int c, bool can_read)
643         char *eol;
644         ssize_t readsize;
646         while (TRUE) {
647                 if (io->bufsize > 0) {
648                         eol = memchr(io->bufpos, c, io->bufsize);
649                         if (eol) {
650                                 char *line = io->bufpos;
652                                 *eol = 0;
653                                 io->bufpos = eol + 1;
654                                 io->bufsize -= io->bufpos - line;
655                                 return line;
656                         }
657                 }
659                 if (io_eof(io)) {
660                         if (io->bufsize) {
661                                 io->bufpos[io->bufsize] = 0;
662                                 io->bufsize = 0;
663                                 return io->bufpos;
664                         }
665                         return NULL;
666                 }
668                 if (!can_read)
669                         return NULL;
671                 if (io->bufsize > 0 && io->bufpos > io->buf)
672                         memmove(io->buf, io->bufpos, io->bufsize);
674                 if (io->bufalloc == io->bufsize) {
675                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
676                                 return NULL;
677                         io->bufalloc += BUFSIZ;
678                 }
680                 io->bufpos = io->buf;
681                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
682                 if (io_error(io))
683                         return NULL;
684                 io->bufsize += readsize;
685         }
688 static bool
689 io_write(struct io *io, const void *buf, size_t bufsize)
691         size_t written = 0;
693         while (!io_error(io) && written < bufsize) {
694                 ssize_t size;
696                 size = write(io->pipe, buf + written, bufsize - written);
697                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
698                         continue;
699                 else if (size == -1)
700                         io->error = errno;
701                 else
702                         written += size;
703         }
705         return written == bufsize;
708 static bool
709 io_read_buf(struct io *io, char buf[], size_t bufsize)
711         char *result = io_get(io, '\n', TRUE);
713         if (result) {
714                 result = chomp_string(result);
715                 string_ncopy_do(buf, bufsize, result, strlen(result));
716         }
718         return done_io(io) && result;
721 static bool
722 run_io_buf(const char **argv, char buf[], size_t bufsize)
724         struct io io = {};
726         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
729 static int
730 io_load(struct io *io, const char *separators,
731         int (*read_property)(char *, size_t, char *, size_t))
733         char *name;
734         int state = OK;
736         if (!start_io(io))
737                 return ERR;
739         while (state == OK && (name = io_get(io, '\n', TRUE))) {
740                 char *value;
741                 size_t namelen;
742                 size_t valuelen;
744                 name = chomp_string(name);
745                 namelen = strcspn(name, separators);
747                 if (name[namelen]) {
748                         name[namelen] = 0;
749                         value = chomp_string(name + namelen + 1);
750                         valuelen = strlen(value);
752                 } else {
753                         value = "";
754                         valuelen = 0;
755                 }
757                 state = read_property(name, namelen, value, valuelen);
758         }
760         if (state != ERR && io_error(io))
761                 state = ERR;
762         done_io(io);
764         return state;
767 static int
768 run_io_load(const char **argv, const char *separators,
769             int (*read_property)(char *, size_t, char *, size_t))
771         struct io io = {};
773         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
774                 ? io_load(&io, separators, read_property) : ERR;
778 /*
779  * User requests
780  */
782 #define REQ_INFO \
783         /* XXX: Keep the view request first and in sync with views[]. */ \
784         REQ_GROUP("View switching") \
785         REQ_(VIEW_MAIN,         "Show main view"), \
786         REQ_(VIEW_DIFF,         "Show diff view"), \
787         REQ_(VIEW_LOG,          "Show log view"), \
788         REQ_(VIEW_TREE,         "Show tree view"), \
789         REQ_(VIEW_BLOB,         "Show blob view"), \
790         REQ_(VIEW_BLAME,        "Show blame view"), \
791         REQ_(VIEW_BRANCH,       "Show branch view"), \
792         REQ_(VIEW_HELP,         "Show help page"), \
793         REQ_(VIEW_PAGER,        "Show pager view"), \
794         REQ_(VIEW_STATUS,       "Show status view"), \
795         REQ_(VIEW_STAGE,        "Show stage view"), \
796         \
797         REQ_GROUP("View manipulation") \
798         REQ_(ENTER,             "Enter current line and scroll"), \
799         REQ_(NEXT,              "Move to next"), \
800         REQ_(PREVIOUS,          "Move to previous"), \
801         REQ_(PARENT,            "Move to parent"), \
802         REQ_(VIEW_NEXT,         "Move focus to next view"), \
803         REQ_(REFRESH,           "Reload and refresh"), \
804         REQ_(MAXIMIZE,          "Maximize the current view"), \
805         REQ_(VIEW_CLOSE,        "Close the current view"), \
806         REQ_(QUIT,              "Close all views and quit"), \
807         \
808         REQ_GROUP("View specific requests") \
809         REQ_(STATUS_UPDATE,     "Update file status"), \
810         REQ_(STATUS_REVERT,     "Revert file changes"), \
811         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
812         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
813         \
814         REQ_GROUP("Cursor navigation") \
815         REQ_(MOVE_UP,           "Move cursor one line up"), \
816         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
817         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
818         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
819         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
820         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
821         \
822         REQ_GROUP("Scrolling") \
823         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
824         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
825         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
826         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
827         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
828         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
829         \
830         REQ_GROUP("Searching") \
831         REQ_(SEARCH,            "Search the view"), \
832         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
833         REQ_(FIND_NEXT,         "Find next search match"), \
834         REQ_(FIND_PREV,         "Find previous search match"), \
835         \
836         REQ_GROUP("Option manipulation") \
837         REQ_(OPTIONS,           "Open option menu"), \
838         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
839         REQ_(TOGGLE_DATE,       "Toggle date display"), \
840         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
841         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
842         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
843         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
844         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
845         \
846         REQ_GROUP("Misc") \
847         REQ_(PROMPT,            "Bring up the prompt"), \
848         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
849         REQ_(SHOW_VERSION,      "Show version information"), \
850         REQ_(STOP_LOADING,      "Stop all loading views"), \
851         REQ_(EDIT,              "Open in editor"), \
852         REQ_(NONE,              "Do nothing")
855 /* User action requests. */
856 enum request {
857 #define REQ_GROUP(help)
858 #define REQ_(req, help) REQ_##req
860         /* Offset all requests to avoid conflicts with ncurses getch values. */
861         REQ_OFFSET = KEY_MAX + 1,
862         REQ_INFO
864 #undef  REQ_GROUP
865 #undef  REQ_
866 };
868 struct request_info {
869         enum request request;
870         const char *name;
871         int namelen;
872         const char *help;
873 };
875 static const struct request_info req_info[] = {
876 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
877 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
878         REQ_INFO
879 #undef  REQ_GROUP
880 #undef  REQ_
881 };
883 static enum request
884 get_request(const char *name)
886         int namelen = strlen(name);
887         int i;
889         for (i = 0; i < ARRAY_SIZE(req_info); i++)
890                 if (req_info[i].namelen == namelen &&
891                     !string_enum_compare(req_info[i].name, name, namelen))
892                         return req_info[i].request;
894         return REQ_NONE;
898 /*
899  * Options
900  */
902 /* Option and state variables. */
903 static bool opt_date                    = TRUE;
904 static bool opt_author                  = TRUE;
905 static bool opt_line_number             = FALSE;
906 static bool opt_line_graphics           = TRUE;
907 static bool opt_rev_graph               = FALSE;
908 static bool opt_show_refs               = TRUE;
909 static int opt_num_interval             = 5;
910 static double opt_hscroll               = 0.50;
911 static double opt_scale_split_view      = 2.0 / 3.0;
912 static int opt_tab_size                 = 8;
913 static int opt_author_cols              = 19;
914 static char opt_path[SIZEOF_STR]        = "";
915 static char opt_file[SIZEOF_STR]        = "";
916 static char opt_ref[SIZEOF_REF]         = "";
917 static char opt_head[SIZEOF_REF]        = "";
918 static char opt_head_rev[SIZEOF_REV]    = "";
919 static char opt_remote[SIZEOF_REF]      = "";
920 static char opt_encoding[20]            = "UTF-8";
921 static bool opt_utf8                    = TRUE;
922 static char opt_codeset[20]             = "UTF-8";
923 static iconv_t opt_iconv                = ICONV_NONE;
924 static char opt_search[SIZEOF_STR]      = "";
925 static char opt_cdup[SIZEOF_STR]        = "";
926 static char opt_prefix[SIZEOF_STR]      = "";
927 static char opt_git_dir[SIZEOF_STR]     = "";
928 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
929 static char opt_editor[SIZEOF_STR]      = "";
930 static FILE *opt_tty                    = NULL;
932 #define is_initial_commit()     (!*opt_head_rev)
933 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
936 /*
937  * Line-oriented content detection.
938  */
940 #define LINE_INFO \
941 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
942 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
943 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
944 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
945 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
946 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
947 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
948 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
949 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
950 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
951 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
952 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
953 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
954 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
955 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
956 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
957 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
958 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
959 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
960 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
961 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
962 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
963 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
964 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
965 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
966 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
967 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
968 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
969 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
970 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
971 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
972 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
973 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
974 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
975 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
976 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
977 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
978 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
979 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
980 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
981 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
982 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
983 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
984 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
985 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
986 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
987 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
988 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
989 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
990 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
991 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
992 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
993 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
994 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
995 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
997 enum line_type {
998 #define LINE(type, line, fg, bg, attr) \
999         LINE_##type
1000         LINE_INFO,
1001         LINE_NONE
1002 #undef  LINE
1003 };
1005 struct line_info {
1006         const char *name;       /* Option name. */
1007         int namelen;            /* Size of option name. */
1008         const char *line;       /* The start of line to match. */
1009         int linelen;            /* Size of string to match. */
1010         int fg, bg, attr;       /* Color and text attributes for the lines. */
1011 };
1013 static struct line_info line_info[] = {
1014 #define LINE(type, line, fg, bg, attr) \
1015         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1016         LINE_INFO
1017 #undef  LINE
1018 };
1020 static enum line_type
1021 get_line_type(const char *line)
1023         int linelen = strlen(line);
1024         enum line_type type;
1026         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1027                 /* Case insensitive search matches Signed-off-by lines better. */
1028                 if (linelen >= line_info[type].linelen &&
1029                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1030                         return type;
1032         return LINE_DEFAULT;
1035 static inline int
1036 get_line_attr(enum line_type type)
1038         assert(type < ARRAY_SIZE(line_info));
1039         return COLOR_PAIR(type) | line_info[type].attr;
1042 static struct line_info *
1043 get_line_info(const char *name)
1045         size_t namelen = strlen(name);
1046         enum line_type type;
1048         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1049                 if (namelen == line_info[type].namelen &&
1050                     !string_enum_compare(line_info[type].name, name, namelen))
1051                         return &line_info[type];
1053         return NULL;
1056 static void
1057 init_colors(void)
1059         int default_bg = line_info[LINE_DEFAULT].bg;
1060         int default_fg = line_info[LINE_DEFAULT].fg;
1061         enum line_type type;
1063         start_color();
1065         if (assume_default_colors(default_fg, default_bg) == ERR) {
1066                 default_bg = COLOR_BLACK;
1067                 default_fg = COLOR_WHITE;
1068         }
1070         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1071                 struct line_info *info = &line_info[type];
1072                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1073                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1075                 init_pair(type, fg, bg);
1076         }
1079 struct line {
1080         enum line_type type;
1082         /* State flags */
1083         unsigned int selected:1;
1084         unsigned int dirty:1;
1085         unsigned int cleareol:1;
1087         void *data;             /* User data */
1088 };
1091 /*
1092  * Keys
1093  */
1095 struct keybinding {
1096         int alias;
1097         enum request request;
1098 };
1100 static const struct keybinding default_keybindings[] = {
1101         /* View switching */
1102         { 'm',          REQ_VIEW_MAIN },
1103         { 'd',          REQ_VIEW_DIFF },
1104         { 'l',          REQ_VIEW_LOG },
1105         { 't',          REQ_VIEW_TREE },
1106         { 'f',          REQ_VIEW_BLOB },
1107         { 'B',          REQ_VIEW_BLAME },
1108         { 'H',          REQ_VIEW_BRANCH },
1109         { 'p',          REQ_VIEW_PAGER },
1110         { 'h',          REQ_VIEW_HELP },
1111         { 'S',          REQ_VIEW_STATUS },
1112         { 'c',          REQ_VIEW_STAGE },
1114         /* View manipulation */
1115         { 'q',          REQ_VIEW_CLOSE },
1116         { KEY_TAB,      REQ_VIEW_NEXT },
1117         { KEY_RETURN,   REQ_ENTER },
1118         { KEY_UP,       REQ_PREVIOUS },
1119         { KEY_DOWN,     REQ_NEXT },
1120         { 'R',          REQ_REFRESH },
1121         { KEY_F(5),     REQ_REFRESH },
1122         { 'O',          REQ_MAXIMIZE },
1124         /* Cursor navigation */
1125         { 'k',          REQ_MOVE_UP },
1126         { 'j',          REQ_MOVE_DOWN },
1127         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1128         { KEY_END,      REQ_MOVE_LAST_LINE },
1129         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1130         { ' ',          REQ_MOVE_PAGE_DOWN },
1131         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1132         { 'b',          REQ_MOVE_PAGE_UP },
1133         { '-',          REQ_MOVE_PAGE_UP },
1135         /* Scrolling */
1136         { KEY_LEFT,     REQ_SCROLL_LEFT },
1137         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1138         { KEY_IC,       REQ_SCROLL_LINE_UP },
1139         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1140         { 'w',          REQ_SCROLL_PAGE_UP },
1141         { 's',          REQ_SCROLL_PAGE_DOWN },
1143         /* Searching */
1144         { '/',          REQ_SEARCH },
1145         { '?',          REQ_SEARCH_BACK },
1146         { 'n',          REQ_FIND_NEXT },
1147         { 'N',          REQ_FIND_PREV },
1149         /* Misc */
1150         { 'Q',          REQ_QUIT },
1151         { 'z',          REQ_STOP_LOADING },
1152         { 'v',          REQ_SHOW_VERSION },
1153         { 'r',          REQ_SCREEN_REDRAW },
1154         { 'o',          REQ_OPTIONS },
1155         { '.',          REQ_TOGGLE_LINENO },
1156         { 'D',          REQ_TOGGLE_DATE },
1157         { 'A',          REQ_TOGGLE_AUTHOR },
1158         { 'g',          REQ_TOGGLE_REV_GRAPH },
1159         { 'F',          REQ_TOGGLE_REFS },
1160         { 'I',          REQ_TOGGLE_SORT_ORDER },
1161         { 'i',          REQ_TOGGLE_SORT_FIELD },
1162         { ':',          REQ_PROMPT },
1163         { 'u',          REQ_STATUS_UPDATE },
1164         { '!',          REQ_STATUS_REVERT },
1165         { 'M',          REQ_STATUS_MERGE },
1166         { '@',          REQ_STAGE_NEXT },
1167         { ',',          REQ_PARENT },
1168         { 'e',          REQ_EDIT },
1169 };
1171 #define KEYMAP_INFO \
1172         KEYMAP_(GENERIC), \
1173         KEYMAP_(MAIN), \
1174         KEYMAP_(DIFF), \
1175         KEYMAP_(LOG), \
1176         KEYMAP_(TREE), \
1177         KEYMAP_(BLOB), \
1178         KEYMAP_(BLAME), \
1179         KEYMAP_(BRANCH), \
1180         KEYMAP_(PAGER), \
1181         KEYMAP_(HELP), \
1182         KEYMAP_(STATUS), \
1183         KEYMAP_(STAGE)
1185 enum keymap {
1186 #define KEYMAP_(name) KEYMAP_##name
1187         KEYMAP_INFO
1188 #undef  KEYMAP_
1189 };
1191 static const struct enum_map keymap_table[] = {
1192 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1193         KEYMAP_INFO
1194 #undef  KEYMAP_
1195 };
1197 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1199 struct keybinding_table {
1200         struct keybinding *data;
1201         size_t size;
1202 };
1204 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1206 static void
1207 add_keybinding(enum keymap keymap, enum request request, int key)
1209         struct keybinding_table *table = &keybindings[keymap];
1211         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1212         if (!table->data)
1213                 die("Failed to allocate keybinding");
1214         table->data[table->size].alias = key;
1215         table->data[table->size++].request = request;
1218 /* Looks for a key binding first in the given map, then in the generic map, and
1219  * lastly in the default keybindings. */
1220 static enum request
1221 get_keybinding(enum keymap keymap, int key)
1223         size_t i;
1225         for (i = 0; i < keybindings[keymap].size; i++)
1226                 if (keybindings[keymap].data[i].alias == key)
1227                         return keybindings[keymap].data[i].request;
1229         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1230                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1231                         return keybindings[KEYMAP_GENERIC].data[i].request;
1233         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1234                 if (default_keybindings[i].alias == key)
1235                         return default_keybindings[i].request;
1237         return (enum request) key;
1241 struct key {
1242         const char *name;
1243         int value;
1244 };
1246 static const struct key key_table[] = {
1247         { "Enter",      KEY_RETURN },
1248         { "Space",      ' ' },
1249         { "Backspace",  KEY_BACKSPACE },
1250         { "Tab",        KEY_TAB },
1251         { "Escape",     KEY_ESC },
1252         { "Left",       KEY_LEFT },
1253         { "Right",      KEY_RIGHT },
1254         { "Up",         KEY_UP },
1255         { "Down",       KEY_DOWN },
1256         { "Insert",     KEY_IC },
1257         { "Delete",     KEY_DC },
1258         { "Hash",       '#' },
1259         { "Home",       KEY_HOME },
1260         { "End",        KEY_END },
1261         { "PageUp",     KEY_PPAGE },
1262         { "PageDown",   KEY_NPAGE },
1263         { "F1",         KEY_F(1) },
1264         { "F2",         KEY_F(2) },
1265         { "F3",         KEY_F(3) },
1266         { "F4",         KEY_F(4) },
1267         { "F5",         KEY_F(5) },
1268         { "F6",         KEY_F(6) },
1269         { "F7",         KEY_F(7) },
1270         { "F8",         KEY_F(8) },
1271         { "F9",         KEY_F(9) },
1272         { "F10",        KEY_F(10) },
1273         { "F11",        KEY_F(11) },
1274         { "F12",        KEY_F(12) },
1275 };
1277 static int
1278 get_key_value(const char *name)
1280         int i;
1282         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1283                 if (!strcasecmp(key_table[i].name, name))
1284                         return key_table[i].value;
1286         if (strlen(name) == 1 && isprint(*name))
1287                 return (int) *name;
1289         return ERR;
1292 static const char *
1293 get_key_name(int key_value)
1295         static char key_char[] = "'X'";
1296         const char *seq = NULL;
1297         int key;
1299         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1300                 if (key_table[key].value == key_value)
1301                         seq = key_table[key].name;
1303         if (seq == NULL &&
1304             key_value < 127 &&
1305             isprint(key_value)) {
1306                 key_char[1] = (char) key_value;
1307                 seq = key_char;
1308         }
1310         return seq ? seq : "(no key)";
1313 static const char *
1314 get_key(enum request request)
1316         static char buf[BUFSIZ];
1317         size_t pos = 0;
1318         char *sep = "";
1319         int i;
1321         buf[pos] = 0;
1323         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1324                 const struct keybinding *keybinding = &default_keybindings[i];
1326                 if (keybinding->request != request)
1327                         continue;
1329                 if (!string_format_from(buf, &pos, "%s%s", sep,
1330                                         get_key_name(keybinding->alias)))
1331                         return "Too many keybindings!";
1332                 sep = ", ";
1333         }
1335         return buf;
1338 struct run_request {
1339         enum keymap keymap;
1340         int key;
1341         const char *argv[SIZEOF_ARG];
1342 };
1344 static struct run_request *run_request;
1345 static size_t run_requests;
1347 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1349 static enum request
1350 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1352         struct run_request *req;
1354         if (argc >= ARRAY_SIZE(req->argv) - 1)
1355                 return REQ_NONE;
1357         if (!realloc_run_requests(&run_request, run_requests, 1))
1358                 return REQ_NONE;
1360         req = &run_request[run_requests];
1361         req->keymap = keymap;
1362         req->key = key;
1363         req->argv[0] = NULL;
1365         if (!format_argv(req->argv, argv, FORMAT_NONE))
1366                 return REQ_NONE;
1368         return REQ_NONE + ++run_requests;
1371 static struct run_request *
1372 get_run_request(enum request request)
1374         if (request <= REQ_NONE)
1375                 return NULL;
1376         return &run_request[request - REQ_NONE - 1];
1379 static void
1380 add_builtin_run_requests(void)
1382         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1383         const char *commit[] = { "git", "commit", NULL };
1384         const char *gc[] = { "git", "gc", NULL };
1385         struct {
1386                 enum keymap keymap;
1387                 int key;
1388                 int argc;
1389                 const char **argv;
1390         } reqs[] = {
1391                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1392                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1393                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1394         };
1395         int i;
1397         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1398                 enum request req;
1400                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1401                 if (req != REQ_NONE)
1402                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1403         }
1406 /*
1407  * User config file handling.
1408  */
1410 static int   config_lineno;
1411 static bool  config_errors;
1412 static const char *config_msg;
1414 static const struct enum_map color_map[] = {
1415 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1416         COLOR_MAP(DEFAULT),
1417         COLOR_MAP(BLACK),
1418         COLOR_MAP(BLUE),
1419         COLOR_MAP(CYAN),
1420         COLOR_MAP(GREEN),
1421         COLOR_MAP(MAGENTA),
1422         COLOR_MAP(RED),
1423         COLOR_MAP(WHITE),
1424         COLOR_MAP(YELLOW),
1425 };
1427 static const struct enum_map attr_map[] = {
1428 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1429         ATTR_MAP(NORMAL),
1430         ATTR_MAP(BLINK),
1431         ATTR_MAP(BOLD),
1432         ATTR_MAP(DIM),
1433         ATTR_MAP(REVERSE),
1434         ATTR_MAP(STANDOUT),
1435         ATTR_MAP(UNDERLINE),
1436 };
1438 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1440 static int parse_step(double *opt, const char *arg)
1442         *opt = atoi(arg);
1443         if (!strchr(arg, '%'))
1444                 return OK;
1446         /* "Shift down" so 100% and 1 does not conflict. */
1447         *opt = (*opt - 1) / 100;
1448         if (*opt >= 1.0) {
1449                 *opt = 0.99;
1450                 config_msg = "Step value larger than 100%";
1451                 return ERR;
1452         }
1453         if (*opt < 0.0) {
1454                 *opt = 1;
1455                 config_msg = "Invalid step value";
1456                 return ERR;
1457         }
1458         return OK;
1461 static int
1462 parse_int(int *opt, const char *arg, int min, int max)
1464         int value = atoi(arg);
1466         if (min <= value && value <= max) {
1467                 *opt = value;
1468                 return OK;
1469         }
1471         config_msg = "Integer value out of bound";
1472         return ERR;
1475 static bool
1476 set_color(int *color, const char *name)
1478         if (map_enum(color, color_map, name))
1479                 return TRUE;
1480         if (!prefixcmp(name, "color"))
1481                 return parse_int(color, name + 5, 0, 255) == OK;
1482         return FALSE;
1485 /* Wants: object fgcolor bgcolor [attribute] */
1486 static int
1487 option_color_command(int argc, const char *argv[])
1489         struct line_info *info;
1491         if (argc < 3) {
1492                 config_msg = "Wrong number of arguments given to color command";
1493                 return ERR;
1494         }
1496         info = get_line_info(argv[0]);
1497         if (!info) {
1498                 static const struct enum_map obsolete[] = {
1499                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1500                         ENUM_MAP("main-date",   LINE_DATE),
1501                         ENUM_MAP("main-author", LINE_AUTHOR),
1502                 };
1503                 int index;
1505                 if (!map_enum(&index, obsolete, argv[0])) {
1506                         config_msg = "Unknown color name";
1507                         return ERR;
1508                 }
1509                 info = &line_info[index];
1510         }
1512         if (!set_color(&info->fg, argv[1]) ||
1513             !set_color(&info->bg, argv[2])) {
1514                 config_msg = "Unknown color";
1515                 return ERR;
1516         }
1518         info->attr = 0;
1519         while (argc-- > 3) {
1520                 int attr;
1522                 if (!set_attribute(&attr, argv[argc])) {
1523                         config_msg = "Unknown attribute";
1524                         return ERR;
1525                 }
1526                 info->attr |= attr;
1527         }
1529         return OK;
1532 static int parse_bool(bool *opt, const char *arg)
1534         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1535                 ? TRUE : FALSE;
1536         return OK;
1539 static int
1540 parse_string(char *opt, const char *arg, size_t optsize)
1542         int arglen = strlen(arg);
1544         switch (arg[0]) {
1545         case '\"':
1546         case '\'':
1547                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1548                         config_msg = "Unmatched quotation";
1549                         return ERR;
1550                 }
1551                 arg += 1; arglen -= 2;
1552         default:
1553                 string_ncopy_do(opt, optsize, arg, arglen);
1554                 return OK;
1555         }
1558 /* Wants: name = value */
1559 static int
1560 option_set_command(int argc, const char *argv[])
1562         if (argc != 3) {
1563                 config_msg = "Wrong number of arguments given to set command";
1564                 return ERR;
1565         }
1567         if (strcmp(argv[1], "=")) {
1568                 config_msg = "No value assigned";
1569                 return ERR;
1570         }
1572         if (!strcmp(argv[0], "show-author"))
1573                 return parse_bool(&opt_author, argv[2]);
1575         if (!strcmp(argv[0], "show-date"))
1576                 return parse_bool(&opt_date, argv[2]);
1578         if (!strcmp(argv[0], "show-rev-graph"))
1579                 return parse_bool(&opt_rev_graph, argv[2]);
1581         if (!strcmp(argv[0], "show-refs"))
1582                 return parse_bool(&opt_show_refs, argv[2]);
1584         if (!strcmp(argv[0], "show-line-numbers"))
1585                 return parse_bool(&opt_line_number, argv[2]);
1587         if (!strcmp(argv[0], "line-graphics"))
1588                 return parse_bool(&opt_line_graphics, argv[2]);
1590         if (!strcmp(argv[0], "line-number-interval"))
1591                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1593         if (!strcmp(argv[0], "author-width"))
1594                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1596         if (!strcmp(argv[0], "horizontal-scroll"))
1597                 return parse_step(&opt_hscroll, argv[2]);
1599         if (!strcmp(argv[0], "split-view-height"))
1600                 return parse_step(&opt_scale_split_view, argv[2]);
1602         if (!strcmp(argv[0], "tab-size"))
1603                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1605         if (!strcmp(argv[0], "commit-encoding"))
1606                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1608         config_msg = "Unknown variable name";
1609         return ERR;
1612 /* Wants: mode request key */
1613 static int
1614 option_bind_command(int argc, const char *argv[])
1616         enum request request;
1617         int keymap;
1618         int key;
1620         if (argc < 3) {
1621                 config_msg = "Wrong number of arguments given to bind command";
1622                 return ERR;
1623         }
1625         if (set_keymap(&keymap, argv[0]) == ERR) {
1626                 config_msg = "Unknown key map";
1627                 return ERR;
1628         }
1630         key = get_key_value(argv[1]);
1631         if (key == ERR) {
1632                 config_msg = "Unknown key";
1633                 return ERR;
1634         }
1636         request = get_request(argv[2]);
1637         if (request == REQ_NONE) {
1638                 static const struct enum_map obsolete[] = {
1639                         ENUM_MAP("cherry-pick",         REQ_NONE),
1640                         ENUM_MAP("screen-resize",       REQ_NONE),
1641                         ENUM_MAP("tree-parent",         REQ_PARENT),
1642                 };
1643                 int alias;
1645                 if (map_enum(&alias, obsolete, argv[2])) {
1646                         if (alias != REQ_NONE)
1647                                 add_keybinding(keymap, alias, key);
1648                         config_msg = "Obsolete request name";
1649                         return ERR;
1650                 }
1651         }
1652         if (request == REQ_NONE && *argv[2]++ == '!')
1653                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1654         if (request == REQ_NONE) {
1655                 config_msg = "Unknown request name";
1656                 return ERR;
1657         }
1659         add_keybinding(keymap, request, key);
1661         return OK;
1664 static int
1665 set_option(const char *opt, char *value)
1667         const char *argv[SIZEOF_ARG];
1668         int argc = 0;
1670         if (!argv_from_string(argv, &argc, value)) {
1671                 config_msg = "Too many option arguments";
1672                 return ERR;
1673         }
1675         if (!strcmp(opt, "color"))
1676                 return option_color_command(argc, argv);
1678         if (!strcmp(opt, "set"))
1679                 return option_set_command(argc, argv);
1681         if (!strcmp(opt, "bind"))
1682                 return option_bind_command(argc, argv);
1684         config_msg = "Unknown option command";
1685         return ERR;
1688 static int
1689 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1691         int status = OK;
1693         config_lineno++;
1694         config_msg = "Internal error";
1696         /* Check for comment markers, since read_properties() will
1697          * only ensure opt and value are split at first " \t". */
1698         optlen = strcspn(opt, "#");
1699         if (optlen == 0)
1700                 return OK;
1702         if (opt[optlen] != 0) {
1703                 config_msg = "No option value";
1704                 status = ERR;
1706         }  else {
1707                 /* Look for comment endings in the value. */
1708                 size_t len = strcspn(value, "#");
1710                 if (len < valuelen) {
1711                         valuelen = len;
1712                         value[valuelen] = 0;
1713                 }
1715                 status = set_option(opt, value);
1716         }
1718         if (status == ERR) {
1719                 warn("Error on line %d, near '%.*s': %s",
1720                      config_lineno, (int) optlen, opt, config_msg);
1721                 config_errors = TRUE;
1722         }
1724         /* Always keep going if errors are encountered. */
1725         return OK;
1728 static void
1729 load_option_file(const char *path)
1731         struct io io = {};
1733         /* It's OK that the file doesn't exist. */
1734         if (!io_open(&io, path))
1735                 return;
1737         config_lineno = 0;
1738         config_errors = FALSE;
1740         if (io_load(&io, " \t", read_option) == ERR ||
1741             config_errors == TRUE)
1742                 warn("Errors while loading %s.", path);
1745 static int
1746 load_options(void)
1748         const char *home = getenv("HOME");
1749         const char *tigrc_user = getenv("TIGRC_USER");
1750         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1751         char buf[SIZEOF_STR];
1753         add_builtin_run_requests();
1755         if (!tigrc_system)
1756                 tigrc_system = SYSCONFDIR "/tigrc";
1757         load_option_file(tigrc_system);
1759         if (!tigrc_user) {
1760                 if (!home || !string_format(buf, "%s/.tigrc", home))
1761                         return ERR;
1762                 tigrc_user = buf;
1763         }
1764         load_option_file(tigrc_user);
1766         return OK;
1770 /*
1771  * The viewer
1772  */
1774 struct view;
1775 struct view_ops;
1777 /* The display array of active views and the index of the current view. */
1778 static struct view *display[2];
1779 static unsigned int current_view;
1781 #define foreach_displayed_view(view, i) \
1782         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1784 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1786 /* Current head and commit ID */
1787 static char ref_blob[SIZEOF_REF]        = "";
1788 static char ref_commit[SIZEOF_REF]      = "HEAD";
1789 static char ref_head[SIZEOF_REF]        = "HEAD";
1791 struct view {
1792         const char *name;       /* View name */
1793         const char *cmd_env;    /* Command line set via environment */
1794         const char *id;         /* Points to either of ref_{head,commit,blob} */
1796         struct view_ops *ops;   /* View operations */
1798         enum keymap keymap;     /* What keymap does this view have */
1799         bool git_dir;           /* Whether the view requires a git directory. */
1801         char ref[SIZEOF_REF];   /* Hovered commit reference */
1802         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1804         int height, width;      /* The width and height of the main window */
1805         WINDOW *win;            /* The main window */
1806         WINDOW *title;          /* The title window living below the main window */
1808         /* Navigation */
1809         unsigned long offset;   /* Offset of the window top */
1810         unsigned long yoffset;  /* Offset from the window side. */
1811         unsigned long lineno;   /* Current line number */
1812         unsigned long p_offset; /* Previous offset of the window top */
1813         unsigned long p_yoffset;/* Previous offset from the window side */
1814         unsigned long p_lineno; /* Previous current line number */
1815         bool p_restore;         /* Should the previous position be restored. */
1817         /* Searching */
1818         char grep[SIZEOF_STR];  /* Search string */
1819         regex_t *regex;         /* Pre-compiled regexp */
1821         /* If non-NULL, points to the view that opened this view. If this view
1822          * is closed tig will switch back to the parent view. */
1823         struct view *parent;
1825         /* Buffering */
1826         size_t lines;           /* Total number of lines */
1827         struct line *line;      /* Line index */
1828         unsigned int digits;    /* Number of digits in the lines member. */
1830         /* Drawing */
1831         struct line *curline;   /* Line currently being drawn. */
1832         enum line_type curtype; /* Attribute currently used for drawing. */
1833         unsigned long col;      /* Column when drawing. */
1834         bool has_scrolled;      /* View was scrolled. */
1836         /* Loading */
1837         struct io io;
1838         struct io *pipe;
1839         time_t start_time;
1840         time_t update_secs;
1841 };
1843 struct view_ops {
1844         /* What type of content being displayed. Used in the title bar. */
1845         const char *type;
1846         /* Default command arguments. */
1847         const char **argv;
1848         /* Open and reads in all view content. */
1849         bool (*open)(struct view *view);
1850         /* Read one line; updates view->line. */
1851         bool (*read)(struct view *view, char *data);
1852         /* Draw one line; @lineno must be < view->height. */
1853         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1854         /* Depending on view handle a special requests. */
1855         enum request (*request)(struct view *view, enum request request, struct line *line);
1856         /* Search for regexp in a line. */
1857         bool (*grep)(struct view *view, struct line *line);
1858         /* Select line */
1859         void (*select)(struct view *view, struct line *line);
1860 };
1862 static struct view_ops blame_ops;
1863 static struct view_ops blob_ops;
1864 static struct view_ops diff_ops;
1865 static struct view_ops help_ops;
1866 static struct view_ops log_ops;
1867 static struct view_ops main_ops;
1868 static struct view_ops pager_ops;
1869 static struct view_ops stage_ops;
1870 static struct view_ops status_ops;
1871 static struct view_ops tree_ops;
1872 static struct view_ops branch_ops;
1874 #define VIEW_STR(name, env, ref, ops, map, git) \
1875         { name, #env, ref, ops, map, git }
1877 #define VIEW_(id, name, ops, git, ref) \
1878         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1881 static struct view views[] = {
1882         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1883         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1884         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1885         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1886         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1887         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1888         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
1889         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1890         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1891         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1892         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1893 };
1895 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1896 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1898 #define foreach_view(view, i) \
1899         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1901 #define view_is_displayed(view) \
1902         (view == display[0] || view == display[1])
1905 enum line_graphic {
1906         LINE_GRAPHIC_VLINE
1907 };
1909 static chtype line_graphics[] = {
1910         /* LINE_GRAPHIC_VLINE: */ '|'
1911 };
1913 static inline void
1914 set_view_attr(struct view *view, enum line_type type)
1916         if (!view->curline->selected && view->curtype != type) {
1917                 wattrset(view->win, get_line_attr(type));
1918                 wchgat(view->win, -1, 0, type, NULL);
1919                 view->curtype = type;
1920         }
1923 static int
1924 draw_chars(struct view *view, enum line_type type, const char *string,
1925            int max_len, bool use_tilde)
1927         int len = 0;
1928         int col = 0;
1929         int trimmed = FALSE;
1930         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1932         if (max_len <= 0)
1933                 return 0;
1935         if (opt_utf8) {
1936                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1937         } else {
1938                 col = len = strlen(string);
1939                 if (len > max_len) {
1940                         if (use_tilde) {
1941                                 max_len -= 1;
1942                         }
1943                         col = len = max_len;
1944                         trimmed = TRUE;
1945                 }
1946         }
1948         set_view_attr(view, type);
1949         if (len > 0)
1950                 waddnstr(view->win, string, len);
1951         if (trimmed && use_tilde) {
1952                 set_view_attr(view, LINE_DELIMITER);
1953                 waddch(view->win, '~');
1954                 col++;
1955         }
1957         return col;
1960 static int
1961 draw_space(struct view *view, enum line_type type, int max, int spaces)
1963         static char space[] = "                    ";
1964         int col = 0;
1966         spaces = MIN(max, spaces);
1968         while (spaces > 0) {
1969                 int len = MIN(spaces, sizeof(space) - 1);
1971                 col += draw_chars(view, type, space, len, FALSE);
1972                 spaces -= len;
1973         }
1975         return col;
1978 static bool
1979 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1981         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1982         return view->width + view->yoffset <= view->col;
1985 static bool
1986 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1988         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1989         int max = view->width + view->yoffset - view->col;
1990         int i;
1992         if (max < size)
1993                 size = max;
1995         set_view_attr(view, type);
1996         /* Using waddch() instead of waddnstr() ensures that
1997          * they'll be rendered correctly for the cursor line. */
1998         for (i = skip; i < size; i++)
1999                 waddch(view->win, graphic[i]);
2001         view->col += size;
2002         if (size < max && skip <= size)
2003                 waddch(view->win, ' ');
2004         view->col++;
2006         return view->width + view->yoffset <= view->col;
2009 static bool
2010 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2012         int max = MIN(view->width + view->yoffset - view->col, len);
2013         int col;
2015         if (text)
2016                 col = draw_chars(view, type, text, max - 1, trim);
2017         else
2018                 col = draw_space(view, type, max - 1, max - 1);
2020         view->col += col;
2021         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2022         return view->width + view->yoffset <= view->col;
2025 static bool
2026 draw_date(struct view *view, time_t *time)
2028         const char *date = mkdate(time);
2030         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2033 static bool
2034 draw_author(struct view *view, const char *author)
2036         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2038         if (!trim) {
2039                 static char initials[10];
2040                 size_t pos;
2042 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2044                 memset(initials, 0, sizeof(initials));
2045                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2046                         while (is_initial_sep(*author))
2047                                 author++;
2048                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2049                         while (*author && !is_initial_sep(author[1]))
2050                                 author++;
2051                 }
2053                 author = initials;
2054         }
2056         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2059 static bool
2060 draw_mode(struct view *view, mode_t mode)
2062         const char *str;
2064         if (S_ISDIR(mode))
2065                 str = "drwxr-xr-x";
2066         else if (S_ISLNK(mode))
2067                 str = "lrwxrwxrwx";
2068         else if (S_ISGITLINK(mode))
2069                 str = "m---------";
2070         else if (S_ISREG(mode) && mode & S_IXUSR)
2071                 str = "-rwxr-xr-x";
2072         else if (S_ISREG(mode))
2073                 str = "-rw-r--r--";
2074         else
2075                 str = "----------";
2077         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2080 static bool
2081 draw_lineno(struct view *view, unsigned int lineno)
2083         char number[10];
2084         int digits3 = view->digits < 3 ? 3 : view->digits;
2085         int max = MIN(view->width + view->yoffset - view->col, digits3);
2086         char *text = NULL;
2088         lineno += view->offset + 1;
2089         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2090                 static char fmt[] = "%1ld";
2092                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2093                 if (string_format(number, fmt, lineno))
2094                         text = number;
2095         }
2096         if (text)
2097                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2098         else
2099                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2100         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2103 static bool
2104 draw_view_line(struct view *view, unsigned int lineno)
2106         struct line *line;
2107         bool selected = (view->offset + lineno == view->lineno);
2109         assert(view_is_displayed(view));
2111         if (view->offset + lineno >= view->lines)
2112                 return FALSE;
2114         line = &view->line[view->offset + lineno];
2116         wmove(view->win, lineno, 0);
2117         if (line->cleareol)
2118                 wclrtoeol(view->win);
2119         view->col = 0;
2120         view->curline = line;
2121         view->curtype = LINE_NONE;
2122         line->selected = FALSE;
2123         line->dirty = line->cleareol = 0;
2125         if (selected) {
2126                 set_view_attr(view, LINE_CURSOR);
2127                 line->selected = TRUE;
2128                 view->ops->select(view, line);
2129         }
2131         return view->ops->draw(view, line, lineno);
2134 static void
2135 redraw_view_dirty(struct view *view)
2137         bool dirty = FALSE;
2138         int lineno;
2140         for (lineno = 0; lineno < view->height; lineno++) {
2141                 if (view->offset + lineno >= view->lines)
2142                         break;
2143                 if (!view->line[view->offset + lineno].dirty)
2144                         continue;
2145                 dirty = TRUE;
2146                 if (!draw_view_line(view, lineno))
2147                         break;
2148         }
2150         if (!dirty)
2151                 return;
2152         wnoutrefresh(view->win);
2155 static void
2156 redraw_view_from(struct view *view, int lineno)
2158         assert(0 <= lineno && lineno < view->height);
2160         for (; lineno < view->height; lineno++) {
2161                 if (!draw_view_line(view, lineno))
2162                         break;
2163         }
2165         wnoutrefresh(view->win);
2168 static void
2169 redraw_view(struct view *view)
2171         werase(view->win);
2172         redraw_view_from(view, 0);
2176 static void
2177 update_view_title(struct view *view)
2179         char buf[SIZEOF_STR];
2180         char state[SIZEOF_STR];
2181         size_t bufpos = 0, statelen = 0;
2183         assert(view_is_displayed(view));
2185         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2186                 unsigned int view_lines = view->offset + view->height;
2187                 unsigned int lines = view->lines
2188                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2189                                    : 0;
2191                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2192                                    view->ops->type,
2193                                    view->lineno + 1,
2194                                    view->lines,
2195                                    lines);
2197         }
2199         if (view->pipe) {
2200                 time_t secs = time(NULL) - view->start_time;
2202                 /* Three git seconds are a long time ... */
2203                 if (secs > 2)
2204                         string_format_from(state, &statelen, " loading %lds", secs);
2205         }
2207         string_format_from(buf, &bufpos, "[%s]", view->name);
2208         if (*view->ref && bufpos < view->width) {
2209                 size_t refsize = strlen(view->ref);
2210                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2212                 if (minsize < view->width)
2213                         refsize = view->width - minsize + 7;
2214                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2215         }
2217         if (statelen && bufpos < view->width) {
2218                 string_format_from(buf, &bufpos, "%s", state);
2219         }
2221         if (view == display[current_view])
2222                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2223         else
2224                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2226         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2227         wclrtoeol(view->title);
2228         wnoutrefresh(view->title);
2231 static int
2232 apply_step(double step, int value)
2234         if (step >= 1)
2235                 return (int) step;
2236         value *= step + 0.01;
2237         return value ? value : 1;
2240 static void
2241 resize_display(void)
2243         int offset, i;
2244         struct view *base = display[0];
2245         struct view *view = display[1] ? display[1] : display[0];
2247         /* Setup window dimensions */
2249         getmaxyx(stdscr, base->height, base->width);
2251         /* Make room for the status window. */
2252         base->height -= 1;
2254         if (view != base) {
2255                 /* Horizontal split. */
2256                 view->width   = base->width;
2257                 view->height  = apply_step(opt_scale_split_view, base->height);
2258                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2259                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2260                 base->height -= view->height;
2262                 /* Make room for the title bar. */
2263                 view->height -= 1;
2264         }
2266         /* Make room for the title bar. */
2267         base->height -= 1;
2269         offset = 0;
2271         foreach_displayed_view (view, i) {
2272                 if (!view->win) {
2273                         view->win = newwin(view->height, 0, offset, 0);
2274                         if (!view->win)
2275                                 die("Failed to create %s view", view->name);
2277                         scrollok(view->win, FALSE);
2279                         view->title = newwin(1, 0, offset + view->height, 0);
2280                         if (!view->title)
2281                                 die("Failed to create title window");
2283                 } else {
2284                         wresize(view->win, view->height, view->width);
2285                         mvwin(view->win,   offset, 0);
2286                         mvwin(view->title, offset + view->height, 0);
2287                 }
2289                 offset += view->height + 1;
2290         }
2293 static void
2294 redraw_display(bool clear)
2296         struct view *view;
2297         int i;
2299         foreach_displayed_view (view, i) {
2300                 if (clear)
2301                         wclear(view->win);
2302                 redraw_view(view);
2303                 update_view_title(view);
2304         }
2307 static void
2308 toggle_view_option(bool *option, const char *help)
2310         *option = !*option;
2311         redraw_display(FALSE);
2312         report("%sabling %s", *option ? "En" : "Dis", help);
2315 static void
2316 open_option_menu(void)
2318         const struct menu_item menu[] = {
2319                 { '.', "line numbers", &opt_line_number },
2320                 { 'D', "date display", &opt_date },
2321                 { 'A', "author display", &opt_author },
2322                 { 'g', "revision graph display", &opt_rev_graph },
2323                 { 'F', "reference display", &opt_show_refs },
2324                 { 0 }
2325         };
2326         int selected = 0;
2328         if (prompt_menu("Toggle option", menu, &selected))
2329                 toggle_view_option(menu[selected].data, menu[selected].text);
2332 static void
2333 maximize_view(struct view *view)
2335         memset(display, 0, sizeof(display));
2336         current_view = 0;
2337         display[current_view] = view;
2338         resize_display();
2339         redraw_display(FALSE);
2340         report("");
2344 /*
2345  * Navigation
2346  */
2348 static bool
2349 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2351         if (lineno >= view->lines)
2352                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2354         if (offset > lineno || offset + view->height <= lineno) {
2355                 unsigned long half = view->height / 2;
2357                 if (lineno > half)
2358                         offset = lineno - half;
2359                 else
2360                         offset = 0;
2361         }
2363         if (offset != view->offset || lineno != view->lineno) {
2364                 view->offset = offset;
2365                 view->lineno = lineno;
2366                 return TRUE;
2367         }
2369         return FALSE;
2372 /* Scrolling backend */
2373 static void
2374 do_scroll_view(struct view *view, int lines)
2376         bool redraw_current_line = FALSE;
2378         /* The rendering expects the new offset. */
2379         view->offset += lines;
2381         assert(0 <= view->offset && view->offset < view->lines);
2382         assert(lines);
2384         /* Move current line into the view. */
2385         if (view->lineno < view->offset) {
2386                 view->lineno = view->offset;
2387                 redraw_current_line = TRUE;
2388         } else if (view->lineno >= view->offset + view->height) {
2389                 view->lineno = view->offset + view->height - 1;
2390                 redraw_current_line = TRUE;
2391         }
2393         assert(view->offset <= view->lineno && view->lineno < view->lines);
2395         /* Redraw the whole screen if scrolling is pointless. */
2396         if (view->height < ABS(lines)) {
2397                 redraw_view(view);
2399         } else {
2400                 int line = lines > 0 ? view->height - lines : 0;
2401                 int end = line + ABS(lines);
2403                 scrollok(view->win, TRUE);
2404                 wscrl(view->win, lines);
2405                 scrollok(view->win, FALSE);
2407                 while (line < end && draw_view_line(view, line))
2408                         line++;
2410                 if (redraw_current_line)
2411                         draw_view_line(view, view->lineno - view->offset);
2412                 wnoutrefresh(view->win);
2413         }
2415         view->has_scrolled = TRUE;
2416         report("");
2419 /* Scroll frontend */
2420 static void
2421 scroll_view(struct view *view, enum request request)
2423         int lines = 1;
2425         assert(view_is_displayed(view));
2427         switch (request) {
2428         case REQ_SCROLL_LEFT:
2429                 if (view->yoffset == 0) {
2430                         report("Cannot scroll beyond the first column");
2431                         return;
2432                 }
2433                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2434                         view->yoffset = 0;
2435                 else
2436                         view->yoffset -= apply_step(opt_hscroll, view->width);
2437                 redraw_view_from(view, 0);
2438                 report("");
2439                 return;
2440         case REQ_SCROLL_RIGHT:
2441                 view->yoffset += apply_step(opt_hscroll, view->width);
2442                 redraw_view(view);
2443                 report("");
2444                 return;
2445         case REQ_SCROLL_PAGE_DOWN:
2446                 lines = view->height;
2447         case REQ_SCROLL_LINE_DOWN:
2448                 if (view->offset + lines > view->lines)
2449                         lines = view->lines - view->offset;
2451                 if (lines == 0 || view->offset + view->height >= view->lines) {
2452                         report("Cannot scroll beyond the last line");
2453                         return;
2454                 }
2455                 break;
2457         case REQ_SCROLL_PAGE_UP:
2458                 lines = view->height;
2459         case REQ_SCROLL_LINE_UP:
2460                 if (lines > view->offset)
2461                         lines = view->offset;
2463                 if (lines == 0) {
2464                         report("Cannot scroll beyond the first line");
2465                         return;
2466                 }
2468                 lines = -lines;
2469                 break;
2471         default:
2472                 die("request %d not handled in switch", request);
2473         }
2475         do_scroll_view(view, lines);
2478 /* Cursor moving */
2479 static void
2480 move_view(struct view *view, enum request request)
2482         int scroll_steps = 0;
2483         int steps;
2485         switch (request) {
2486         case REQ_MOVE_FIRST_LINE:
2487                 steps = -view->lineno;
2488                 break;
2490         case REQ_MOVE_LAST_LINE:
2491                 steps = view->lines - view->lineno - 1;
2492                 break;
2494         case REQ_MOVE_PAGE_UP:
2495                 steps = view->height > view->lineno
2496                       ? -view->lineno : -view->height;
2497                 break;
2499         case REQ_MOVE_PAGE_DOWN:
2500                 steps = view->lineno + view->height >= view->lines
2501                       ? view->lines - view->lineno - 1 : view->height;
2502                 break;
2504         case REQ_MOVE_UP:
2505                 steps = -1;
2506                 break;
2508         case REQ_MOVE_DOWN:
2509                 steps = 1;
2510                 break;
2512         default:
2513                 die("request %d not handled in switch", request);
2514         }
2516         if (steps <= 0 && view->lineno == 0) {
2517                 report("Cannot move beyond the first line");
2518                 return;
2520         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2521                 report("Cannot move beyond the last line");
2522                 return;
2523         }
2525         /* Move the current line */
2526         view->lineno += steps;
2527         assert(0 <= view->lineno && view->lineno < view->lines);
2529         /* Check whether the view needs to be scrolled */
2530         if (view->lineno < view->offset ||
2531             view->lineno >= view->offset + view->height) {
2532                 scroll_steps = steps;
2533                 if (steps < 0 && -steps > view->offset) {
2534                         scroll_steps = -view->offset;
2536                 } else if (steps > 0) {
2537                         if (view->lineno == view->lines - 1 &&
2538                             view->lines > view->height) {
2539                                 scroll_steps = view->lines - view->offset - 1;
2540                                 if (scroll_steps >= view->height)
2541                                         scroll_steps -= view->height - 1;
2542                         }
2543                 }
2544         }
2546         if (!view_is_displayed(view)) {
2547                 view->offset += scroll_steps;
2548                 assert(0 <= view->offset && view->offset < view->lines);
2549                 view->ops->select(view, &view->line[view->lineno]);
2550                 return;
2551         }
2553         /* Repaint the old "current" line if we be scrolling */
2554         if (ABS(steps) < view->height)
2555                 draw_view_line(view, view->lineno - steps - view->offset);
2557         if (scroll_steps) {
2558                 do_scroll_view(view, scroll_steps);
2559                 return;
2560         }
2562         /* Draw the current line */
2563         draw_view_line(view, view->lineno - view->offset);
2565         wnoutrefresh(view->win);
2566         report("");
2570 /*
2571  * Searching
2572  */
2574 static void search_view(struct view *view, enum request request);
2576 static bool
2577 grep_text(struct view *view, const char *text[])
2579         regmatch_t pmatch;
2580         size_t i;
2582         for (i = 0; text[i]; i++)
2583                 if (*text[i] &&
2584                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2585                         return TRUE;
2586         return FALSE;
2589 static void
2590 select_view_line(struct view *view, unsigned long lineno)
2592         unsigned long old_lineno = view->lineno;
2593         unsigned long old_offset = view->offset;
2595         if (goto_view_line(view, view->offset, lineno)) {
2596                 if (view_is_displayed(view)) {
2597                         if (old_offset != view->offset) {
2598                                 redraw_view(view);
2599                         } else {
2600                                 draw_view_line(view, old_lineno - view->offset);
2601                                 draw_view_line(view, view->lineno - view->offset);
2602                                 wnoutrefresh(view->win);
2603                         }
2604                 } else {
2605                         view->ops->select(view, &view->line[view->lineno]);
2606                 }
2607         }
2610 static void
2611 find_next(struct view *view, enum request request)
2613         unsigned long lineno = view->lineno;
2614         int direction;
2616         if (!*view->grep) {
2617                 if (!*opt_search)
2618                         report("No previous search");
2619                 else
2620                         search_view(view, request);
2621                 return;
2622         }
2624         switch (request) {
2625         case REQ_SEARCH:
2626         case REQ_FIND_NEXT:
2627                 direction = 1;
2628                 break;
2630         case REQ_SEARCH_BACK:
2631         case REQ_FIND_PREV:
2632                 direction = -1;
2633                 break;
2635         default:
2636                 return;
2637         }
2639         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2640                 lineno += direction;
2642         /* Note, lineno is unsigned long so will wrap around in which case it
2643          * will become bigger than view->lines. */
2644         for (; lineno < view->lines; lineno += direction) {
2645                 if (view->ops->grep(view, &view->line[lineno])) {
2646                         select_view_line(view, lineno);
2647                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2648                         return;
2649                 }
2650         }
2652         report("No match found for '%s'", view->grep);
2655 static void
2656 search_view(struct view *view, enum request request)
2658         int regex_err;
2660         if (view->regex) {
2661                 regfree(view->regex);
2662                 *view->grep = 0;
2663         } else {
2664                 view->regex = calloc(1, sizeof(*view->regex));
2665                 if (!view->regex)
2666                         return;
2667         }
2669         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2670         if (regex_err != 0) {
2671                 char buf[SIZEOF_STR] = "unknown error";
2673                 regerror(regex_err, view->regex, buf, sizeof(buf));
2674                 report("Search failed: %s", buf);
2675                 return;
2676         }
2678         string_copy(view->grep, opt_search);
2680         find_next(view, request);
2683 /*
2684  * Incremental updating
2685  */
2687 static void
2688 reset_view(struct view *view)
2690         int i;
2692         for (i = 0; i < view->lines; i++)
2693                 free(view->line[i].data);
2694         free(view->line);
2696         view->p_offset = view->offset;
2697         view->p_yoffset = view->yoffset;
2698         view->p_lineno = view->lineno;
2700         view->line = NULL;
2701         view->offset = 0;
2702         view->yoffset = 0;
2703         view->lines  = 0;
2704         view->lineno = 0;
2705         view->vid[0] = 0;
2706         view->update_secs = 0;
2709 static void
2710 free_argv(const char *argv[])
2712         int argc;
2714         for (argc = 0; argv[argc]; argc++)
2715                 free((void *) argv[argc]);
2718 static bool
2719 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2721         char buf[SIZEOF_STR];
2722         int argc;
2723         bool noreplace = flags == FORMAT_NONE;
2725         free_argv(dst_argv);
2727         for (argc = 0; src_argv[argc]; argc++) {
2728                 const char *arg = src_argv[argc];
2729                 size_t bufpos = 0;
2731                 while (arg) {
2732                         char *next = strstr(arg, "%(");
2733                         int len = next - arg;
2734                         const char *value;
2736                         if (!next || noreplace) {
2737                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2738                                         noreplace = TRUE;
2739                                 len = strlen(arg);
2740                                 value = "";
2742                         } else if (!prefixcmp(next, "%(directory)")) {
2743                                 value = opt_path;
2745                         } else if (!prefixcmp(next, "%(file)")) {
2746                                 value = opt_file;
2748                         } else if (!prefixcmp(next, "%(ref)")) {
2749                                 value = *opt_ref ? opt_ref : "HEAD";
2751                         } else if (!prefixcmp(next, "%(head)")) {
2752                                 value = ref_head;
2754                         } else if (!prefixcmp(next, "%(commit)")) {
2755                                 value = ref_commit;
2757                         } else if (!prefixcmp(next, "%(blob)")) {
2758                                 value = ref_blob;
2760                         } else {
2761                                 report("Unknown replacement: `%s`", next);
2762                                 return FALSE;
2763                         }
2765                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2766                                 return FALSE;
2768                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2769                 }
2771                 dst_argv[argc] = strdup(buf);
2772                 if (!dst_argv[argc])
2773                         break;
2774         }
2776         dst_argv[argc] = NULL;
2778         return src_argv[argc] == NULL;
2781 static bool
2782 restore_view_position(struct view *view)
2784         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2785                 return FALSE;
2787         /* Changing the view position cancels the restoring. */
2788         /* FIXME: Changing back to the first line is not detected. */
2789         if (view->offset != 0 || view->lineno != 0) {
2790                 view->p_restore = FALSE;
2791                 return FALSE;
2792         }
2794         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2795             view_is_displayed(view))
2796                 werase(view->win);
2798         view->yoffset = view->p_yoffset;
2799         view->p_restore = FALSE;
2801         return TRUE;
2804 static void
2805 end_update(struct view *view, bool force)
2807         if (!view->pipe)
2808                 return;
2809         while (!view->ops->read(view, NULL))
2810                 if (!force)
2811                         return;
2812         set_nonblocking_input(FALSE);
2813         if (force)
2814                 kill_io(view->pipe);
2815         done_io(view->pipe);
2816         view->pipe = NULL;
2819 static void
2820 setup_update(struct view *view, const char *vid)
2822         set_nonblocking_input(TRUE);
2823         reset_view(view);
2824         string_copy_rev(view->vid, vid);
2825         view->pipe = &view->io;
2826         view->start_time = time(NULL);
2829 static bool
2830 prepare_update(struct view *view, const char *argv[], const char *dir,
2831                enum format_flags flags)
2833         if (view->pipe)
2834                 end_update(view, TRUE);
2835         return init_io_rd(&view->io, argv, dir, flags);
2838 static bool
2839 prepare_update_file(struct view *view, const char *name)
2841         if (view->pipe)
2842                 end_update(view, TRUE);
2843         return io_open(&view->io, name);
2846 static bool
2847 begin_update(struct view *view, bool refresh)
2849         if (view->pipe)
2850                 end_update(view, TRUE);
2852         if (refresh) {
2853                 if (!start_io(&view->io))
2854                         return FALSE;
2856         } else {
2857                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2858                         opt_path[0] = 0;
2860                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2861                         return FALSE;
2863                 /* Put the current ref_* value to the view title ref
2864                  * member. This is needed by the blob view. Most other
2865                  * views sets it automatically after loading because the
2866                  * first line is a commit line. */
2867                 string_copy_rev(view->ref, view->id);
2868         }
2870         setup_update(view, view->id);
2872         return TRUE;
2875 static bool
2876 update_view(struct view *view)
2878         char out_buffer[BUFSIZ * 2];
2879         char *line;
2880         /* Clear the view and redraw everything since the tree sorting
2881          * might have rearranged things. */
2882         bool redraw = view->lines == 0;
2883         bool can_read = TRUE;
2885         if (!view->pipe)
2886                 return TRUE;
2888         if (!io_can_read(view->pipe)) {
2889                 if (view->lines == 0 && view_is_displayed(view)) {
2890                         time_t secs = time(NULL) - view->start_time;
2892                         if (secs > 1 && secs > view->update_secs) {
2893                                 if (view->update_secs == 0)
2894                                         redraw_view(view);
2895                                 update_view_title(view);
2896                                 view->update_secs = secs;
2897                         }
2898                 }
2899                 return TRUE;
2900         }
2902         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2903                 if (opt_iconv != ICONV_NONE) {
2904                         ICONV_CONST char *inbuf = line;
2905                         size_t inlen = strlen(line) + 1;
2907                         char *outbuf = out_buffer;
2908                         size_t outlen = sizeof(out_buffer);
2910                         size_t ret;
2912                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2913                         if (ret != (size_t) -1)
2914                                 line = out_buffer;
2915                 }
2917                 if (!view->ops->read(view, line)) {
2918                         report("Allocation failure");
2919                         end_update(view, TRUE);
2920                         return FALSE;
2921                 }
2922         }
2924         {
2925                 unsigned long lines = view->lines;
2926                 int digits;
2928                 for (digits = 0; lines; digits++)
2929                         lines /= 10;
2931                 /* Keep the displayed view in sync with line number scaling. */
2932                 if (digits != view->digits) {
2933                         view->digits = digits;
2934                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2935                                 redraw = TRUE;
2936                 }
2937         }
2939         if (io_error(view->pipe)) {
2940                 report("Failed to read: %s", io_strerror(view->pipe));
2941                 end_update(view, TRUE);
2943         } else if (io_eof(view->pipe)) {
2944                 report("");
2945                 end_update(view, FALSE);
2946         }
2948         if (restore_view_position(view))
2949                 redraw = TRUE;
2951         if (!view_is_displayed(view))
2952                 return TRUE;
2954         if (redraw)
2955                 redraw_view_from(view, 0);
2956         else
2957                 redraw_view_dirty(view);
2959         /* Update the title _after_ the redraw so that if the redraw picks up a
2960          * commit reference in view->ref it'll be available here. */
2961         update_view_title(view);
2962         return TRUE;
2965 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2967 static struct line *
2968 add_line_data(struct view *view, void *data, enum line_type type)
2970         struct line *line;
2972         if (!realloc_lines(&view->line, view->lines, 1))
2973                 return NULL;
2975         line = &view->line[view->lines++];
2976         memset(line, 0, sizeof(*line));
2977         line->type = type;
2978         line->data = data;
2979         line->dirty = 1;
2981         return line;
2984 static struct line *
2985 add_line_text(struct view *view, const char *text, enum line_type type)
2987         char *data = text ? strdup(text) : NULL;
2989         return data ? add_line_data(view, data, type) : NULL;
2992 static struct line *
2993 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2995         char buf[SIZEOF_STR];
2996         va_list args;
2998         va_start(args, fmt);
2999         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3000                 buf[0] = 0;
3001         va_end(args);
3003         return buf[0] ? add_line_text(view, buf, type) : NULL;
3006 /*
3007  * View opening
3008  */
3010 enum open_flags {
3011         OPEN_DEFAULT = 0,       /* Use default view switching. */
3012         OPEN_SPLIT = 1,         /* Split current view. */
3013         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3014         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3015         OPEN_PREPARED = 32,     /* Open already prepared command. */
3016 };
3018 static void
3019 open_view(struct view *prev, enum request request, enum open_flags flags)
3021         bool split = !!(flags & OPEN_SPLIT);
3022         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3023         bool nomaximize = !!(flags & OPEN_REFRESH);
3024         struct view *view = VIEW(request);
3025         int nviews = displayed_views();
3026         struct view *base_view = display[0];
3028         if (view == prev && nviews == 1 && !reload) {
3029                 report("Already in %s view", view->name);
3030                 return;
3031         }
3033         if (view->git_dir && !opt_git_dir[0]) {
3034                 report("The %s view is disabled in pager view", view->name);
3035                 return;
3036         }
3038         if (split) {
3039                 display[1] = view;
3040                 current_view = 1;
3041         } else if (!nomaximize) {
3042                 /* Maximize the current view. */
3043                 memset(display, 0, sizeof(display));
3044                 current_view = 0;
3045                 display[current_view] = view;
3046         }
3048         /* Resize the view when switching between split- and full-screen,
3049          * or when switching between two different full-screen views. */
3050         if (nviews != displayed_views() ||
3051             (nviews == 1 && base_view != display[0]))
3052                 resize_display();
3054         if (view->ops->open) {
3055                 if (view->pipe)
3056                         end_update(view, TRUE);
3057                 if (!view->ops->open(view)) {
3058                         report("Failed to load %s view", view->name);
3059                         return;
3060                 }
3061                 restore_view_position(view);
3063         } else if ((reload || strcmp(view->vid, view->id)) &&
3064                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3065                 report("Failed to load %s view", view->name);
3066                 return;
3067         }
3069         if (split && prev->lineno - prev->offset >= prev->height) {
3070                 /* Take the title line into account. */
3071                 int lines = prev->lineno - prev->offset - prev->height + 1;
3073                 /* Scroll the view that was split if the current line is
3074                  * outside the new limited view. */
3075                 do_scroll_view(prev, lines);
3076         }
3078         if (prev && view != prev) {
3079                 if (split) {
3080                         /* "Blur" the previous view. */
3081                         update_view_title(prev);
3082                 }
3084                 view->parent = prev;
3085         }
3087         if (view->pipe && view->lines == 0) {
3088                 /* Clear the old view and let the incremental updating refill
3089                  * the screen. */
3090                 werase(view->win);
3091                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3092                 report("");
3093         } else if (view_is_displayed(view)) {
3094                 redraw_view(view);
3095                 report("");
3096         }
3099 static void
3100 open_external_viewer(const char *argv[], const char *dir)
3102         def_prog_mode();           /* save current tty modes */
3103         endwin();                  /* restore original tty modes */
3104         run_io_fg(argv, dir);
3105         fprintf(stderr, "Press Enter to continue");
3106         getc(opt_tty);
3107         reset_prog_mode();
3108         redraw_display(TRUE);
3111 static void
3112 open_mergetool(const char *file)
3114         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3116         open_external_viewer(mergetool_argv, opt_cdup);
3119 static void
3120 open_editor(bool from_root, const char *file)
3122         const char *editor_argv[] = { "vi", file, NULL };
3123         const char *editor;
3125         editor = getenv("GIT_EDITOR");
3126         if (!editor && *opt_editor)
3127                 editor = opt_editor;
3128         if (!editor)
3129                 editor = getenv("VISUAL");
3130         if (!editor)
3131                 editor = getenv("EDITOR");
3132         if (!editor)
3133                 editor = "vi";
3135         editor_argv[0] = editor;
3136         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3139 static void
3140 open_run_request(enum request request)
3142         struct run_request *req = get_run_request(request);
3143         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3145         if (!req) {
3146                 report("Unknown run request");
3147                 return;
3148         }
3150         if (format_argv(argv, req->argv, FORMAT_ALL))
3151                 open_external_viewer(argv, NULL);
3152         free_argv(argv);
3155 /*
3156  * User request switch noodle
3157  */
3159 static int
3160 view_driver(struct view *view, enum request request)
3162         int i;
3164         if (request == REQ_NONE)
3165                 return TRUE;
3167         if (request > REQ_NONE) {
3168                 open_run_request(request);
3169                 /* FIXME: When all views can refresh always do this. */
3170                 if (view == VIEW(REQ_VIEW_STATUS) ||
3171                     view == VIEW(REQ_VIEW_MAIN) ||
3172                     view == VIEW(REQ_VIEW_LOG) ||
3173                     view == VIEW(REQ_VIEW_BRANCH) ||
3174                     view == VIEW(REQ_VIEW_STAGE))
3175                         request = REQ_REFRESH;
3176                 else
3177                         return TRUE;
3178         }
3180         if (view && view->lines) {
3181                 request = view->ops->request(view, request, &view->line[view->lineno]);
3182                 if (request == REQ_NONE)
3183                         return TRUE;
3184         }
3186         switch (request) {
3187         case REQ_MOVE_UP:
3188         case REQ_MOVE_DOWN:
3189         case REQ_MOVE_PAGE_UP:
3190         case REQ_MOVE_PAGE_DOWN:
3191         case REQ_MOVE_FIRST_LINE:
3192         case REQ_MOVE_LAST_LINE:
3193                 move_view(view, request);
3194                 break;
3196         case REQ_SCROLL_LEFT:
3197         case REQ_SCROLL_RIGHT:
3198         case REQ_SCROLL_LINE_DOWN:
3199         case REQ_SCROLL_LINE_UP:
3200         case REQ_SCROLL_PAGE_DOWN:
3201         case REQ_SCROLL_PAGE_UP:
3202                 scroll_view(view, request);
3203                 break;
3205         case REQ_VIEW_BLAME:
3206                 if (!opt_file[0]) {
3207                         report("No file chosen, press %s to open tree view",
3208                                get_key(REQ_VIEW_TREE));
3209                         break;
3210                 }
3211                 open_view(view, request, OPEN_DEFAULT);
3212                 break;
3214         case REQ_VIEW_BLOB:
3215                 if (!ref_blob[0]) {
3216                         report("No file chosen, press %s to open tree view",
3217                                get_key(REQ_VIEW_TREE));
3218                         break;
3219                 }
3220                 open_view(view, request, OPEN_DEFAULT);
3221                 break;
3223         case REQ_VIEW_PAGER:
3224                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3225                         report("No pager content, press %s to run command from prompt",
3226                                get_key(REQ_PROMPT));
3227                         break;
3228                 }
3229                 open_view(view, request, OPEN_DEFAULT);
3230                 break;
3232         case REQ_VIEW_STAGE:
3233                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3234                         report("No stage content, press %s to open the status view and choose file",
3235                                get_key(REQ_VIEW_STATUS));
3236                         break;
3237                 }
3238                 open_view(view, request, OPEN_DEFAULT);
3239                 break;
3241         case REQ_VIEW_STATUS:
3242                 if (opt_is_inside_work_tree == FALSE) {
3243                         report("The status view requires a working tree");
3244                         break;
3245                 }
3246                 open_view(view, request, OPEN_DEFAULT);
3247                 break;
3249         case REQ_VIEW_MAIN:
3250         case REQ_VIEW_DIFF:
3251         case REQ_VIEW_LOG:
3252         case REQ_VIEW_TREE:
3253         case REQ_VIEW_HELP:
3254         case REQ_VIEW_BRANCH:
3255                 open_view(view, request, OPEN_DEFAULT);
3256                 break;
3258         case REQ_NEXT:
3259         case REQ_PREVIOUS:
3260                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3262                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3263                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3264                    (view == VIEW(REQ_VIEW_DIFF) &&
3265                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3266                    (view == VIEW(REQ_VIEW_STAGE) &&
3267                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3268                    (view == VIEW(REQ_VIEW_BLOB) &&
3269                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3270                    (view == VIEW(REQ_VIEW_MAIN) &&
3271                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3272                         int line;
3274                         view = view->parent;
3275                         line = view->lineno;
3276                         move_view(view, request);
3277                         if (view_is_displayed(view))
3278                                 update_view_title(view);
3279                         if (line != view->lineno)
3280                                 view->ops->request(view, REQ_ENTER,
3281                                                    &view->line[view->lineno]);
3283                 } else {
3284                         move_view(view, request);
3285                 }
3286                 break;
3288         case REQ_VIEW_NEXT:
3289         {
3290                 int nviews = displayed_views();
3291                 int next_view = (current_view + 1) % nviews;
3293                 if (next_view == current_view) {
3294                         report("Only one view is displayed");
3295                         break;
3296                 }
3298                 current_view = next_view;
3299                 /* Blur out the title of the previous view. */
3300                 update_view_title(view);
3301                 report("");
3302                 break;
3303         }
3304         case REQ_REFRESH:
3305                 report("Refreshing is not yet supported for the %s view", view->name);
3306                 break;
3308         case REQ_MAXIMIZE:
3309                 if (displayed_views() == 2)
3310                         maximize_view(view);
3311                 break;
3313         case REQ_OPTIONS:
3314                 open_option_menu();
3315                 break;
3317         case REQ_TOGGLE_LINENO:
3318                 toggle_view_option(&opt_line_number, "line numbers");
3319                 break;
3321         case REQ_TOGGLE_DATE:
3322                 toggle_view_option(&opt_date, "date display");
3323                 break;
3325         case REQ_TOGGLE_AUTHOR:
3326                 toggle_view_option(&opt_author, "author display");
3327                 break;
3329         case REQ_TOGGLE_REV_GRAPH:
3330                 toggle_view_option(&opt_rev_graph, "revision graph display");
3331                 break;
3333         case REQ_TOGGLE_REFS:
3334                 toggle_view_option(&opt_show_refs, "reference display");
3335                 break;
3337         case REQ_TOGGLE_SORT_FIELD:
3338         case REQ_TOGGLE_SORT_ORDER:
3339                 report("Sorting is not yet supported for the %s view", view->name);
3340                 break;
3342         case REQ_SEARCH:
3343         case REQ_SEARCH_BACK:
3344                 search_view(view, request);
3345                 break;
3347         case REQ_FIND_NEXT:
3348         case REQ_FIND_PREV:
3349                 find_next(view, request);
3350                 break;
3352         case REQ_STOP_LOADING:
3353                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3354                         view = &views[i];
3355                         if (view->pipe)
3356                                 report("Stopped loading the %s view", view->name),
3357                         end_update(view, TRUE);
3358                 }
3359                 break;
3361         case REQ_SHOW_VERSION:
3362                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3363                 return TRUE;
3365         case REQ_SCREEN_REDRAW:
3366                 redraw_display(TRUE);
3367                 break;
3369         case REQ_EDIT:
3370                 report("Nothing to edit");
3371                 break;
3373         case REQ_ENTER:
3374                 report("Nothing to enter");
3375                 break;
3377         case REQ_VIEW_CLOSE:
3378                 /* XXX: Mark closed views by letting view->parent point to the
3379                  * view itself. Parents to closed view should never be
3380                  * followed. */
3381                 if (view->parent &&
3382                     view->parent->parent != view->parent) {
3383                         maximize_view(view->parent);
3384                         view->parent = view;
3385                         break;
3386                 }
3387                 /* Fall-through */
3388         case REQ_QUIT:
3389                 return FALSE;
3391         default:
3392                 report("Unknown key, press 'h' for help");
3393                 return TRUE;
3394         }
3396         return TRUE;
3400 /*
3401  * View backend utilities
3402  */
3404 enum sort_field {
3405         ORDERBY_NAME,
3406         ORDERBY_DATE,
3407         ORDERBY_AUTHOR,
3408 };
3410 struct sort_state {
3411         const enum sort_field *fields;
3412         size_t size, current;
3413         bool reverse;
3414 };
3416 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3417 #define get_sort_field(state) ((state).fields[(state).current])
3418 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3420 static void
3421 sort_view(struct view *view, enum request request, struct sort_state *state,
3422           int (*compare)(const void *, const void *))
3424         switch (request) {
3425         case REQ_TOGGLE_SORT_FIELD:
3426                 state->current = (state->current + 1) % state->size;
3427                 break;
3429         case REQ_TOGGLE_SORT_ORDER:
3430                 state->reverse = !state->reverse;
3431                 break;
3432         default:
3433                 die("Not a sort request");
3434         }
3436         qsort(view->line, view->lines, sizeof(*view->line), compare);
3437         redraw_view(view);
3440 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3442 /* Small author cache to reduce memory consumption. It uses binary
3443  * search to lookup or find place to position new entries. No entries
3444  * are ever freed. */
3445 static const char *
3446 get_author(const char *name)
3448         static const char **authors;
3449         static size_t authors_size;
3450         int from = 0, to = authors_size - 1;
3452         while (from <= to) {
3453                 size_t pos = (to + from) / 2;
3454                 int cmp = strcmp(name, authors[pos]);
3456                 if (!cmp)
3457                         return authors[pos];
3459                 if (cmp < 0)
3460                         to = pos - 1;
3461                 else
3462                         from = pos + 1;
3463         }
3465         if (!realloc_authors(&authors, authors_size, 1))
3466                 return NULL;
3467         name = strdup(name);
3468         if (!name)
3469                 return NULL;
3471         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3472         authors[from] = name;
3473         authors_size++;
3475         return name;
3478 static void
3479 parse_timezone(time_t *time, const char *zone)
3481         long tz;
3483         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3484         tz += ('0' - zone[2]) * 60 * 60;
3485         tz += ('0' - zone[3]) * 60;
3486         tz += ('0' - zone[4]);
3488         if (zone[0] == '-')
3489                 tz = -tz;
3491         *time -= tz;
3494 /* Parse author lines where the name may be empty:
3495  *      author  <email@address.tld> 1138474660 +0100
3496  */
3497 static void
3498 parse_author_line(char *ident, const char **author, time_t *time)
3500         char *nameend = strchr(ident, '<');
3501         char *emailend = strchr(ident, '>');
3503         if (nameend && emailend)
3504                 *nameend = *emailend = 0;
3505         ident = chomp_string(ident);
3506         if (!*ident) {
3507                 if (nameend)
3508                         ident = chomp_string(nameend + 1);
3509                 if (!*ident)
3510                         ident = "Unknown";
3511         }
3513         *author = get_author(ident);
3515         /* Parse epoch and timezone */
3516         if (emailend && emailend[1] == ' ') {
3517                 char *secs = emailend + 2;
3518                 char *zone = strchr(secs, ' ');
3520                 *time = (time_t) atol(secs);
3522                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3523                         parse_timezone(time, zone + 1);
3524         }
3527 static bool
3528 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3530         char rev[SIZEOF_REV];
3531         const char *revlist_argv[] = {
3532                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3533         };
3534         struct menu_item *items;
3535         char text[SIZEOF_STR];
3536         bool ok = TRUE;
3537         int i;
3539         items = calloc(*parents + 1, sizeof(*items));
3540         if (!items)
3541                 return FALSE;
3543         for (i = 0; i < *parents; i++) {
3544                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3545                 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3546                     !(items[i].text = strdup(text))) {
3547                         ok = FALSE;
3548                         break;
3549                 }
3550         }
3552         if (ok) {
3553                 *parents = 0;
3554                 ok = prompt_menu("Select parent", items, parents);
3555         }
3556         for (i = 0; items[i].text; i++)
3557                 free((char *) items[i].text);
3558         free(items);
3559         return ok;
3562 static bool
3563 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3565         char buf[SIZEOF_STR * 4];
3566         const char *revlist_argv[] = {
3567                 "git", "log", "--no-color", "-1",
3568                         "--pretty=format:%P", id, "--", path, NULL
3569         };
3570         int parents;
3572         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3573             (parents = strlen(buf) / 40) < 0) {
3574                 report("Failed to get parent information");
3575                 return FALSE;
3577         } else if (parents == 0) {
3578                 if (path)
3579                         report("Path '%s' does not exist in the parent", path);
3580                 else
3581                         report("The selected commit has no parents");
3582                 return FALSE;
3583         }
3585         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3586                 return FALSE;
3588         string_copy_rev(rev, &buf[41 * parents]);
3589         return TRUE;
3592 /*
3593  * Pager backend
3594  */
3596 static bool
3597 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3599         char text[SIZEOF_STR];
3601         if (opt_line_number && draw_lineno(view, lineno))
3602                 return TRUE;
3604         string_expand(text, sizeof(text), line->data, opt_tab_size);
3605         draw_text(view, line->type, text, TRUE);
3606         return TRUE;
3609 static bool
3610 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3612         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3613         char ref[SIZEOF_STR];
3615         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3616                 return TRUE;
3618         /* This is the only fatal call, since it can "corrupt" the buffer. */
3619         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3620                 return FALSE;
3622         return TRUE;
3625 static void
3626 add_pager_refs(struct view *view, struct line *line)
3628         char buf[SIZEOF_STR];
3629         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3630         struct ref_list *list;
3631         size_t bufpos = 0, i;
3632         const char *sep = "Refs: ";
3633         bool is_tag = FALSE;
3635         assert(line->type == LINE_COMMIT);
3637         list = get_ref_list(commit_id);
3638         if (!list) {
3639                 if (view == VIEW(REQ_VIEW_DIFF))
3640                         goto try_add_describe_ref;
3641                 return;
3642         }
3644         for (i = 0; i < list->size; i++) {
3645                 struct ref *ref = list->refs[i];
3646                 const char *fmt = ref->tag    ? "%s[%s]" :
3647                                   ref->remote ? "%s<%s>" : "%s%s";
3649                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3650                         return;
3651                 sep = ", ";
3652                 if (ref->tag)
3653                         is_tag = TRUE;
3654         }
3656         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3657 try_add_describe_ref:
3658                 /* Add <tag>-g<commit_id> "fake" reference. */
3659                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3660                         return;
3661         }
3663         if (bufpos == 0)
3664                 return;
3666         add_line_text(view, buf, LINE_PP_REFS);
3669 static bool
3670 pager_read(struct view *view, char *data)
3672         struct line *line;
3674         if (!data)
3675                 return TRUE;
3677         line = add_line_text(view, data, get_line_type(data));
3678         if (!line)
3679                 return FALSE;
3681         if (line->type == LINE_COMMIT &&
3682             (view == VIEW(REQ_VIEW_DIFF) ||
3683              view == VIEW(REQ_VIEW_LOG)))
3684                 add_pager_refs(view, line);
3686         return TRUE;
3689 static enum request
3690 pager_request(struct view *view, enum request request, struct line *line)
3692         int split = 0;
3694         if (request != REQ_ENTER)
3695                 return request;
3697         if (line->type == LINE_COMMIT &&
3698            (view == VIEW(REQ_VIEW_LOG) ||
3699             view == VIEW(REQ_VIEW_PAGER))) {
3700                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3701                 split = 1;
3702         }
3704         /* Always scroll the view even if it was split. That way
3705          * you can use Enter to scroll through the log view and
3706          * split open each commit diff. */
3707         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3709         /* FIXME: A minor workaround. Scrolling the view will call report("")
3710          * but if we are scrolling a non-current view this won't properly
3711          * update the view title. */
3712         if (split)
3713                 update_view_title(view);
3715         return REQ_NONE;
3718 static bool
3719 pager_grep(struct view *view, struct line *line)
3721         const char *text[] = { line->data, NULL };
3723         return grep_text(view, text);
3726 static void
3727 pager_select(struct view *view, struct line *line)
3729         if (line->type == LINE_COMMIT) {
3730                 char *text = (char *)line->data + STRING_SIZE("commit ");
3732                 if (view != VIEW(REQ_VIEW_PAGER))
3733                         string_copy_rev(view->ref, text);
3734                 string_copy_rev(ref_commit, text);
3735         }
3738 static struct view_ops pager_ops = {
3739         "line",
3740         NULL,
3741         NULL,
3742         pager_read,
3743         pager_draw,
3744         pager_request,
3745         pager_grep,
3746         pager_select,
3747 };
3749 static const char *log_argv[SIZEOF_ARG] = {
3750         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3751 };
3753 static enum request
3754 log_request(struct view *view, enum request request, struct line *line)
3756         switch (request) {
3757         case REQ_REFRESH:
3758                 load_refs();
3759                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3760                 return REQ_NONE;
3761         default:
3762                 return pager_request(view, request, line);
3763         }
3766 static struct view_ops log_ops = {
3767         "line",
3768         log_argv,
3769         NULL,
3770         pager_read,
3771         pager_draw,
3772         log_request,
3773         pager_grep,
3774         pager_select,
3775 };
3777 static const char *diff_argv[SIZEOF_ARG] = {
3778         "git", "show", "--pretty=fuller", "--no-color", "--root",
3779                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3780 };
3782 static struct view_ops diff_ops = {
3783         "line",
3784         diff_argv,
3785         NULL,
3786         pager_read,
3787         pager_draw,
3788         pager_request,
3789         pager_grep,
3790         pager_select,
3791 };
3793 /*
3794  * Help backend
3795  */
3797 static bool
3798 help_open(struct view *view)
3800         char buf[SIZEOF_STR];
3801         size_t bufpos;
3802         int i;
3804         if (view->lines > 0)
3805                 return TRUE;
3807         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3809         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3810                 const char *key;
3812                 if (req_info[i].request == REQ_NONE)
3813                         continue;
3815                 if (!req_info[i].request) {
3816                         add_line_text(view, "", LINE_DEFAULT);
3817                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3818                         continue;
3819                 }
3821                 key = get_key(req_info[i].request);
3822                 if (!*key)
3823                         key = "(no key defined)";
3825                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3826                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3827                         if (buf[bufpos] == '_')
3828                                 buf[bufpos] = '-';
3829                 }
3831                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3832                                 key, buf, req_info[i].help);
3833         }
3835         if (run_requests) {
3836                 add_line_text(view, "", LINE_DEFAULT);
3837                 add_line_text(view, "External commands:", LINE_DEFAULT);
3838         }
3840         for (i = 0; i < run_requests; i++) {
3841                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3842                 const char *key;
3843                 int argc;
3845                 if (!req)
3846                         continue;
3848                 key = get_key_name(req->key);
3849                 if (!*key)
3850                         key = "(no key defined)";
3852                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3853                         if (!string_format_from(buf, &bufpos, "%s%s",
3854                                                 argc ? " " : "", req->argv[argc]))
3855                                 return REQ_NONE;
3857                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3858                                 keymap_table[req->keymap].name, key, buf);
3859         }
3861         return TRUE;
3864 static struct view_ops help_ops = {
3865         "line",
3866         NULL,
3867         help_open,
3868         NULL,
3869         pager_draw,
3870         pager_request,
3871         pager_grep,
3872         pager_select,
3873 };
3876 /*
3877  * Tree backend
3878  */
3880 struct tree_stack_entry {
3881         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3882         unsigned long lineno;           /* Line number to restore */
3883         char *name;                     /* Position of name in opt_path */
3884 };
3886 /* The top of the path stack. */
3887 static struct tree_stack_entry *tree_stack = NULL;
3888 unsigned long tree_lineno = 0;
3890 static void
3891 pop_tree_stack_entry(void)
3893         struct tree_stack_entry *entry = tree_stack;
3895         tree_lineno = entry->lineno;
3896         entry->name[0] = 0;
3897         tree_stack = entry->prev;
3898         free(entry);
3901 static void
3902 push_tree_stack_entry(const char *name, unsigned long lineno)
3904         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3905         size_t pathlen = strlen(opt_path);
3907         if (!entry)
3908                 return;
3910         entry->prev = tree_stack;
3911         entry->name = opt_path + pathlen;
3912         tree_stack = entry;
3914         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3915                 pop_tree_stack_entry();
3916                 return;
3917         }
3919         /* Move the current line to the first tree entry. */
3920         tree_lineno = 1;
3921         entry->lineno = lineno;
3924 /* Parse output from git-ls-tree(1):
3925  *
3926  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3927  */
3929 #define SIZEOF_TREE_ATTR \
3930         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3932 #define SIZEOF_TREE_MODE \
3933         STRING_SIZE("100644 ")
3935 #define TREE_ID_OFFSET \
3936         STRING_SIZE("100644 blob ")
3938 struct tree_entry {
3939         char id[SIZEOF_REV];
3940         mode_t mode;
3941         time_t time;                    /* Date from the author ident. */
3942         const char *author;             /* Author of the commit. */
3943         char name[1];
3944 };
3946 static const char *
3947 tree_path(const struct line *line)
3949         return ((struct tree_entry *) line->data)->name;
3952 static int
3953 tree_compare_entry(const struct line *line1, const struct line *line2)
3955         if (line1->type != line2->type)
3956                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3957         return strcmp(tree_path(line1), tree_path(line2));
3960 static const enum sort_field tree_sort_fields[] = {
3961         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3962 };
3963 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3965 static int
3966 tree_compare(const void *l1, const void *l2)
3968         const struct line *line1 = (const struct line *) l1;
3969         const struct line *line2 = (const struct line *) l2;
3970         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3971         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3973         if (line1->type == LINE_TREE_HEAD)
3974                 return -1;
3975         if (line2->type == LINE_TREE_HEAD)
3976                 return 1;
3978         switch (get_sort_field(tree_sort_state)) {
3979         case ORDERBY_DATE:
3980                 return sort_order(tree_sort_state, entry1->time - entry2->time);
3982         case ORDERBY_AUTHOR:
3983                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3985         case ORDERBY_NAME:
3986         default:
3987                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3988         }
3992 static struct line *
3993 tree_entry(struct view *view, enum line_type type, const char *path,
3994            const char *mode, const char *id)
3996         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3997         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3999         if (!entry || !line) {
4000                 free(entry);
4001                 return NULL;
4002         }
4004         strncpy(entry->name, path, strlen(path));
4005         if (mode)
4006                 entry->mode = strtoul(mode, NULL, 8);
4007         if (id)
4008                 string_copy_rev(entry->id, id);
4010         return line;
4013 static bool
4014 tree_read_date(struct view *view, char *text, bool *read_date)
4016         static const char *author_name;
4017         static time_t author_time;
4019         if (!text && *read_date) {
4020                 *read_date = FALSE;
4021                 return TRUE;
4023         } else if (!text) {
4024                 char *path = *opt_path ? opt_path : ".";
4025                 /* Find next entry to process */
4026                 const char *log_file[] = {
4027                         "git", "log", "--no-color", "--pretty=raw",
4028                                 "--cc", "--raw", view->id, "--", path, NULL
4029                 };
4030                 struct io io = {};
4032                 if (!view->lines) {
4033                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4034                         report("Tree is empty");
4035                         return TRUE;
4036                 }
4038                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
4039                         report("Failed to load tree data");
4040                         return TRUE;
4041                 }
4043                 done_io(view->pipe);
4044                 view->io = io;
4045                 *read_date = TRUE;
4046                 return FALSE;
4048         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4049                 parse_author_line(text + STRING_SIZE("author "),
4050                                   &author_name, &author_time);
4052         } else if (*text == ':') {
4053                 char *pos;
4054                 size_t annotated = 1;
4055                 size_t i;
4057                 pos = strchr(text, '\t');
4058                 if (!pos)
4059                         return TRUE;
4060                 text = pos + 1;
4061                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4062                         text += strlen(opt_prefix);
4063                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4064                         text += strlen(opt_path);
4065                 pos = strchr(text, '/');
4066                 if (pos)
4067                         *pos = 0;
4069                 for (i = 1; i < view->lines; i++) {
4070                         struct line *line = &view->line[i];
4071                         struct tree_entry *entry = line->data;
4073                         annotated += !!entry->author;
4074                         if (entry->author || strcmp(entry->name, text))
4075                                 continue;
4077                         entry->author = author_name;
4078                         entry->time = author_time;
4079                         line->dirty = 1;
4080                         break;
4081                 }
4083                 if (annotated == view->lines)
4084                         kill_io(view->pipe);
4085         }
4086         return TRUE;
4089 static bool
4090 tree_read(struct view *view, char *text)
4092         static bool read_date = FALSE;
4093         struct tree_entry *data;
4094         struct line *entry, *line;
4095         enum line_type type;
4096         size_t textlen = text ? strlen(text) : 0;
4097         char *path = text + SIZEOF_TREE_ATTR;
4099         if (read_date || !text)
4100                 return tree_read_date(view, text, &read_date);
4102         if (textlen <= SIZEOF_TREE_ATTR)
4103                 return FALSE;
4104         if (view->lines == 0 &&
4105             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4106                 return FALSE;
4108         /* Strip the path part ... */
4109         if (*opt_path) {
4110                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4111                 size_t striplen = strlen(opt_path);
4113                 if (pathlen > striplen)
4114                         memmove(path, path + striplen,
4115                                 pathlen - striplen + 1);
4117                 /* Insert "link" to parent directory. */
4118                 if (view->lines == 1 &&
4119                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4120                         return FALSE;
4121         }
4123         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4124         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4125         if (!entry)
4126                 return FALSE;
4127         data = entry->data;
4129         /* Skip "Directory ..." and ".." line. */
4130         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4131                 if (tree_compare_entry(line, entry) <= 0)
4132                         continue;
4134                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4136                 line->data = data;
4137                 line->type = type;
4138                 for (; line <= entry; line++)
4139                         line->dirty = line->cleareol = 1;
4140                 return TRUE;
4141         }
4143         if (tree_lineno > view->lineno) {
4144                 view->lineno = tree_lineno;
4145                 tree_lineno = 0;
4146         }
4148         return TRUE;
4151 static bool
4152 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4154         struct tree_entry *entry = line->data;
4156         if (line->type == LINE_TREE_HEAD) {
4157                 if (draw_text(view, line->type, "Directory path /", TRUE))
4158                         return TRUE;
4159         } else {
4160                 if (draw_mode(view, entry->mode))
4161                         return TRUE;
4163                 if (opt_author && draw_author(view, entry->author))
4164                         return TRUE;
4166                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4167                         return TRUE;
4168         }
4169         if (draw_text(view, line->type, entry->name, TRUE))
4170                 return TRUE;
4171         return TRUE;
4174 static void
4175 open_blob_editor()
4177         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4178         int fd = mkstemp(file);
4180         if (fd == -1)
4181                 report("Failed to create temporary file");
4182         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4183                 report("Failed to save blob data to file");
4184         else
4185                 open_editor(FALSE, file);
4186         if (fd != -1)
4187                 unlink(file);
4190 static enum request
4191 tree_request(struct view *view, enum request request, struct line *line)
4193         enum open_flags flags;
4195         switch (request) {
4196         case REQ_VIEW_BLAME:
4197                 if (line->type != LINE_TREE_FILE) {
4198                         report("Blame only supported for files");
4199                         return REQ_NONE;
4200                 }
4202                 string_copy(opt_ref, view->vid);
4203                 return request;
4205         case REQ_EDIT:
4206                 if (line->type != LINE_TREE_FILE) {
4207                         report("Edit only supported for files");
4208                 } else if (!is_head_commit(view->vid)) {
4209                         open_blob_editor();
4210                 } else {
4211                         open_editor(TRUE, opt_file);
4212                 }
4213                 return REQ_NONE;
4215         case REQ_TOGGLE_SORT_FIELD:
4216         case REQ_TOGGLE_SORT_ORDER:
4217                 sort_view(view, request, &tree_sort_state, tree_compare);
4218                 return REQ_NONE;
4220         case REQ_PARENT:
4221                 if (!*opt_path) {
4222                         /* quit view if at top of tree */
4223                         return REQ_VIEW_CLOSE;
4224                 }
4225                 /* fake 'cd  ..' */
4226                 line = &view->line[1];
4227                 break;
4229         case REQ_ENTER:
4230                 break;
4232         default:
4233                 return request;
4234         }
4236         /* Cleanup the stack if the tree view is at a different tree. */
4237         while (!*opt_path && tree_stack)
4238                 pop_tree_stack_entry();
4240         switch (line->type) {
4241         case LINE_TREE_DIR:
4242                 /* Depending on whether it is a subdirectory or parent link
4243                  * mangle the path buffer. */
4244                 if (line == &view->line[1] && *opt_path) {
4245                         pop_tree_stack_entry();
4247                 } else {
4248                         const char *basename = tree_path(line);
4250                         push_tree_stack_entry(basename, view->lineno);
4251                 }
4253                 /* Trees and subtrees share the same ID, so they are not not
4254                  * unique like blobs. */
4255                 flags = OPEN_RELOAD;
4256                 request = REQ_VIEW_TREE;
4257                 break;
4259         case LINE_TREE_FILE:
4260                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4261                 request = REQ_VIEW_BLOB;
4262                 break;
4264         default:
4265                 return REQ_NONE;
4266         }
4268         open_view(view, request, flags);
4269         if (request == REQ_VIEW_TREE)
4270                 view->lineno = tree_lineno;
4272         return REQ_NONE;
4275 static bool
4276 tree_grep(struct view *view, struct line *line)
4278         struct tree_entry *entry = line->data;
4279         const char *text[] = {
4280                 entry->name,
4281                 opt_author ? entry->author : "",
4282                 opt_date ? mkdate(&entry->time) : "",
4283                 NULL
4284         };
4286         return grep_text(view, text);
4289 static void
4290 tree_select(struct view *view, struct line *line)
4292         struct tree_entry *entry = line->data;
4294         if (line->type == LINE_TREE_FILE) {
4295                 string_copy_rev(ref_blob, entry->id);
4296                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4298         } else if (line->type != LINE_TREE_DIR) {
4299                 return;
4300         }
4302         string_copy_rev(view->ref, entry->id);
4305 static const char *tree_argv[SIZEOF_ARG] = {
4306         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4307 };
4309 static struct view_ops tree_ops = {
4310         "file",
4311         tree_argv,
4312         NULL,
4313         tree_read,
4314         tree_draw,
4315         tree_request,
4316         tree_grep,
4317         tree_select,
4318 };
4320 static bool
4321 blob_read(struct view *view, char *line)
4323         if (!line)
4324                 return TRUE;
4325         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4328 static enum request
4329 blob_request(struct view *view, enum request request, struct line *line)
4331         switch (request) {
4332         case REQ_EDIT:
4333                 open_blob_editor();
4334                 return REQ_NONE;
4335         default:
4336                 return pager_request(view, request, line);
4337         }
4340 static const char *blob_argv[SIZEOF_ARG] = {
4341         "git", "cat-file", "blob", "%(blob)", NULL
4342 };
4344 static struct view_ops blob_ops = {
4345         "line",
4346         blob_argv,
4347         NULL,
4348         blob_read,
4349         pager_draw,
4350         blob_request,
4351         pager_grep,
4352         pager_select,
4353 };
4355 /*
4356  * Blame backend
4357  *
4358  * Loading the blame view is a two phase job:
4359  *
4360  *  1. File content is read either using opt_file from the
4361  *     filesystem or using git-cat-file.
4362  *  2. Then blame information is incrementally added by
4363  *     reading output from git-blame.
4364  */
4366 static const char *blame_head_argv[] = {
4367         "git", "blame", "--incremental", "--", "%(file)", NULL
4368 };
4370 static const char *blame_ref_argv[] = {
4371         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4372 };
4374 static const char *blame_cat_file_argv[] = {
4375         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4376 };
4378 struct blame_commit {
4379         char id[SIZEOF_REV];            /* SHA1 ID. */
4380         char title[128];                /* First line of the commit message. */
4381         const char *author;             /* Author of the commit. */
4382         time_t time;                    /* Date from the author ident. */
4383         char filename[128];             /* Name of file. */
4384         bool has_previous;              /* Was a "previous" line detected. */
4385 };
4387 struct blame {
4388         struct blame_commit *commit;
4389         unsigned long lineno;
4390         char text[1];
4391 };
4393 static bool
4394 blame_open(struct view *view)
4396         if (*opt_ref || !io_open(&view->io, opt_file)) {
4397                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4398                         return FALSE;
4399         }
4401         setup_update(view, opt_file);
4402         string_format(view->ref, "%s ...", opt_file);
4404         return TRUE;
4407 static struct blame_commit *
4408 get_blame_commit(struct view *view, const char *id)
4410         size_t i;
4412         for (i = 0; i < view->lines; i++) {
4413                 struct blame *blame = view->line[i].data;
4415                 if (!blame->commit)
4416                         continue;
4418                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4419                         return blame->commit;
4420         }
4422         {
4423                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4425                 if (commit)
4426                         string_ncopy(commit->id, id, SIZEOF_REV);
4427                 return commit;
4428         }
4431 static bool
4432 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4434         const char *pos = *posref;
4436         *posref = NULL;
4437         pos = strchr(pos + 1, ' ');
4438         if (!pos || !isdigit(pos[1]))
4439                 return FALSE;
4440         *number = atoi(pos + 1);
4441         if (*number < min || *number > max)
4442                 return FALSE;
4444         *posref = pos;
4445         return TRUE;
4448 static struct blame_commit *
4449 parse_blame_commit(struct view *view, const char *text, int *blamed)
4451         struct blame_commit *commit;
4452         struct blame *blame;
4453         const char *pos = text + SIZEOF_REV - 2;
4454         size_t orig_lineno = 0;
4455         size_t lineno;
4456         size_t group;
4458         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4459                 return NULL;
4461         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4462             !parse_number(&pos, &lineno, 1, view->lines) ||
4463             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4464                 return NULL;
4466         commit = get_blame_commit(view, text);
4467         if (!commit)
4468                 return NULL;
4470         *blamed += group;
4471         while (group--) {
4472                 struct line *line = &view->line[lineno + group - 1];
4474                 blame = line->data;
4475                 blame->commit = commit;
4476                 blame->lineno = orig_lineno + group - 1;
4477                 line->dirty = 1;
4478         }
4480         return commit;
4483 static bool
4484 blame_read_file(struct view *view, const char *line, bool *read_file)
4486         if (!line) {
4487                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4488                 struct io io = {};
4490                 if (view->lines == 0 && !view->parent)
4491                         die("No blame exist for %s", view->vid);
4493                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4494                         report("Failed to load blame data");
4495                         return TRUE;
4496                 }
4498                 done_io(view->pipe);
4499                 view->io = io;
4500                 *read_file = FALSE;
4501                 return FALSE;
4503         } else {
4504                 size_t linelen = strlen(line);
4505                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4507                 if (!blame)
4508                         return FALSE;
4510                 blame->commit = NULL;
4511                 strncpy(blame->text, line, linelen);
4512                 blame->text[linelen] = 0;
4513                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4514         }
4517 static bool
4518 match_blame_header(const char *name, char **line)
4520         size_t namelen = strlen(name);
4521         bool matched = !strncmp(name, *line, namelen);
4523         if (matched)
4524                 *line += namelen;
4526         return matched;
4529 static bool
4530 blame_read(struct view *view, char *line)
4532         static struct blame_commit *commit = NULL;
4533         static int blamed = 0;
4534         static bool read_file = TRUE;
4536         if (read_file)
4537                 return blame_read_file(view, line, &read_file);
4539         if (!line) {
4540                 /* Reset all! */
4541                 commit = NULL;
4542                 blamed = 0;
4543                 read_file = TRUE;
4544                 string_format(view->ref, "%s", view->vid);
4545                 if (view_is_displayed(view)) {
4546                         update_view_title(view);
4547                         redraw_view_from(view, 0);
4548                 }
4549                 return TRUE;
4550         }
4552         if (!commit) {
4553                 commit = parse_blame_commit(view, line, &blamed);
4554                 string_format(view->ref, "%s %2d%%", view->vid,
4555                               view->lines ? blamed * 100 / view->lines : 0);
4557         } else if (match_blame_header("author ", &line)) {
4558                 commit->author = get_author(line);
4560         } else if (match_blame_header("author-time ", &line)) {
4561                 commit->time = (time_t) atol(line);
4563         } else if (match_blame_header("author-tz ", &line)) {
4564                 parse_timezone(&commit->time, line);
4566         } else if (match_blame_header("summary ", &line)) {
4567                 string_ncopy(commit->title, line, strlen(line));
4569         } else if (match_blame_header("previous ", &line)) {
4570                 commit->has_previous = TRUE;
4572         } else if (match_blame_header("filename ", &line)) {
4573                 string_ncopy(commit->filename, line, strlen(line));
4574                 commit = NULL;
4575         }
4577         return TRUE;
4580 static bool
4581 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4583         struct blame *blame = line->data;
4584         time_t *time = NULL;
4585         const char *id = NULL, *author = NULL;
4586         char text[SIZEOF_STR];
4588         if (blame->commit && *blame->commit->filename) {
4589                 id = blame->commit->id;
4590                 author = blame->commit->author;
4591                 time = &blame->commit->time;
4592         }
4594         if (opt_date && draw_date(view, time))
4595                 return TRUE;
4597         if (opt_author && draw_author(view, author))
4598                 return TRUE;
4600         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4601                 return TRUE;
4603         if (draw_lineno(view, lineno))
4604                 return TRUE;
4606         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4607         draw_text(view, LINE_DEFAULT, text, TRUE);
4608         return TRUE;
4611 static bool
4612 check_blame_commit(struct blame *blame, bool check_null_id)
4614         if (!blame->commit)
4615                 report("Commit data not loaded yet");
4616         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4617                 report("No commit exist for the selected line");
4618         else
4619                 return TRUE;
4620         return FALSE;
4623 static void
4624 setup_blame_parent_line(struct view *view, struct blame *blame)
4626         const char *diff_tree_argv[] = {
4627                 "git", "diff-tree", "-U0", blame->commit->id,
4628                         "--", blame->commit->filename, NULL
4629         };
4630         struct io io = {};
4631         int parent_lineno = -1;
4632         int blamed_lineno = -1;
4633         char *line;
4635         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4636                 return;
4638         while ((line = io_get(&io, '\n', TRUE))) {
4639                 if (*line == '@') {
4640                         char *pos = strchr(line, '+');
4642                         parent_lineno = atoi(line + 4);
4643                         if (pos)
4644                                 blamed_lineno = atoi(pos + 1);
4646                 } else if (*line == '+' && parent_lineno != -1) {
4647                         if (blame->lineno == blamed_lineno - 1 &&
4648                             !strcmp(blame->text, line + 1)) {
4649                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4650                                 break;
4651                         }
4652                         blamed_lineno++;
4653                 }
4654         }
4656         done_io(&io);
4659 static enum request
4660 blame_request(struct view *view, enum request request, struct line *line)
4662         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4663         struct blame *blame = line->data;
4665         switch (request) {
4666         case REQ_VIEW_BLAME:
4667                 if (check_blame_commit(blame, TRUE)) {
4668                         string_copy(opt_ref, blame->commit->id);
4669                         string_copy(opt_file, blame->commit->filename);
4670                         if (blame->lineno)
4671                                 view->lineno = blame->lineno;
4672                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4673                 }
4674                 break;
4676         case REQ_PARENT:
4677                 if (check_blame_commit(blame, TRUE) &&
4678                     select_commit_parent(blame->commit->id, opt_ref,
4679                                          blame->commit->filename)) {
4680                         string_copy(opt_file, blame->commit->filename);
4681                         setup_blame_parent_line(view, blame);
4682                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4683                 }
4684                 break;
4686         case REQ_ENTER:
4687                 if (!check_blame_commit(blame, FALSE))
4688                         break;
4690                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4691                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4692                         break;
4694                 if (!strcmp(blame->commit->id, NULL_ID)) {
4695                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4696                         const char *diff_index_argv[] = {
4697                                 "git", "diff-index", "--root", "--patch-with-stat",
4698                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4699                         };
4701                         if (!blame->commit->has_previous) {
4702                                 diff_index_argv[1] = "diff";
4703                                 diff_index_argv[2] = "--no-color";
4704                                 diff_index_argv[6] = "--";
4705                                 diff_index_argv[7] = "/dev/null";
4706                         }
4708                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4709                                 report("Failed to allocate diff command");
4710                                 break;
4711                         }
4712                         flags |= OPEN_PREPARED;
4713                 }
4715                 open_view(view, REQ_VIEW_DIFF, flags);
4716                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4717                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4718                 break;
4720         default:
4721                 return request;
4722         }
4724         return REQ_NONE;
4727 static bool
4728 blame_grep(struct view *view, struct line *line)
4730         struct blame *blame = line->data;
4731         struct blame_commit *commit = blame->commit;
4732         const char *text[] = {
4733                 blame->text,
4734                 commit ? commit->title : "",
4735                 commit ? commit->id : "",
4736                 commit && opt_author ? commit->author : "",
4737                 commit && opt_date ? mkdate(&commit->time) : "",
4738                 NULL
4739         };
4741         return grep_text(view, text);
4744 static void
4745 blame_select(struct view *view, struct line *line)
4747         struct blame *blame = line->data;
4748         struct blame_commit *commit = blame->commit;
4750         if (!commit)
4751                 return;
4753         if (!strcmp(commit->id, NULL_ID))
4754                 string_ncopy(ref_commit, "HEAD", 4);
4755         else
4756                 string_copy_rev(ref_commit, commit->id);
4759 static struct view_ops blame_ops = {
4760         "line",
4761         NULL,
4762         blame_open,
4763         blame_read,
4764         blame_draw,
4765         blame_request,
4766         blame_grep,
4767         blame_select,
4768 };
4770 /*
4771  * Branch backend
4772  */
4774 struct branch {
4775         const char *author;             /* Author of the last commit. */
4776         time_t time;                    /* Date of the last activity. */
4777         struct ref *ref;                /* Name and commit ID information. */
4778 };
4780 static const enum sort_field branch_sort_fields[] = {
4781         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4782 };
4783 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4785 static int
4786 branch_compare(const void *l1, const void *l2)
4788         const struct branch *branch1 = ((const struct line *) l1)->data;
4789         const struct branch *branch2 = ((const struct line *) l2)->data;
4791         switch (get_sort_field(branch_sort_state)) {
4792         case ORDERBY_DATE:
4793                 return sort_order(branch_sort_state, branch1->time - branch2->time);
4795         case ORDERBY_AUTHOR:
4796                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4798         case ORDERBY_NAME:
4799         default:
4800                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4801         }
4804 static bool
4805 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4807         struct branch *branch = line->data;
4808         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4810         if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4811                 return TRUE;
4813         if (opt_author && draw_author(view, branch->author))
4814                 return TRUE;
4816         draw_text(view, type, branch->ref->name, TRUE);
4817         return TRUE;
4820 static enum request
4821 branch_request(struct view *view, enum request request, struct line *line)
4823         switch (request) {
4824         case REQ_REFRESH:
4825                 load_refs();
4826                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4827                 return REQ_NONE;
4829         case REQ_TOGGLE_SORT_FIELD:
4830         case REQ_TOGGLE_SORT_ORDER:
4831                 sort_view(view, request, &branch_sort_state, branch_compare);
4832                 return REQ_NONE;
4834         case REQ_ENTER:
4835                 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4836                 return REQ_NONE;
4838         default:
4839                 return request;
4840         }
4843 static bool
4844 branch_read(struct view *view, char *line)
4846         static char id[SIZEOF_REV];
4847         struct branch *reference;
4848         size_t i;
4850         if (!line)
4851                 return TRUE;
4853         switch (get_line_type(line)) {
4854         case LINE_COMMIT:
4855                 string_copy_rev(id, line + STRING_SIZE("commit "));
4856                 return TRUE;
4858         case LINE_AUTHOR:
4859                 for (i = 0, reference = NULL; i < view->lines; i++) {
4860                         struct branch *branch = view->line[i].data;
4862                         if (strcmp(branch->ref->id, id))
4863                                 continue;
4865                         view->line[i].dirty = TRUE;
4866                         if (reference) {
4867                                 branch->author = reference->author;
4868                                 branch->time = reference->time;
4869                                 continue;
4870                         }
4872                         parse_author_line(line + STRING_SIZE("author "),
4873                                           &branch->author, &branch->time);
4874                         reference = branch;
4875                 }
4876                 return TRUE;
4878         default:
4879                 return TRUE;
4880         }
4884 static bool
4885 branch_open_visitor(void *data, struct ref *ref)
4887         struct view *view = data;
4888         struct branch *branch;
4890         if (ref->tag || ref->ltag || ref->remote)
4891                 return TRUE;
4893         branch = calloc(1, sizeof(*branch));
4894         if (!branch)
4895                 return FALSE;
4897         branch->ref = ref;
4898         return !!add_line_data(view, branch, LINE_DEFAULT);
4901 static bool
4902 branch_open(struct view *view)
4904         const char *branch_log[] = {
4905                 "git", "log", "--no-color", "--pretty=raw",
4906                         "--simplify-by-decoration", "--all", NULL
4907         };
4909         if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4910                 report("Failed to load branch data");
4911                 return TRUE;
4912         }
4914         setup_update(view, view->id);
4915         foreach_ref(branch_open_visitor, view);
4916         view->p_restore = TRUE;
4918         return TRUE;
4921 static bool
4922 branch_grep(struct view *view, struct line *line)
4924         struct branch *branch = line->data;
4925         const char *text[] = {
4926                 branch->ref->name,
4927                 branch->author,
4928                 NULL
4929         };
4931         return grep_text(view, text);
4934 static void
4935 branch_select(struct view *view, struct line *line)
4937         struct branch *branch = line->data;
4939         string_copy_rev(view->ref, branch->ref->id);
4940         string_copy_rev(ref_commit, branch->ref->id);
4941         string_copy_rev(ref_head, branch->ref->id);
4944 static struct view_ops branch_ops = {
4945         "branch",
4946         NULL,
4947         branch_open,
4948         branch_read,
4949         branch_draw,
4950         branch_request,
4951         branch_grep,
4952         branch_select,
4953 };
4955 /*
4956  * Status backend
4957  */
4959 struct status {
4960         char status;
4961         struct {
4962                 mode_t mode;
4963                 char rev[SIZEOF_REV];
4964                 char name[SIZEOF_STR];
4965         } old;
4966         struct {
4967                 mode_t mode;
4968                 char rev[SIZEOF_REV];
4969                 char name[SIZEOF_STR];
4970         } new;
4971 };
4973 static char status_onbranch[SIZEOF_STR];
4974 static struct status stage_status;
4975 static enum line_type stage_line_type;
4976 static size_t stage_chunks;
4977 static int *stage_chunk;
4979 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4981 /* This should work even for the "On branch" line. */
4982 static inline bool
4983 status_has_none(struct view *view, struct line *line)
4985         return line < view->line + view->lines && !line[1].data;
4988 /* Get fields from the diff line:
4989  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4990  */
4991 static inline bool
4992 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4994         const char *old_mode = buf +  1;
4995         const char *new_mode = buf +  8;
4996         const char *old_rev  = buf + 15;
4997         const char *new_rev  = buf + 56;
4998         const char *status   = buf + 97;
5000         if (bufsize < 98 ||
5001             old_mode[-1] != ':' ||
5002             new_mode[-1] != ' ' ||
5003             old_rev[-1]  != ' ' ||
5004             new_rev[-1]  != ' ' ||
5005             status[-1]   != ' ')
5006                 return FALSE;
5008         file->status = *status;
5010         string_copy_rev(file->old.rev, old_rev);
5011         string_copy_rev(file->new.rev, new_rev);
5013         file->old.mode = strtoul(old_mode, NULL, 8);
5014         file->new.mode = strtoul(new_mode, NULL, 8);
5016         file->old.name[0] = file->new.name[0] = 0;
5018         return TRUE;
5021 static bool
5022 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5024         struct status *unmerged = NULL;
5025         char *buf;
5026         struct io io = {};
5028         if (!run_io(&io, argv, NULL, IO_RD))
5029                 return FALSE;
5031         add_line_data(view, NULL, type);
5033         while ((buf = io_get(&io, 0, TRUE))) {
5034                 struct status *file = unmerged;
5036                 if (!file) {
5037                         file = calloc(1, sizeof(*file));
5038                         if (!file || !add_line_data(view, file, type))
5039                                 goto error_out;
5040                 }
5042                 /* Parse diff info part. */
5043                 if (status) {
5044                         file->status = status;
5045                         if (status == 'A')
5046                                 string_copy(file->old.rev, NULL_ID);
5048                 } else if (!file->status || file == unmerged) {
5049                         if (!status_get_diff(file, buf, strlen(buf)))
5050                                 goto error_out;
5052                         buf = io_get(&io, 0, TRUE);
5053                         if (!buf)
5054                                 break;
5056                         /* Collapse all modified entries that follow an
5057                          * associated unmerged entry. */
5058                         if (unmerged == file) {
5059                                 unmerged->status = 'U';
5060                                 unmerged = NULL;
5061                         } else if (file->status == 'U') {
5062                                 unmerged = file;
5063                         }
5064                 }
5066                 /* Grab the old name for rename/copy. */
5067                 if (!*file->old.name &&
5068                     (file->status == 'R' || file->status == 'C')) {
5069                         string_ncopy(file->old.name, buf, strlen(buf));
5071                         buf = io_get(&io, 0, TRUE);
5072                         if (!buf)
5073                                 break;
5074                 }
5076                 /* git-ls-files just delivers a NUL separated list of
5077                  * file names similar to the second half of the
5078                  * git-diff-* output. */
5079                 string_ncopy(file->new.name, buf, strlen(buf));
5080                 if (!*file->old.name)
5081                         string_copy(file->old.name, file->new.name);
5082                 file = NULL;
5083         }
5085         if (io_error(&io)) {
5086 error_out:
5087                 done_io(&io);
5088                 return FALSE;
5089         }
5091         if (!view->line[view->lines - 1].data)
5092                 add_line_data(view, NULL, LINE_STAT_NONE);
5094         done_io(&io);
5095         return TRUE;
5098 /* Don't show unmerged entries in the staged section. */
5099 static const char *status_diff_index_argv[] = {
5100         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5101                              "--cached", "-M", "HEAD", NULL
5102 };
5104 static const char *status_diff_files_argv[] = {
5105         "git", "diff-files", "-z", NULL
5106 };
5108 static const char *status_list_other_argv[] = {
5109         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5110 };
5112 static const char *status_list_no_head_argv[] = {
5113         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5114 };
5116 static const char *update_index_argv[] = {
5117         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5118 };
5120 /* Restore the previous line number to stay in the context or select a
5121  * line with something that can be updated. */
5122 static void
5123 status_restore(struct view *view)
5125         if (view->p_lineno >= view->lines)
5126                 view->p_lineno = view->lines - 1;
5127         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5128                 view->p_lineno++;
5129         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5130                 view->p_lineno--;
5132         /* If the above fails, always skip the "On branch" line. */
5133         if (view->p_lineno < view->lines)
5134                 view->lineno = view->p_lineno;
5135         else
5136                 view->lineno = 1;
5138         if (view->lineno < view->offset)
5139                 view->offset = view->lineno;
5140         else if (view->offset + view->height <= view->lineno)
5141                 view->offset = view->lineno - view->height + 1;
5143         view->p_restore = FALSE;
5146 static void
5147 status_update_onbranch(void)
5149         static const char *paths[][2] = {
5150                 { "rebase-apply/rebasing",      "Rebasing" },
5151                 { "rebase-apply/applying",      "Applying mailbox" },
5152                 { "rebase-apply/",              "Rebasing mailbox" },
5153                 { "rebase-merge/interactive",   "Interactive rebase" },
5154                 { "rebase-merge/",              "Rebase merge" },
5155                 { "MERGE_HEAD",                 "Merging" },
5156                 { "BISECT_LOG",                 "Bisecting" },
5157                 { "HEAD",                       "On branch" },
5158         };
5159         char buf[SIZEOF_STR];
5160         struct stat stat;
5161         int i;
5163         if (is_initial_commit()) {
5164                 string_copy(status_onbranch, "Initial commit");
5165                 return;
5166         }
5168         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5169                 char *head = opt_head;
5171                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5172                     lstat(buf, &stat) < 0)
5173                         continue;
5175                 if (!*opt_head) {
5176                         struct io io = {};
5178                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5179                             io_open(&io, buf) &&
5180                             io_read_buf(&io, buf, sizeof(buf))) {
5181                                 head = buf;
5182                                 if (!prefixcmp(head, "refs/heads/"))
5183                                         head += STRING_SIZE("refs/heads/");
5184                         }
5185                 }
5187                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5188                         string_copy(status_onbranch, opt_head);
5189                 return;
5190         }
5192         string_copy(status_onbranch, "Not currently on any branch");
5195 /* First parse staged info using git-diff-index(1), then parse unstaged
5196  * info using git-diff-files(1), and finally untracked files using
5197  * git-ls-files(1). */
5198 static bool
5199 status_open(struct view *view)
5201         reset_view(view);
5203         add_line_data(view, NULL, LINE_STAT_HEAD);
5204         status_update_onbranch();
5206         run_io_bg(update_index_argv);
5208         if (is_initial_commit()) {
5209                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5210                         return FALSE;
5211         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5212                 return FALSE;
5213         }
5215         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5216             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5217                 return FALSE;
5219         /* Restore the exact position or use the specialized restore
5220          * mode? */
5221         if (!view->p_restore)
5222                 status_restore(view);
5223         return TRUE;
5226 static bool
5227 status_draw(struct view *view, struct line *line, unsigned int lineno)
5229         struct status *status = line->data;
5230         enum line_type type;
5231         const char *text;
5233         if (!status) {
5234                 switch (line->type) {
5235                 case LINE_STAT_STAGED:
5236                         type = LINE_STAT_SECTION;
5237                         text = "Changes to be committed:";
5238                         break;
5240                 case LINE_STAT_UNSTAGED:
5241                         type = LINE_STAT_SECTION;
5242                         text = "Changed but not updated:";
5243                         break;
5245                 case LINE_STAT_UNTRACKED:
5246                         type = LINE_STAT_SECTION;
5247                         text = "Untracked files:";
5248                         break;
5250                 case LINE_STAT_NONE:
5251                         type = LINE_DEFAULT;
5252                         text = "  (no files)";
5253                         break;
5255                 case LINE_STAT_HEAD:
5256                         type = LINE_STAT_HEAD;
5257                         text = status_onbranch;
5258                         break;
5260                 default:
5261                         return FALSE;
5262                 }
5263         } else {
5264                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5266                 buf[0] = status->status;
5267                 if (draw_text(view, line->type, buf, TRUE))
5268                         return TRUE;
5269                 type = LINE_DEFAULT;
5270                 text = status->new.name;
5271         }
5273         draw_text(view, type, text, TRUE);
5274         return TRUE;
5277 static enum request
5278 status_load_error(struct view *view, struct view *stage, const char *path)
5280         if (displayed_views() == 2 || display[current_view] != view)
5281                 maximize_view(view);
5282         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5283         return REQ_NONE;
5286 static enum request
5287 status_enter(struct view *view, struct line *line)
5289         struct status *status = line->data;
5290         const char *oldpath = status ? status->old.name : NULL;
5291         /* Diffs for unmerged entries are empty when passing the new
5292          * path, so leave it empty. */
5293         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5294         const char *info;
5295         enum open_flags split;
5296         struct view *stage = VIEW(REQ_VIEW_STAGE);
5298         if (line->type == LINE_STAT_NONE ||
5299             (!status && line[1].type == LINE_STAT_NONE)) {
5300                 report("No file to diff");
5301                 return REQ_NONE;
5302         }
5304         switch (line->type) {
5305         case LINE_STAT_STAGED:
5306                 if (is_initial_commit()) {
5307                         const char *no_head_diff_argv[] = {
5308                                 "git", "diff", "--no-color", "--patch-with-stat",
5309                                         "--", "/dev/null", newpath, NULL
5310                         };
5312                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5313                                 return status_load_error(view, stage, newpath);
5314                 } else {
5315                         const char *index_show_argv[] = {
5316                                 "git", "diff-index", "--root", "--patch-with-stat",
5317                                         "-C", "-M", "--cached", "HEAD", "--",
5318                                         oldpath, newpath, NULL
5319                         };
5321                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5322                                 return status_load_error(view, stage, newpath);
5323                 }
5325                 if (status)
5326                         info = "Staged changes to %s";
5327                 else
5328                         info = "Staged changes";
5329                 break;
5331         case LINE_STAT_UNSTAGED:
5332         {
5333                 const char *files_show_argv[] = {
5334                         "git", "diff-files", "--root", "--patch-with-stat",
5335                                 "-C", "-M", "--", oldpath, newpath, NULL
5336                 };
5338                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5339                         return status_load_error(view, stage, newpath);
5340                 if (status)
5341                         info = "Unstaged changes to %s";
5342                 else
5343                         info = "Unstaged changes";
5344                 break;
5345         }
5346         case LINE_STAT_UNTRACKED:
5347                 if (!newpath) {
5348                         report("No file to show");
5349                         return REQ_NONE;
5350                 }
5352                 if (!suffixcmp(status->new.name, -1, "/")) {
5353                         report("Cannot display a directory");
5354                         return REQ_NONE;
5355                 }
5357                 if (!prepare_update_file(stage, newpath))
5358                         return status_load_error(view, stage, newpath);
5359                 info = "Untracked file %s";
5360                 break;
5362         case LINE_STAT_HEAD:
5363                 return REQ_NONE;
5365         default:
5366                 die("line type %d not handled in switch", line->type);
5367         }
5369         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5370         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5371         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5372                 if (status) {
5373                         stage_status = *status;
5374                 } else {
5375                         memset(&stage_status, 0, sizeof(stage_status));
5376                 }
5378                 stage_line_type = line->type;
5379                 stage_chunks = 0;
5380                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5381         }
5383         return REQ_NONE;
5386 static bool
5387 status_exists(struct status *status, enum line_type type)
5389         struct view *view = VIEW(REQ_VIEW_STATUS);
5390         unsigned long lineno;
5392         for (lineno = 0; lineno < view->lines; lineno++) {
5393                 struct line *line = &view->line[lineno];
5394                 struct status *pos = line->data;
5396                 if (line->type != type)
5397                         continue;
5398                 if (!pos && (!status || !status->status) && line[1].data) {
5399                         select_view_line(view, lineno);
5400                         return TRUE;
5401                 }
5402                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5403                         select_view_line(view, lineno);
5404                         return TRUE;
5405                 }
5406         }
5408         return FALSE;
5412 static bool
5413 status_update_prepare(struct io *io, enum line_type type)
5415         const char *staged_argv[] = {
5416                 "git", "update-index", "-z", "--index-info", NULL
5417         };
5418         const char *others_argv[] = {
5419                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5420         };
5422         switch (type) {
5423         case LINE_STAT_STAGED:
5424                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5426         case LINE_STAT_UNSTAGED:
5427                 return run_io(io, others_argv, opt_cdup, IO_WR);
5429         case LINE_STAT_UNTRACKED:
5430                 return run_io(io, others_argv, NULL, IO_WR);
5432         default:
5433                 die("line type %d not handled in switch", type);
5434                 return FALSE;
5435         }
5438 static bool
5439 status_update_write(struct io *io, struct status *status, enum line_type type)
5441         char buf[SIZEOF_STR];
5442         size_t bufsize = 0;
5444         switch (type) {
5445         case LINE_STAT_STAGED:
5446                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5447                                         status->old.mode,
5448                                         status->old.rev,
5449                                         status->old.name, 0))
5450                         return FALSE;
5451                 break;
5453         case LINE_STAT_UNSTAGED:
5454         case LINE_STAT_UNTRACKED:
5455                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5456                         return FALSE;
5457                 break;
5459         default:
5460                 die("line type %d not handled in switch", type);
5461         }
5463         return io_write(io, buf, bufsize);
5466 static bool
5467 status_update_file(struct status *status, enum line_type type)
5469         struct io io = {};
5470         bool result;
5472         if (!status_update_prepare(&io, type))
5473                 return FALSE;
5475         result = status_update_write(&io, status, type);
5476         return done_io(&io) && result;
5479 static bool
5480 status_update_files(struct view *view, struct line *line)
5482         char buf[sizeof(view->ref)];
5483         struct io io = {};
5484         bool result = TRUE;
5485         struct line *pos = view->line + view->lines;
5486         int files = 0;
5487         int file, done;
5488         int cursor_y, cursor_x;
5490         if (!status_update_prepare(&io, line->type))
5491                 return FALSE;
5493         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5494                 files++;
5496         string_copy(buf, view->ref);
5497         getsyx(cursor_y, cursor_x);
5498         for (file = 0, done = 5; result && file < files; line++, file++) {
5499                 int almost_done = file * 100 / files;
5501                 if (almost_done > done) {
5502                         done = almost_done;
5503                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5504                                       file, files, done);
5505                         update_view_title(view);
5506                         setsyx(cursor_y, cursor_x);
5507                         doupdate();
5508                 }
5509                 result = status_update_write(&io, line->data, line->type);
5510         }
5511         string_copy(view->ref, buf);
5513         return done_io(&io) && result;
5516 static bool
5517 status_update(struct view *view)
5519         struct line *line = &view->line[view->lineno];
5521         assert(view->lines);
5523         if (!line->data) {
5524                 /* This should work even for the "On branch" line. */
5525                 if (line < view->line + view->lines && !line[1].data) {
5526                         report("Nothing to update");
5527                         return FALSE;
5528                 }
5530                 if (!status_update_files(view, line + 1)) {
5531                         report("Failed to update file status");
5532                         return FALSE;
5533                 }
5535         } else if (!status_update_file(line->data, line->type)) {
5536                 report("Failed to update file status");
5537                 return FALSE;
5538         }
5540         return TRUE;
5543 static bool
5544 status_revert(struct status *status, enum line_type type, bool has_none)
5546         if (!status || type != LINE_STAT_UNSTAGED) {
5547                 if (type == LINE_STAT_STAGED) {
5548                         report("Cannot revert changes to staged files");
5549                 } else if (type == LINE_STAT_UNTRACKED) {
5550                         report("Cannot revert changes to untracked files");
5551                 } else if (has_none) {
5552                         report("Nothing to revert");
5553                 } else {
5554                         report("Cannot revert changes to multiple files");
5555                 }
5556                 return FALSE;
5558         } else {
5559                 char mode[10] = "100644";
5560                 const char *reset_argv[] = {
5561                         "git", "update-index", "--cacheinfo", mode,
5562                                 status->old.rev, status->old.name, NULL
5563                 };
5564                 const char *checkout_argv[] = {
5565                         "git", "checkout", "--", status->old.name, NULL
5566                 };
5568                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5569                         return FALSE;
5570                 string_format(mode, "%o", status->old.mode);
5571                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5572                         run_io_fg(checkout_argv, opt_cdup);
5573         }
5576 static enum request
5577 status_request(struct view *view, enum request request, struct line *line)
5579         struct status *status = line->data;
5581         switch (request) {
5582         case REQ_STATUS_UPDATE:
5583                 if (!status_update(view))
5584                         return REQ_NONE;
5585                 break;
5587         case REQ_STATUS_REVERT:
5588                 if (!status_revert(status, line->type, status_has_none(view, line)))
5589                         return REQ_NONE;
5590                 break;
5592         case REQ_STATUS_MERGE:
5593                 if (!status || status->status != 'U') {
5594                         report("Merging only possible for files with unmerged status ('U').");
5595                         return REQ_NONE;
5596                 }
5597                 open_mergetool(status->new.name);
5598                 break;
5600         case REQ_EDIT:
5601                 if (!status)
5602                         return request;
5603                 if (status->status == 'D') {
5604                         report("File has been deleted.");
5605                         return REQ_NONE;
5606                 }
5608                 open_editor(status->status != '?', status->new.name);
5609                 break;
5611         case REQ_VIEW_BLAME:
5612                 if (status) {
5613                         string_copy(opt_file, status->new.name);
5614                         opt_ref[0] = 0;
5615                 }
5616                 return request;
5618         case REQ_ENTER:
5619                 /* After returning the status view has been split to
5620                  * show the stage view. No further reloading is
5621                  * necessary. */
5622                 return status_enter(view, line);
5624         case REQ_REFRESH:
5625                 /* Simply reload the view. */
5626                 break;
5628         default:
5629                 return request;
5630         }
5632         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5634         return REQ_NONE;
5637 static void
5638 status_select(struct view *view, struct line *line)
5640         struct status *status = line->data;
5641         char file[SIZEOF_STR] = "all files";
5642         const char *text;
5643         const char *key;
5645         if (status && !string_format(file, "'%s'", status->new.name))
5646                 return;
5648         if (!status && line[1].type == LINE_STAT_NONE)
5649                 line++;
5651         switch (line->type) {
5652         case LINE_STAT_STAGED:
5653                 text = "Press %s to unstage %s for commit";
5654                 break;
5656         case LINE_STAT_UNSTAGED:
5657                 text = "Press %s to stage %s for commit";
5658                 break;
5660         case LINE_STAT_UNTRACKED:
5661                 text = "Press %s to stage %s for addition";
5662                 break;
5664         case LINE_STAT_HEAD:
5665         case LINE_STAT_NONE:
5666                 text = "Nothing to update";
5667                 break;
5669         default:
5670                 die("line type %d not handled in switch", line->type);
5671         }
5673         if (status && status->status == 'U') {
5674                 text = "Press %s to resolve conflict in %s";
5675                 key = get_key(REQ_STATUS_MERGE);
5677         } else {
5678                 key = get_key(REQ_STATUS_UPDATE);
5679         }
5681         string_format(view->ref, text, key, file);
5684 static bool
5685 status_grep(struct view *view, struct line *line)
5687         struct status *status = line->data;
5689         if (status) {
5690                 const char buf[2] = { status->status, 0 };
5691                 const char *text[] = { status->new.name, buf, NULL };
5693                 return grep_text(view, text);
5694         }
5696         return FALSE;
5699 static struct view_ops status_ops = {
5700         "file",
5701         NULL,
5702         status_open,
5703         NULL,
5704         status_draw,
5705         status_request,
5706         status_grep,
5707         status_select,
5708 };
5711 static bool
5712 stage_diff_write(struct io *io, struct line *line, struct line *end)
5714         while (line < end) {
5715                 if (!io_write(io, line->data, strlen(line->data)) ||
5716                     !io_write(io, "\n", 1))
5717                         return FALSE;
5718                 line++;
5719                 if (line->type == LINE_DIFF_CHUNK ||
5720                     line->type == LINE_DIFF_HEADER)
5721                         break;
5722         }
5724         return TRUE;
5727 static struct line *
5728 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5730         for (; view->line < line; line--)
5731                 if (line->type == type)
5732                         return line;
5734         return NULL;
5737 static bool
5738 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5740         const char *apply_argv[SIZEOF_ARG] = {
5741                 "git", "apply", "--whitespace=nowarn", NULL
5742         };
5743         struct line *diff_hdr;
5744         struct io io = {};
5745         int argc = 3;
5747         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5748         if (!diff_hdr)
5749                 return FALSE;
5751         if (!revert)
5752                 apply_argv[argc++] = "--cached";
5753         if (revert || stage_line_type == LINE_STAT_STAGED)
5754                 apply_argv[argc++] = "-R";
5755         apply_argv[argc++] = "-";
5756         apply_argv[argc++] = NULL;
5757         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5758                 return FALSE;
5760         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5761             !stage_diff_write(&io, chunk, view->line + view->lines))
5762                 chunk = NULL;
5764         done_io(&io);
5765         run_io_bg(update_index_argv);
5767         return chunk ? TRUE : FALSE;
5770 static bool
5771 stage_update(struct view *view, struct line *line)
5773         struct line *chunk = NULL;
5775         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5776                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5778         if (chunk) {
5779                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5780                         report("Failed to apply chunk");
5781                         return FALSE;
5782                 }
5784         } else if (!stage_status.status) {
5785                 view = VIEW(REQ_VIEW_STATUS);
5787                 for (line = view->line; line < view->line + view->lines; line++)
5788                         if (line->type == stage_line_type)
5789                                 break;
5791                 if (!status_update_files(view, line + 1)) {
5792                         report("Failed to update files");
5793                         return FALSE;
5794                 }
5796         } else if (!status_update_file(&stage_status, stage_line_type)) {
5797                 report("Failed to update file");
5798                 return FALSE;
5799         }
5801         return TRUE;
5804 static bool
5805 stage_revert(struct view *view, struct line *line)
5807         struct line *chunk = NULL;
5809         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5810                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5812         if (chunk) {
5813                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5814                         return FALSE;
5816                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5817                         report("Failed to revert chunk");
5818                         return FALSE;
5819                 }
5820                 return TRUE;
5822         } else {
5823                 return status_revert(stage_status.status ? &stage_status : NULL,
5824                                      stage_line_type, FALSE);
5825         }
5829 static void
5830 stage_next(struct view *view, struct line *line)
5832         int i;
5834         if (!stage_chunks) {
5835                 for (line = view->line; line < view->line + view->lines; line++) {
5836                         if (line->type != LINE_DIFF_CHUNK)
5837                                 continue;
5839                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5840                                 report("Allocation failure");
5841                                 return;
5842                         }
5844                         stage_chunk[stage_chunks++] = line - view->line;
5845                 }
5846         }
5848         for (i = 0; i < stage_chunks; i++) {
5849                 if (stage_chunk[i] > view->lineno) {
5850                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5851                         report("Chunk %d of %d", i + 1, stage_chunks);
5852                         return;
5853                 }
5854         }
5856         report("No next chunk found");
5859 static enum request
5860 stage_request(struct view *view, enum request request, struct line *line)
5862         switch (request) {
5863         case REQ_STATUS_UPDATE:
5864                 if (!stage_update(view, line))
5865                         return REQ_NONE;
5866                 break;
5868         case REQ_STATUS_REVERT:
5869                 if (!stage_revert(view, line))
5870                         return REQ_NONE;
5871                 break;
5873         case REQ_STAGE_NEXT:
5874                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5875                         report("File is untracked; press %s to add",
5876                                get_key(REQ_STATUS_UPDATE));
5877                         return REQ_NONE;
5878                 }
5879                 stage_next(view, line);
5880                 return REQ_NONE;
5882         case REQ_EDIT:
5883                 if (!stage_status.new.name[0])
5884                         return request;
5885                 if (stage_status.status == 'D') {
5886                         report("File has been deleted.");
5887                         return REQ_NONE;
5888                 }
5890                 open_editor(stage_status.status != '?', stage_status.new.name);
5891                 break;
5893         case REQ_REFRESH:
5894                 /* Reload everything ... */
5895                 break;
5897         case REQ_VIEW_BLAME:
5898                 if (stage_status.new.name[0]) {
5899                         string_copy(opt_file, stage_status.new.name);
5900                         opt_ref[0] = 0;
5901                 }
5902                 return request;
5904         case REQ_ENTER:
5905                 return pager_request(view, request, line);
5907         default:
5908                 return request;
5909         }
5911         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5912         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5914         /* Check whether the staged entry still exists, and close the
5915          * stage view if it doesn't. */
5916         if (!status_exists(&stage_status, stage_line_type)) {
5917                 status_restore(VIEW(REQ_VIEW_STATUS));
5918                 return REQ_VIEW_CLOSE;
5919         }
5921         if (stage_line_type == LINE_STAT_UNTRACKED) {
5922                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5923                         report("Cannot display a directory");
5924                         return REQ_NONE;
5925                 }
5927                 if (!prepare_update_file(view, stage_status.new.name)) {
5928                         report("Failed to open file: %s", strerror(errno));
5929                         return REQ_NONE;
5930                 }
5931         }
5932         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5934         return REQ_NONE;
5937 static struct view_ops stage_ops = {
5938         "line",
5939         NULL,
5940         NULL,
5941         pager_read,
5942         pager_draw,
5943         stage_request,
5944         pager_grep,
5945         pager_select,
5946 };
5949 /*
5950  * Revision graph
5951  */
5953 struct commit {
5954         char id[SIZEOF_REV];            /* SHA1 ID. */
5955         char title[128];                /* First line of the commit message. */
5956         const char *author;             /* Author of the commit. */
5957         time_t time;                    /* Date from the author ident. */
5958         struct ref_list *refs;          /* Repository references. */
5959         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5960         size_t graph_size;              /* The width of the graph array. */
5961         bool has_parents;               /* Rewritten --parents seen. */
5962 };
5964 /* Size of rev graph with no  "padding" columns */
5965 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5967 struct rev_graph {
5968         struct rev_graph *prev, *next, *parents;
5969         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5970         size_t size;
5971         struct commit *commit;
5972         size_t pos;
5973         unsigned int boundary:1;
5974 };
5976 /* Parents of the commit being visualized. */
5977 static struct rev_graph graph_parents[4];
5979 /* The current stack of revisions on the graph. */
5980 static struct rev_graph graph_stacks[4] = {
5981         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5982         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5983         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5984         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5985 };
5987 static inline bool
5988 graph_parent_is_merge(struct rev_graph *graph)
5990         return graph->parents->size > 1;
5993 static inline void
5994 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5996         struct commit *commit = graph->commit;
5998         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5999                 commit->graph[commit->graph_size++] = symbol;
6002 static void
6003 clear_rev_graph(struct rev_graph *graph)
6005         graph->boundary = 0;
6006         graph->size = graph->pos = 0;
6007         graph->commit = NULL;
6008         memset(graph->parents, 0, sizeof(*graph->parents));
6011 static void
6012 done_rev_graph(struct rev_graph *graph)
6014         if (graph_parent_is_merge(graph) &&
6015             graph->pos < graph->size - 1 &&
6016             graph->next->size == graph->size + graph->parents->size - 1) {
6017                 size_t i = graph->pos + graph->parents->size - 1;
6019                 graph->commit->graph_size = i * 2;
6020                 while (i < graph->next->size - 1) {
6021                         append_to_rev_graph(graph, ' ');
6022                         append_to_rev_graph(graph, '\\');
6023                         i++;
6024                 }
6025         }
6027         clear_rev_graph(graph);
6030 static void
6031 push_rev_graph(struct rev_graph *graph, const char *parent)
6033         int i;
6035         /* "Collapse" duplicate parents lines.
6036          *
6037          * FIXME: This needs to also update update the drawn graph but
6038          * for now it just serves as a method for pruning graph lines. */
6039         for (i = 0; i < graph->size; i++)
6040                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6041                         return;
6043         if (graph->size < SIZEOF_REVITEMS) {
6044                 string_copy_rev(graph->rev[graph->size++], parent);
6045         }
6048 static chtype
6049 get_rev_graph_symbol(struct rev_graph *graph)
6051         chtype symbol;
6053         if (graph->boundary)
6054                 symbol = REVGRAPH_BOUND;
6055         else if (graph->parents->size == 0)
6056                 symbol = REVGRAPH_INIT;
6057         else if (graph_parent_is_merge(graph))
6058                 symbol = REVGRAPH_MERGE;
6059         else if (graph->pos >= graph->size)
6060                 symbol = REVGRAPH_BRANCH;
6061         else
6062                 symbol = REVGRAPH_COMMIT;
6064         return symbol;
6067 static void
6068 draw_rev_graph(struct rev_graph *graph)
6070         struct rev_filler {
6071                 chtype separator, line;
6072         };
6073         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6074         static struct rev_filler fillers[] = {
6075                 { ' ',  '|' },
6076                 { '`',  '.' },
6077                 { '\'', ' ' },
6078                 { '/',  ' ' },
6079         };
6080         chtype symbol = get_rev_graph_symbol(graph);
6081         struct rev_filler *filler;
6082         size_t i;
6084         if (opt_line_graphics)
6085                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6087         filler = &fillers[DEFAULT];
6089         for (i = 0; i < graph->pos; i++) {
6090                 append_to_rev_graph(graph, filler->line);
6091                 if (graph_parent_is_merge(graph->prev) &&
6092                     graph->prev->pos == i)
6093                         filler = &fillers[RSHARP];
6095                 append_to_rev_graph(graph, filler->separator);
6096         }
6098         /* Place the symbol for this revision. */
6099         append_to_rev_graph(graph, symbol);
6101         if (graph->prev->size > graph->size)
6102                 filler = &fillers[RDIAG];
6103         else
6104                 filler = &fillers[DEFAULT];
6106         i++;
6108         for (; i < graph->size; i++) {
6109                 append_to_rev_graph(graph, filler->separator);
6110                 append_to_rev_graph(graph, filler->line);
6111                 if (graph_parent_is_merge(graph->prev) &&
6112                     i < graph->prev->pos + graph->parents->size)
6113                         filler = &fillers[RSHARP];
6114                 if (graph->prev->size > graph->size)
6115                         filler = &fillers[LDIAG];
6116         }
6118         if (graph->prev->size > graph->size) {
6119                 append_to_rev_graph(graph, filler->separator);
6120                 if (filler->line != ' ')
6121                         append_to_rev_graph(graph, filler->line);
6122         }
6125 /* Prepare the next rev graph */
6126 static void
6127 prepare_rev_graph(struct rev_graph *graph)
6129         size_t i;
6131         /* First, traverse all lines of revisions up to the active one. */
6132         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6133                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6134                         break;
6136                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6137         }
6139         /* Interleave the new revision parent(s). */
6140         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6141                 push_rev_graph(graph->next, graph->parents->rev[i]);
6143         /* Lastly, put any remaining revisions. */
6144         for (i = graph->pos + 1; i < graph->size; i++)
6145                 push_rev_graph(graph->next, graph->rev[i]);
6148 static void
6149 update_rev_graph(struct view *view, struct rev_graph *graph)
6151         /* If this is the finalizing update ... */
6152         if (graph->commit)
6153                 prepare_rev_graph(graph);
6155         /* Graph visualization needs a one rev look-ahead,
6156          * so the first update doesn't visualize anything. */
6157         if (!graph->prev->commit)
6158                 return;
6160         if (view->lines > 2)
6161                 view->line[view->lines - 3].dirty = 1;
6162         if (view->lines > 1)
6163                 view->line[view->lines - 2].dirty = 1;
6164         draw_rev_graph(graph->prev);
6165         done_rev_graph(graph->prev->prev);
6169 /*
6170  * Main view backend
6171  */
6173 static const char *main_argv[SIZEOF_ARG] = {
6174         "git", "log", "--no-color", "--pretty=raw", "--parents",
6175                       "--topo-order", "%(head)", NULL
6176 };
6178 static bool
6179 main_draw(struct view *view, struct line *line, unsigned int lineno)
6181         struct commit *commit = line->data;
6183         if (!commit->author)
6184                 return FALSE;
6186         if (opt_date && draw_date(view, &commit->time))
6187                 return TRUE;
6189         if (opt_author && draw_author(view, commit->author))
6190                 return TRUE;
6192         if (opt_rev_graph && commit->graph_size &&
6193             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6194                 return TRUE;
6196         if (opt_show_refs && commit->refs) {
6197                 size_t i;
6199                 for (i = 0; i < commit->refs->size; i++) {
6200                         struct ref *ref = commit->refs->refs[i];
6201                         enum line_type type;
6203                         if (ref->head)
6204                                 type = LINE_MAIN_HEAD;
6205                         else if (ref->ltag)
6206                                 type = LINE_MAIN_LOCAL_TAG;
6207                         else if (ref->tag)
6208                                 type = LINE_MAIN_TAG;
6209                         else if (ref->tracked)
6210                                 type = LINE_MAIN_TRACKED;
6211                         else if (ref->remote)
6212                                 type = LINE_MAIN_REMOTE;
6213                         else
6214                                 type = LINE_MAIN_REF;
6216                         if (draw_text(view, type, "[", TRUE) ||
6217                             draw_text(view, type, ref->name, TRUE) ||
6218                             draw_text(view, type, "]", TRUE))
6219                                 return TRUE;
6221                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6222                                 return TRUE;
6223                 }
6224         }
6226         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6227         return TRUE;
6230 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6231 static bool
6232 main_read(struct view *view, char *line)
6234         static struct rev_graph *graph = graph_stacks;
6235         enum line_type type;
6236         struct commit *commit;
6238         if (!line) {
6239                 int i;
6241                 if (!view->lines && !view->parent)
6242                         die("No revisions match the given arguments.");
6243                 if (view->lines > 0) {
6244                         commit = view->line[view->lines - 1].data;
6245                         view->line[view->lines - 1].dirty = 1;
6246                         if (!commit->author) {
6247                                 view->lines--;
6248                                 free(commit);
6249                                 graph->commit = NULL;
6250                         }
6251                 }
6252                 update_rev_graph(view, graph);
6254                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6255                         clear_rev_graph(&graph_stacks[i]);
6256                 return TRUE;
6257         }
6259         type = get_line_type(line);
6260         if (type == LINE_COMMIT) {
6261                 commit = calloc(1, sizeof(struct commit));
6262                 if (!commit)
6263                         return FALSE;
6265                 line += STRING_SIZE("commit ");
6266                 if (*line == '-') {
6267                         graph->boundary = 1;
6268                         line++;
6269                 }
6271                 string_copy_rev(commit->id, line);
6272                 commit->refs = get_ref_list(commit->id);
6273                 graph->commit = commit;
6274                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6276                 while ((line = strchr(line, ' '))) {
6277                         line++;
6278                         push_rev_graph(graph->parents, line);
6279                         commit->has_parents = TRUE;
6280                 }
6281                 return TRUE;
6282         }
6284         if (!view->lines)
6285                 return TRUE;
6286         commit = view->line[view->lines - 1].data;
6288         switch (type) {
6289         case LINE_PARENT:
6290                 if (commit->has_parents)
6291                         break;
6292                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6293                 break;
6295         case LINE_AUTHOR:
6296                 parse_author_line(line + STRING_SIZE("author "),
6297                                   &commit->author, &commit->time);
6298                 update_rev_graph(view, graph);
6299                 graph = graph->next;
6300                 break;
6302         default:
6303                 /* Fill in the commit title if it has not already been set. */
6304                 if (commit->title[0])
6305                         break;
6307                 /* Require titles to start with a non-space character at the
6308                  * offset used by git log. */
6309                 if (strncmp(line, "    ", 4))
6310                         break;
6311                 line += 4;
6312                 /* Well, if the title starts with a whitespace character,
6313                  * try to be forgiving.  Otherwise we end up with no title. */
6314                 while (isspace(*line))
6315                         line++;
6316                 if (*line == '\0')
6317                         break;
6318                 /* FIXME: More graceful handling of titles; append "..." to
6319                  * shortened titles, etc. */
6321                 string_expand(commit->title, sizeof(commit->title), line, 1);
6322                 view->line[view->lines - 1].dirty = 1;
6323         }
6325         return TRUE;
6328 static enum request
6329 main_request(struct view *view, enum request request, struct line *line)
6331         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6333         switch (request) {
6334         case REQ_ENTER:
6335                 open_view(view, REQ_VIEW_DIFF, flags);
6336                 break;
6337         case REQ_REFRESH:
6338                 load_refs();
6339                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6340                 break;
6341         default:
6342                 return request;
6343         }
6345         return REQ_NONE;
6348 static bool
6349 grep_refs(struct ref_list *list, regex_t *regex)
6351         regmatch_t pmatch;
6352         size_t i;
6354         if (!opt_show_refs || !list)
6355                 return FALSE;
6357         for (i = 0; i < list->size; i++) {
6358                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6359                         return TRUE;
6360         }
6362         return FALSE;
6365 static bool
6366 main_grep(struct view *view, struct line *line)
6368         struct commit *commit = line->data;
6369         const char *text[] = {
6370                 commit->title,
6371                 opt_author ? commit->author : "",
6372                 opt_date ? mkdate(&commit->time) : "",
6373                 NULL
6374         };
6376         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6379 static void
6380 main_select(struct view *view, struct line *line)
6382         struct commit *commit = line->data;
6384         string_copy_rev(view->ref, commit->id);
6385         string_copy_rev(ref_commit, view->ref);
6388 static struct view_ops main_ops = {
6389         "commit",
6390         main_argv,
6391         NULL,
6392         main_read,
6393         main_draw,
6394         main_request,
6395         main_grep,
6396         main_select,
6397 };
6400 /*
6401  * Unicode / UTF-8 handling
6402  *
6403  * NOTE: Much of the following code for dealing with Unicode is derived from
6404  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6405  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6406  */
6408 static inline int
6409 unicode_width(unsigned long c)
6411         if (c >= 0x1100 &&
6412            (c <= 0x115f                         /* Hangul Jamo */
6413             || c == 0x2329
6414             || c == 0x232a
6415             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6416                                                 /* CJK ... Yi */
6417             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6418             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6419             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6420             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6421             || (c >= 0xffe0  && c <= 0xffe6)
6422             || (c >= 0x20000 && c <= 0x2fffd)
6423             || (c >= 0x30000 && c <= 0x3fffd)))
6424                 return 2;
6426         if (c == '\t')
6427                 return opt_tab_size;
6429         return 1;
6432 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6433  * Illegal bytes are set one. */
6434 static const unsigned char utf8_bytes[256] = {
6435         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,
6436         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,
6437         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,
6438         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,
6439         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,
6440         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,
6441         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,
6442         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,
6443 };
6445 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6446 static inline unsigned long
6447 utf8_to_unicode(const char *string, size_t length)
6449         unsigned long unicode;
6451         switch (length) {
6452         case 1:
6453                 unicode  =   string[0];
6454                 break;
6455         case 2:
6456                 unicode  =  (string[0] & 0x1f) << 6;
6457                 unicode +=  (string[1] & 0x3f);
6458                 break;
6459         case 3:
6460                 unicode  =  (string[0] & 0x0f) << 12;
6461                 unicode += ((string[1] & 0x3f) << 6);
6462                 unicode +=  (string[2] & 0x3f);
6463                 break;
6464         case 4:
6465                 unicode  =  (string[0] & 0x0f) << 18;
6466                 unicode += ((string[1] & 0x3f) << 12);
6467                 unicode += ((string[2] & 0x3f) << 6);
6468                 unicode +=  (string[3] & 0x3f);
6469                 break;
6470         case 5:
6471                 unicode  =  (string[0] & 0x0f) << 24;
6472                 unicode += ((string[1] & 0x3f) << 18);
6473                 unicode += ((string[2] & 0x3f) << 12);
6474                 unicode += ((string[3] & 0x3f) << 6);
6475                 unicode +=  (string[4] & 0x3f);
6476                 break;
6477         case 6:
6478                 unicode  =  (string[0] & 0x01) << 30;
6479                 unicode += ((string[1] & 0x3f) << 24);
6480                 unicode += ((string[2] & 0x3f) << 18);
6481                 unicode += ((string[3] & 0x3f) << 12);
6482                 unicode += ((string[4] & 0x3f) << 6);
6483                 unicode +=  (string[5] & 0x3f);
6484                 break;
6485         default:
6486                 die("Invalid Unicode length");
6487         }
6489         /* Invalid characters could return the special 0xfffd value but NUL
6490          * should be just as good. */
6491         return unicode > 0xffff ? 0 : unicode;
6494 /* Calculates how much of string can be shown within the given maximum width
6495  * and sets trimmed parameter to non-zero value if all of string could not be
6496  * shown. If the reserve flag is TRUE, it will reserve at least one
6497  * trailing character, which can be useful when drawing a delimiter.
6498  *
6499  * Returns the number of bytes to output from string to satisfy max_width. */
6500 static size_t
6501 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6503         const char *string = *start;
6504         const char *end = strchr(string, '\0');
6505         unsigned char last_bytes = 0;
6506         size_t last_ucwidth = 0;
6508         *width = 0;
6509         *trimmed = 0;
6511         while (string < end) {
6512                 int c = *(unsigned char *) string;
6513                 unsigned char bytes = utf8_bytes[c];
6514                 size_t ucwidth;
6515                 unsigned long unicode;
6517                 if (string + bytes > end)
6518                         break;
6520                 /* Change representation to figure out whether
6521                  * it is a single- or double-width character. */
6523                 unicode = utf8_to_unicode(string, bytes);
6524                 /* FIXME: Graceful handling of invalid Unicode character. */
6525                 if (!unicode)
6526                         break;
6528                 ucwidth = unicode_width(unicode);
6529                 if (skip > 0) {
6530                         skip -= ucwidth <= skip ? ucwidth : skip;
6531                         *start += bytes;
6532                 }
6533                 *width  += ucwidth;
6534                 if (*width > max_width) {
6535                         *trimmed = 1;
6536                         *width -= ucwidth;
6537                         if (reserve && *width == max_width) {
6538                                 string -= last_bytes;
6539                                 *width -= last_ucwidth;
6540                         }
6541                         break;
6542                 }
6544                 string  += bytes;
6545                 last_bytes = ucwidth ? bytes : 0;
6546                 last_ucwidth = ucwidth;
6547         }
6549         return string - *start;
6553 /*
6554  * Status management
6555  */
6557 /* Whether or not the curses interface has been initialized. */
6558 static bool cursed = FALSE;
6560 /* Terminal hacks and workarounds. */
6561 static bool use_scroll_redrawwin;
6562 static bool use_scroll_status_wclear;
6564 /* The status window is used for polling keystrokes. */
6565 static WINDOW *status_win;
6567 /* Reading from the prompt? */
6568 static bool input_mode = FALSE;
6570 static bool status_empty = FALSE;
6572 /* Update status and title window. */
6573 static void
6574 report(const char *msg, ...)
6576         struct view *view = display[current_view];
6578         if (input_mode)
6579                 return;
6581         if (!view) {
6582                 char buf[SIZEOF_STR];
6583                 va_list args;
6585                 va_start(args, msg);
6586                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6587                         buf[sizeof(buf) - 1] = 0;
6588                         buf[sizeof(buf) - 2] = '.';
6589                         buf[sizeof(buf) - 3] = '.';
6590                         buf[sizeof(buf) - 4] = '.';
6591                 }
6592                 va_end(args);
6593                 die("%s", buf);
6594         }
6596         if (!status_empty || *msg) {
6597                 va_list args;
6599                 va_start(args, msg);
6601                 wmove(status_win, 0, 0);
6602                 if (view->has_scrolled && use_scroll_status_wclear)
6603                         wclear(status_win);
6604                 if (*msg) {
6605                         vwprintw(status_win, msg, args);
6606                         status_empty = FALSE;
6607                 } else {
6608                         status_empty = TRUE;
6609                 }
6610                 wclrtoeol(status_win);
6611                 wnoutrefresh(status_win);
6613                 va_end(args);
6614         }
6616         update_view_title(view);
6619 /* Controls when nodelay should be in effect when polling user input. */
6620 static void
6621 set_nonblocking_input(bool loading)
6623         static unsigned int loading_views;
6625         if ((loading == FALSE && loading_views-- == 1) ||
6626             (loading == TRUE  && loading_views++ == 0))
6627                 nodelay(status_win, loading);
6630 static void
6631 init_display(void)
6633         const char *term;
6634         int x, y;
6636         /* Initialize the curses library */
6637         if (isatty(STDIN_FILENO)) {
6638                 cursed = !!initscr();
6639                 opt_tty = stdin;
6640         } else {
6641                 /* Leave stdin and stdout alone when acting as a pager. */
6642                 opt_tty = fopen("/dev/tty", "r+");
6643                 if (!opt_tty)
6644                         die("Failed to open /dev/tty");
6645                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6646         }
6648         if (!cursed)
6649                 die("Failed to initialize curses");
6651         nonl();         /* Disable conversion and detect newlines from input. */
6652         cbreak();       /* Take input chars one at a time, no wait for \n */
6653         noecho();       /* Don't echo input */
6654         leaveok(stdscr, FALSE);
6656         if (has_colors())
6657                 init_colors();
6659         getmaxyx(stdscr, y, x);
6660         status_win = newwin(1, 0, y - 1, 0);
6661         if (!status_win)
6662                 die("Failed to create status window");
6664         /* Enable keyboard mapping */
6665         keypad(status_win, TRUE);
6666         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6668         TABSIZE = opt_tab_size;
6669         if (opt_line_graphics) {
6670                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6671         }
6673         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6674         if (term && !strcmp(term, "gnome-terminal")) {
6675                 /* In the gnome-terminal-emulator, the message from
6676                  * scrolling up one line when impossible followed by
6677                  * scrolling down one line causes corruption of the
6678                  * status line. This is fixed by calling wclear. */
6679                 use_scroll_status_wclear = TRUE;
6680                 use_scroll_redrawwin = FALSE;
6682         } else if (term && !strcmp(term, "xrvt-xpm")) {
6683                 /* No problems with full optimizations in xrvt-(unicode)
6684                  * and aterm. */
6685                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6687         } else {
6688                 /* When scrolling in (u)xterm the last line in the
6689                  * scrolling direction will update slowly. */
6690                 use_scroll_redrawwin = TRUE;
6691                 use_scroll_status_wclear = FALSE;
6692         }
6695 static int
6696 get_input(int prompt_position)
6698         struct view *view;
6699         int i, key, cursor_y, cursor_x;
6701         if (prompt_position)
6702                 input_mode = TRUE;
6704         while (TRUE) {
6705                 foreach_view (view, i) {
6706                         update_view(view);
6707                         if (view_is_displayed(view) && view->has_scrolled &&
6708                             use_scroll_redrawwin)
6709                                 redrawwin(view->win);
6710                         view->has_scrolled = FALSE;
6711                 }
6713                 /* Update the cursor position. */
6714                 if (prompt_position) {
6715                         getbegyx(status_win, cursor_y, cursor_x);
6716                         cursor_x = prompt_position;
6717                 } else {
6718                         view = display[current_view];
6719                         getbegyx(view->win, cursor_y, cursor_x);
6720                         cursor_x = view->width - 1;
6721                         cursor_y += view->lineno - view->offset;
6722                 }
6723                 setsyx(cursor_y, cursor_x);
6725                 /* Refresh, accept single keystroke of input */
6726                 doupdate();
6727                 key = wgetch(status_win);
6729                 /* wgetch() with nodelay() enabled returns ERR when
6730                  * there's no input. */
6731                 if (key == ERR) {
6733                 } else if (key == KEY_RESIZE) {
6734                         int height, width;
6736                         getmaxyx(stdscr, height, width);
6738                         wresize(status_win, 1, width);
6739                         mvwin(status_win, height - 1, 0);
6740                         wnoutrefresh(status_win);
6741                         resize_display();
6742                         redraw_display(TRUE);
6744                 } else {
6745                         input_mode = FALSE;
6746                         return key;
6747                 }
6748         }
6751 static char *
6752 prompt_input(const char *prompt, input_handler handler, void *data)
6754         enum input_status status = INPUT_OK;
6755         static char buf[SIZEOF_STR];
6756         size_t pos = 0;
6758         buf[pos] = 0;
6760         while (status == INPUT_OK || status == INPUT_SKIP) {
6761                 int key;
6763                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6764                 wclrtoeol(status_win);
6766                 key = get_input(pos + 1);
6767                 switch (key) {
6768                 case KEY_RETURN:
6769                 case KEY_ENTER:
6770                 case '\n':
6771                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6772                         break;
6774                 case KEY_BACKSPACE:
6775                         if (pos > 0)
6776                                 buf[--pos] = 0;
6777                         else
6778                                 status = INPUT_CANCEL;
6779                         break;
6781                 case KEY_ESC:
6782                         status = INPUT_CANCEL;
6783                         break;
6785                 default:
6786                         if (pos >= sizeof(buf)) {
6787                                 report("Input string too long");
6788                                 return NULL;
6789                         }
6791                         status = handler(data, buf, key);
6792                         if (status == INPUT_OK)
6793                                 buf[pos++] = (char) key;
6794                 }
6795         }
6797         /* Clear the status window */
6798         status_empty = FALSE;
6799         report("");
6801         if (status == INPUT_CANCEL)
6802                 return NULL;
6804         buf[pos++] = 0;
6806         return buf;
6809 static enum input_status
6810 prompt_yesno_handler(void *data, char *buf, int c)
6812         if (c == 'y' || c == 'Y')
6813                 return INPUT_STOP;
6814         if (c == 'n' || c == 'N')
6815                 return INPUT_CANCEL;
6816         return INPUT_SKIP;
6819 static bool
6820 prompt_yesno(const char *prompt)
6822         char prompt2[SIZEOF_STR];
6824         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6825                 return FALSE;
6827         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6830 static enum input_status
6831 read_prompt_handler(void *data, char *buf, int c)
6833         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6836 static char *
6837 read_prompt(const char *prompt)
6839         return prompt_input(prompt, read_prompt_handler, NULL);
6842 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
6844         enum input_status status = INPUT_OK;
6845         int size = 0;
6847         while (items[size].text)
6848                 size++;
6850         while (status == INPUT_OK) {
6851                 const struct menu_item *item = &items[*selected];
6852                 int key;
6853                 int i;
6855                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
6856                           prompt, *selected + 1, size);
6857                 if (item->hotkey)
6858                         wprintw(status_win, "[%c] ", (char) item->hotkey);
6859                 wprintw(status_win, "%s", item->text);
6860                 wclrtoeol(status_win);
6862                 key = get_input(COLS - 1);
6863                 switch (key) {
6864                 case KEY_RETURN:
6865                 case KEY_ENTER:
6866                 case '\n':
6867                         status = INPUT_STOP;
6868                         break;
6870                 case KEY_LEFT:
6871                 case KEY_UP:
6872                         *selected = *selected - 1;
6873                         if (*selected < 0)
6874                                 *selected = size - 1;
6875                         break;
6877                 case KEY_RIGHT:
6878                 case KEY_DOWN:
6879                         *selected = (*selected + 1) % size;
6880                         break;
6882                 case KEY_ESC:
6883                         status = INPUT_CANCEL;
6884                         break;
6886                 default:
6887                         for (i = 0; items[i].text; i++)
6888                                 if (items[i].hotkey == key) {
6889                                         *selected = i;
6890                                         status = INPUT_STOP;
6891                                         break;
6892                                 }
6893                 }
6894         }
6896         /* Clear the status window */
6897         status_empty = FALSE;
6898         report("");
6900         return status != INPUT_CANCEL;
6903 /*
6904  * Repository properties
6905  */
6907 static struct ref **refs = NULL;
6908 static size_t refs_size = 0;
6910 static struct ref_list **ref_lists = NULL;
6911 static size_t ref_lists_size = 0;
6913 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
6914 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6915 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
6917 static int
6918 compare_refs(const void *ref1_, const void *ref2_)
6920         const struct ref *ref1 = *(const struct ref **)ref1_;
6921         const struct ref *ref2 = *(const struct ref **)ref2_;
6923         if (ref1->tag != ref2->tag)
6924                 return ref2->tag - ref1->tag;
6925         if (ref1->ltag != ref2->ltag)
6926                 return ref2->ltag - ref2->ltag;
6927         if (ref1->head != ref2->head)
6928                 return ref2->head - ref1->head;
6929         if (ref1->tracked != ref2->tracked)
6930                 return ref2->tracked - ref1->tracked;
6931         if (ref1->remote != ref2->remote)
6932                 return ref2->remote - ref1->remote;
6933         return strcmp(ref1->name, ref2->name);
6936 static void
6937 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6939         size_t i;
6941         for (i = 0; i < refs_size; i++)
6942                 if (!visitor(data, refs[i]))
6943                         break;
6946 static struct ref_list *
6947 get_ref_list(const char *id)
6949         struct ref_list *list;
6950         size_t i;
6952         for (i = 0; i < ref_lists_size; i++)
6953                 if (!strcmp(id, ref_lists[i]->id))
6954                         return ref_lists[i];
6956         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
6957                 return NULL;
6958         list = calloc(1, sizeof(*list));
6959         if (!list)
6960                 return NULL;
6962         for (i = 0; i < refs_size; i++) {
6963                 if (!strcmp(id, refs[i]->id) &&
6964                     realloc_refs_list(&list->refs, list->size, 1))
6965                         list->refs[list->size++] = refs[i];
6966         }
6968         if (!list->refs) {
6969                 free(list);
6970                 return NULL;
6971         }
6973         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
6974         ref_lists[ref_lists_size++] = list;
6975         return list;
6978 static int
6979 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6981         struct ref *ref = NULL;
6982         bool tag = FALSE;
6983         bool ltag = FALSE;
6984         bool remote = FALSE;
6985         bool tracked = FALSE;
6986         bool head = FALSE;
6987         int from = 0, to = refs_size - 1;
6989         if (!prefixcmp(name, "refs/tags/")) {
6990                 if (!suffixcmp(name, namelen, "^{}")) {
6991                         namelen -= 3;
6992                         name[namelen] = 0;
6993                 } else {
6994                         ltag = TRUE;
6995                 }
6997                 tag = TRUE;
6998                 namelen -= STRING_SIZE("refs/tags/");
6999                 name    += STRING_SIZE("refs/tags/");
7001         } else if (!prefixcmp(name, "refs/remotes/")) {
7002                 remote = TRUE;
7003                 namelen -= STRING_SIZE("refs/remotes/");
7004                 name    += STRING_SIZE("refs/remotes/");
7005                 tracked  = !strcmp(opt_remote, name);
7007         } else if (!prefixcmp(name, "refs/heads/")) {
7008                 namelen -= STRING_SIZE("refs/heads/");
7009                 name    += STRING_SIZE("refs/heads/");
7010                 head     = !strncmp(opt_head, name, namelen);
7012         } else if (!strcmp(name, "HEAD")) {
7013                 string_ncopy(opt_head_rev, id, idlen);
7014                 return OK;
7015         }
7017         /* If we are reloading or it's an annotated tag, replace the
7018          * previous SHA1 with the resolved commit id; relies on the fact
7019          * git-ls-remote lists the commit id of an annotated tag right
7020          * before the commit id it points to. */
7021         while (from <= to) {
7022                 size_t pos = (to + from) / 2;
7023                 int cmp = strcmp(name, refs[pos]->name);
7025                 if (!cmp) {
7026                         ref = refs[pos];
7027                         break;
7028                 }
7030                 if (cmp < 0)
7031                         to = pos - 1;
7032                 else
7033                         from = pos + 1;
7034         }
7036         if (!ref) {
7037                 if (!realloc_refs(&refs, refs_size, 1))
7038                         return ERR;
7039                 ref = calloc(1, sizeof(*ref) + namelen);
7040                 if (!ref)
7041                         return ERR;
7042                 memmove(refs + from + 1, refs + from,
7043                         (refs_size - from) * sizeof(*refs));
7044                 refs[from] = ref;
7045                 strncpy(ref->name, name, namelen);
7046                 refs_size++;
7047         }
7049         ref->head = head;
7050         ref->tag = tag;
7051         ref->ltag = ltag;
7052         ref->remote = remote;
7053         ref->tracked = tracked;
7054         string_copy_rev(ref->id, id);
7056         return OK;
7059 static int
7060 load_refs(void)
7062         const char *head_argv[] = {
7063                 "git", "symbolic-ref", "HEAD", NULL
7064         };
7065         static const char *ls_remote_argv[SIZEOF_ARG] = {
7066                 "git", "ls-remote", opt_git_dir, NULL
7067         };
7068         static bool init = FALSE;
7069         size_t i;
7071         if (!init) {
7072                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7073                 init = TRUE;
7074         }
7076         if (!*opt_git_dir)
7077                 return OK;
7079         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7080             !prefixcmp(opt_head, "refs/heads/")) {
7081                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7083                 memmove(opt_head, offset, strlen(offset) + 1);
7084         }
7086         for (i = 0; i < refs_size; i++)
7087                 refs[i]->id[0] = 0;
7089         if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7090                 return ERR;
7092         /* Update the ref lists to reflect changes. */
7093         for (i = 0; i < ref_lists_size; i++) {
7094                 struct ref_list *list = ref_lists[i];
7095                 size_t old, new;
7097                 for (old = new = 0; old < list->size; old++)
7098                         if (!strcmp(list->id, list->refs[old]->id))
7099                                 list->refs[new++] = list->refs[old];
7100                 list->size = new;
7101         }
7103         return OK;
7106 static void
7107 set_remote_branch(const char *name, const char *value, size_t valuelen)
7109         if (!strcmp(name, ".remote")) {
7110                 string_ncopy(opt_remote, value, valuelen);
7112         } else if (*opt_remote && !strcmp(name, ".merge")) {
7113                 size_t from = strlen(opt_remote);
7115                 if (!prefixcmp(value, "refs/heads/"))
7116                         value += STRING_SIZE("refs/heads/");
7118                 if (!string_format_from(opt_remote, &from, "/%s", value))
7119                         opt_remote[0] = 0;
7120         }
7123 static void
7124 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7126         const char *argv[SIZEOF_ARG] = { name, "=" };
7127         int argc = 1 + (cmd == option_set_command);
7128         int error = ERR;
7130         if (!argv_from_string(argv, &argc, value))
7131                 config_msg = "Too many option arguments";
7132         else
7133                 error = cmd(argc, argv);
7135         if (error == ERR)
7136                 warn("Option 'tig.%s': %s", name, config_msg);
7139 static bool
7140 set_environment_variable(const char *name, const char *value)
7142         size_t len = strlen(name) + 1 + strlen(value) + 1;
7143         char *env = malloc(len);
7145         if (env &&
7146             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7147             putenv(env) == 0)
7148                 return TRUE;
7149         free(env);
7150         return FALSE;
7153 static void
7154 set_work_tree(const char *value)
7156         char cwd[SIZEOF_STR];
7158         if (!getcwd(cwd, sizeof(cwd)))
7159                 die("Failed to get cwd path: %s", strerror(errno));
7160         if (chdir(opt_git_dir) < 0)
7161                 die("Failed to chdir(%s): %s", strerror(errno));
7162         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7163                 die("Failed to get git path: %s", strerror(errno));
7164         if (chdir(cwd) < 0)
7165                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7166         if (chdir(value) < 0)
7167                 die("Failed to chdir(%s): %s", value, strerror(errno));
7168         if (!getcwd(cwd, sizeof(cwd)))
7169                 die("Failed to get cwd path: %s", strerror(errno));
7170         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7171                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7172         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7173                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7174         opt_is_inside_work_tree = TRUE;
7177 static int
7178 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7180         if (!strcmp(name, "i18n.commitencoding"))
7181                 string_ncopy(opt_encoding, value, valuelen);
7183         else if (!strcmp(name, "core.editor"))
7184                 string_ncopy(opt_editor, value, valuelen);
7186         else if (!strcmp(name, "core.worktree"))
7187                 set_work_tree(value);
7189         else if (!prefixcmp(name, "tig.color."))
7190                 set_repo_config_option(name + 10, value, option_color_command);
7192         else if (!prefixcmp(name, "tig.bind."))
7193                 set_repo_config_option(name + 9, value, option_bind_command);
7195         else if (!prefixcmp(name, "tig."))
7196                 set_repo_config_option(name + 4, value, option_set_command);
7198         else if (*opt_head && !prefixcmp(name, "branch.") &&
7199                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7200                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7202         return OK;
7205 static int
7206 load_git_config(void)
7208         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7210         return run_io_load(config_list_argv, "=", read_repo_config_option);
7213 static int
7214 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7216         if (!opt_git_dir[0]) {
7217                 string_ncopy(opt_git_dir, name, namelen);
7219         } else if (opt_is_inside_work_tree == -1) {
7220                 /* This can be 3 different values depending on the
7221                  * version of git being used. If git-rev-parse does not
7222                  * understand --is-inside-work-tree it will simply echo
7223                  * the option else either "true" or "false" is printed.
7224                  * Default to true for the unknown case. */
7225                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7227         } else if (*name == '.') {
7228                 string_ncopy(opt_cdup, name, namelen);
7230         } else {
7231                 string_ncopy(opt_prefix, name, namelen);
7232         }
7234         return OK;
7237 static int
7238 load_repo_info(void)
7240         const char *rev_parse_argv[] = {
7241                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7242                         "--show-cdup", "--show-prefix", NULL
7243         };
7245         return run_io_load(rev_parse_argv, "=", read_repo_info);
7249 /*
7250  * Main
7251  */
7253 static const char usage[] =
7254 "tig " TIG_VERSION " (" __DATE__ ")\n"
7255 "\n"
7256 "Usage: tig        [options] [revs] [--] [paths]\n"
7257 "   or: tig show   [options] [revs] [--] [paths]\n"
7258 "   or: tig blame  [rev] path\n"
7259 "   or: tig status\n"
7260 "   or: tig <      [git command output]\n"
7261 "\n"
7262 "Options:\n"
7263 "  -v, --version   Show version and exit\n"
7264 "  -h, --help      Show help message and exit";
7266 static void __NORETURN
7267 quit(int sig)
7269         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7270         if (cursed)
7271                 endwin();
7272         exit(0);
7275 static void __NORETURN
7276 die(const char *err, ...)
7278         va_list args;
7280         endwin();
7282         va_start(args, err);
7283         fputs("tig: ", stderr);
7284         vfprintf(stderr, err, args);
7285         fputs("\n", stderr);
7286         va_end(args);
7288         exit(1);
7291 static void
7292 warn(const char *msg, ...)
7294         va_list args;
7296         va_start(args, msg);
7297         fputs("tig warning: ", stderr);
7298         vfprintf(stderr, msg, args);
7299         fputs("\n", stderr);
7300         va_end(args);
7303 static enum request
7304 parse_options(int argc, const char *argv[])
7306         enum request request = REQ_VIEW_MAIN;
7307         const char *subcommand;
7308         bool seen_dashdash = FALSE;
7309         /* XXX: This is vulnerable to the user overriding options
7310          * required for the main view parser. */
7311         const char *custom_argv[SIZEOF_ARG] = {
7312                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7313                         "--topo-order", NULL
7314         };
7315         int i, j = 6;
7317         if (!isatty(STDIN_FILENO)) {
7318                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7319                 return REQ_VIEW_PAGER;
7320         }
7322         if (argc <= 1)
7323                 return REQ_NONE;
7325         subcommand = argv[1];
7326         if (!strcmp(subcommand, "status")) {
7327                 if (argc > 2)
7328                         warn("ignoring arguments after `%s'", subcommand);
7329                 return REQ_VIEW_STATUS;
7331         } else if (!strcmp(subcommand, "blame")) {
7332                 if (argc <= 2 || argc > 4)
7333                         die("invalid number of options to blame\n\n%s", usage);
7335                 i = 2;
7336                 if (argc == 4) {
7337                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7338                         i++;
7339                 }
7341                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7342                 return REQ_VIEW_BLAME;
7344         } else if (!strcmp(subcommand, "show")) {
7345                 request = REQ_VIEW_DIFF;
7347         } else {
7348                 subcommand = NULL;
7349         }
7351         if (subcommand) {
7352                 custom_argv[1] = subcommand;
7353                 j = 2;
7354         }
7356         for (i = 1 + !!subcommand; i < argc; i++) {
7357                 const char *opt = argv[i];
7359                 if (seen_dashdash || !strcmp(opt, "--")) {
7360                         seen_dashdash = TRUE;
7362                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7363                         printf("tig version %s\n", TIG_VERSION);
7364                         quit(0);
7366                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7367                         printf("%s\n", usage);
7368                         quit(0);
7369                 }
7371                 custom_argv[j++] = opt;
7372                 if (j >= ARRAY_SIZE(custom_argv))
7373                         die("command too long");
7374         }
7376         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
7377                 die("Failed to format arguments"); 
7379         return request;
7382 int
7383 main(int argc, const char *argv[])
7385         enum request request = parse_options(argc, argv);
7386         struct view *view;
7387         size_t i;
7389         signal(SIGINT, quit);
7390         signal(SIGPIPE, SIG_IGN);
7392         if (setlocale(LC_ALL, "")) {
7393                 char *codeset = nl_langinfo(CODESET);
7395                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7396         }
7398         if (load_repo_info() == ERR)
7399                 die("Failed to load repo info.");
7401         if (load_options() == ERR)
7402                 die("Failed to load user config.");
7404         if (load_git_config() == ERR)
7405                 die("Failed to load repo config.");
7407         /* Require a git repository unless when running in pager mode. */
7408         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7409                 die("Not a git repository");
7411         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7412                 opt_utf8 = FALSE;
7414         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7415                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7416                 if (opt_iconv == ICONV_NONE)
7417                         die("Failed to initialize character set conversion");
7418         }
7420         if (load_refs() == ERR)
7421                 die("Failed to load refs.");
7423         foreach_view (view, i)
7424                 argv_from_env(view->ops->argv, view->cmd_env);
7426         init_display();
7428         if (request != REQ_NONE)
7429                 open_view(NULL, request, OPEN_PREPARED);
7430         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7432         while (view_driver(display[current_view], request)) {
7433                 int key = get_input(0);
7435                 view = display[current_view];
7436                 request = get_keybinding(view->keymap, key);
7438                 /* Some low-level request handling. This keeps access to
7439                  * status_win restricted. */
7440                 switch (request) {
7441                 case REQ_PROMPT:
7442                 {
7443                         char *cmd = read_prompt(":");
7445                         if (cmd && isdigit(*cmd)) {
7446                                 int lineno = view->lineno + 1;
7448                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7449                                         select_view_line(view, lineno - 1);
7450                                         report("");
7451                                 } else {
7452                                         report("Unable to parse '%s' as a line number", cmd);
7453                                 }
7455                         } else if (cmd) {
7456                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7457                                 const char *argv[SIZEOF_ARG] = { "git" };
7458                                 int argc = 1;
7460                                 /* When running random commands, initially show the
7461                                  * command in the title. However, it maybe later be
7462                                  * overwritten if a commit line is selected. */
7463                                 string_ncopy(next->ref, cmd, strlen(cmd));
7465                                 if (!argv_from_string(argv, &argc, cmd)) {
7466                                         report("Too many arguments");
7467                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7468                                         report("Failed to format command");
7469                                 } else {
7470                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7471                                 }
7472                         }
7474                         request = REQ_NONE;
7475                         break;
7476                 }
7477                 case REQ_SEARCH:
7478                 case REQ_SEARCH_BACK:
7479                 {
7480                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7481                         char *search = read_prompt(prompt);
7483                         if (search)
7484                                 string_ncopy(opt_search, search, strlen(search));
7485                         else if (*opt_search)
7486                                 request = request == REQ_SEARCH ?
7487                                         REQ_FIND_NEXT :
7488                                         REQ_FIND_PREV;
7489                         else
7490                                 request = REQ_NONE;
7491                         break;
7492                 }
7493                 default:
7494                         break;
7495                 }
7496         }
7498         quit(0);
7500         return 0;